autowired를 통해 주입한 HttpServletRequest 객체로 부터 MultipartRequest 객체를 얻어올수는 없나요?

3,450 views
Skip to first unread message

Jihwan Kim

unread,
Jan 13, 2012, 5:26:36 AM1/13/12
to ks...@googlegroups.com
Service layer에서 HttpServletRequest를 autowired를 통해 주입받아 사용하는데,
multipart request처리에 문제가 있습니다.

@Service("productManager")
public class ProductManagerImpl extends GenericManagerImpl<Product,
Long> implements ProductManager {
.....
@Autowired(required = true)
private HttpServletRequest request;

@Autowired
private CommonsMultipartResolver multipartResolver;

@Override
public Product save(Product product) throws Exception {
....
MultipartHttpServletRequest multipartRequest =
multipartResolver.resolveMultipart(request);
MultipartFile fileImage = multipartRequest.getFile("file_image");
....
}


위 코드에서 fileImage 객체를 가져올 수가 없습니다.

이상한것은 multipartResolver.isMultipart(request) 값은 true가 리턴됩니다.

Controller에서 Service layer에 parameterf로 HttpSerlvetRequet를 넘기는 것이 모양새가
좋지 않아 @Autowired를 이용해 주입시켜 처리할려고 했는데 파일 업로드에서 문제가 생기네요.

Domain 객체(Product) 에 private byte[] file; 프로퍼티를 선언한 후 그것을 이용한 업로드 처리는 가능합니다.

하지만 위 소스와 같은 방법으로는 원래 안되는 것인지, 제가 뭘 잘못하고 있는건지 궁금합니다.

황용대

unread,
Jan 13, 2012, 5:36:34 AM1/13/12
to ks...@googlegroups.com
서블릿 컨테이너가 관리하는 서블릿 객체의 리퀘스트가 넘어와야 하는데
저건 그냥 스프링이 관리하는 빈 객체이니 널은 아니겠지만 원하는 값들이 없을 것 같은데요

컨트롤러에서 멀티파트파일 객체를 파일 객체로 변환해 도메인 객체에 넣고 도메인 객체를 서비스에 넘기든지 해야 하겠네요

일단 서비스 레이어에 http리퀘스트가 들어있는 모양이 이상하네요

2012. 1. 13. 오후 7:26 Jihwan Kim <jhki...@gmail.com> 작성:

> --
> Google 그룹스 'Korea Spring User Group' 그룹에 가입했으므로 본 메일이 전송되었습니다.
> 이 그룹에 게시하려면 ks...@googlegroups.com(으)로 이메일을 보내세요.
> 그룹에서 탈퇴하려면 ksug+uns...@googlegroups.com로 이메일을 보내주세요.
> 더 많은 옵션을 보려면 http://groups.google.com/group/ksug?hl=ko에서 그룹을 방문하세요.
>

Jihwan Kim

unread,
Jan 13, 2012, 6:12:46 AM1/13/12
to ks...@googlegroups.com
네. 서비스 레이어에 있는게 이상하다는 점에는 동의합니다.

그러나 비즈니스 로직 처리하는 부분에 있어서 request객체로부터 필요한 값들이 있어어요. 물론 도메인 객체에 넣어서
전달하면 되겠지만,..그부분은 좀 더 고민해보겠습니다.
컨트롤러에서는 가급적 로직처리를 하지 않을려고 하다보니, 이런 경우가 생기네요.

현재로서는 도메인 모델 객체에 MutipartFile 객체를 추가하여 받는 방법밖에는 없을 것 같습니다.
하이버네이트 엔터티 객체를 화면단까지 같이 사용하고 있기 때문에 @Transient로 프로퍼티를 추가하면 소스가 좀
지저분해지는 것 같아, 서비스에서 처리해볼려고 한것인데 이런 문제가 생겼어요.

2012년 1월 13일 오후 7:36, 황용대 <sta...@gmail.com>님의 말:

Kesarr

unread,
Jan 13, 2012, 10:22:36 AM1/13/12
to ks...@googlegroups.com
일반적인 Servlet/Spring Web MVC 모델에서 HttpServletRequest는 HttpServlet이 각 요청 스레드 당 생성하여 메서드 파라미터로 제공하는 것으로 요청할 때마다 다른 값이 넘어오는 것으로 Spring 애플리케이션 콘텍스트가 관리하는 Spring bean이 아닙니다.

설령 Spring bean으로 관리할 수 있다해도 DispatcherServlet은 웹 계층의 서블릿 콘텍스트로 한정되므로 서비스 계층 또한 반드시 이 콘텍스트에 속해야 하는 문제가 있습니다.

사실 서비스 계층에 HttpServletRequest를 직접 파라미터로 전달하는 것이 부담스럽다면 콘트롤러에서 Product 타입의 객체를 생성할 때 이를 제공하고 서비스 계층에서 HttpServletRequest에 대한 의존 없이 사용할 수 있는 메서드를 Product에 추가하는 것이 나은 선택으로 보입니다만 다른 류의 대안을 원하시는 것 같군요.


현실적으로 설계를 변경하지 않고 타협할 수 있는 일반적인 선택안은 ThreadLocal holder를 활용하는 방법입니다.

이는 서블릿이 요청당 스레드 모델을 제공한다는 점을 활용하는 방법으로 ThreadLocal<HttpServletRequest>를 멤버로 갖는 스태틱 메서드 클래스나 Spring bean을 만들어 동일 스레드에 의해 처리되는 전 계층에 이를 공유할 수 있습니다.

단, 이 방법은 비동기 모델을 도입하고 있다면 사용할 수 없으며, OSGi처럼 각각의 클래스로더가 독립되는 경우에는 스태틱 멤버 방식을 활용하는데 제약이 있고, 애플리케이션 콘텍스트가 다른 계층에서는 Spring bean을 주입받는데 제약이 있다는 점을 참고하시면 좋습니다.


마지막으로 덧붙이자면 콘트롤러는 존재 이유가 뷰(프리젠테이션)와 모델(비즈니스 로직) 사이의 완충입니다.

콘트롤러에 처리 로직을 두느냐 마느냐보다 훨씬 더 중요한 문제는, 서비스 계층에 존재하는 비즈니스 로직으로부터 프리젠테이션 의존성, 즉 이것이 웹을 통해 사용자와 상호작용한다는 관심사 일체를 적절하게 콘트롤러로 추출해낼 수 있느냐가 아닐까하고 생각해봅니다.

@WhyKesarr. MetaDeveloper.
"Keep the modifications due to a single change together in a predictable range."

2012. 1. 13. 오후 8:12 Jihwan Kim <jhki...@gmail.com> 작성:

Jihwan Kim

unread,
Jan 14, 2012, 10:04:58 PM1/14/12
to ks...@googlegroups.com
네 답변 감사합니다.

Controller 는 전역 applicationContext에 등록하여 사용하고 있습니다.(AOP문제로) 웹계층 서블릿
컨텍스트에서 전역 컨텍스트로 옮긴것이구요. 어차피 서블릿컨텍스트는 하나만 사용하고 있으므로 별 관계없을것이라고 판단하였습니다.
즉 Controller와 Service를 동일한 전역 컨텍스트에 등록하여 사용하고 있으므로 말씀하신


"DispatcherServlet은 웹 계층의 서블릿 콘텍스트로 한정되므로 서비스 계층 또한 반드시 이 콘텍스트에 속해야 하는

문제가 있습니다." 대로 하고 있습니다.

제가 궁금한 것은 HttpServletRequest를 Autowired해서 사용할 경우 이것이 (Controller
layer나 Service layer나 동일합니다.) 다른 request는 처리하는데 문제가 없는데 FileUpload일
경우에만 처리하는데에 문제가 있어 왜 그런것일까 원래 안되는것일까, 아니면 스프링에서 그건 모델객체를 통해서 처리하라는
뜻일까..싶은 궁금증이었습니다.

@Autowired HttpServletRequest request 나,
RequestContextHolder.currentRequestAttributes()).getRequest() 를 통해서
HttpServletRequest에 접근하는게 가능하다는 것은 스프링이 관리해주는것으로 생각합니다.따라서 스프링이 관리하는
HttpServletRequest에서는 MultipartRequest는 지원하지 않는다가 제가 듣고싶은 대답이었던 것
같습니다. ^^. 다른 Request처리는 문제가 없는데 왜 multipart만 안되는것일까..하는게 궁금했구요.

위와같은 처리를 Controller에서 하지 않고 ServiceLayer에서 고집하는 이유는 케사르님이 말씀하신 비즈니스
로직에서 프리젠테이션 로직으로의 의존성문제를 포기하고,
개발 편이성에 타협했습니다.트랜잭션이 서비스 계층의 메소드단위로 움직이고, 그 트랜잭션을 결정하는 요소중에 하나가 파일처리도
관련되어 있어 서비스레이어에서 처리하도록 한것이에요.


2012년 1월 14일 오전 12:22, Kesarr <kes...@playxp.com>님의 말:

Sanghyuk Jung

unread,
Jan 16, 2012, 12:20:09 AM1/16/12
to ks...@googlegroups.com
아마도 HttpServletRequest에다 @Autowired를 걸면 ContextExposingHttpServletRequest 같은 것처럼, 스프링이 한번 원래의 HttpServletRequest를 감싼 Wrapper를 반환해주지 않을까하는 생각이 듭니다. AOP에 의해서 자동생성된 proxy일수도 있구요. 그래서 원래의 request가 MultipartHttpServletRequest라도 그 wrapper는 MultipartHttpServletRequest 타입이 아니라서 그런 문제가 생기지 않나 싶습니다.. @Autowired 받은 곳에서 한번 type을 찍어보면 명확해질듯합니다..



2012년 1월 15일 오후 12:04, Jihwan Kim <jhki...@gmail.com>님의 말:

Kesarr

unread,
Jan 16, 2012, 10:18:46 AM1/16/12
to ks...@googlegroups.com
제가 뭔가 오해를 하고 있는 지도 모르겠지만, 말씀하신 방법은 제가 말씀드린 조건과는 다른 것 같습니다.

제가 간단히 테스트해본 바에 따르면(그리고 이미 제가 적은 것처럼) HttpServletRequest Spring bean은 전역 애플리케이션 콘텍스트가 관리하는 bean이 아니라, DispatcherServlet이 관리하는 웹 계층의 콘텍스트에 한정되는 것으로 보입니다. 웹 계층의 콘텍스트가 아닌 곳에 정의된 서비스 bean에 HttpServletRequest가 정말로 주입되나요? 혹시 제가 뭔가 잘못 알고 있어 주입이 안되는 거라면 알려주세요.

물론 언급하신 RequestContextHolder는 제가 설명한 스태틱 ThreadLocal holder 패턴을 따르기 때문에 비동기 모델로 스레드를 사용하지 않고 클래스 로더가 동일한 조건 하에서는 애플리케이션 콘텍스트에 상관없이 사용할 수 있습니다.

아무튼 CommonsMultipartResolver를 대강 훑어본 바, HttpServletRequest의 구현 타입에 따라 동작하는 건 아닌 것 같은데 wrapper가 있다고 해서 안되는 건 아니지 않을까 싶네요. 정말 똑같은 상황에서 Multipart만 안되는 것인지 확인해봐야 할 것 같습니다. 제 답변이 도움이 못되는 것같아 죄송합니다.

@WhyKesarr. MetaDeveloper.
"Keep the modifications due to a single change together in a predictable range."

2012. 1. 15. 오후 12:04 Jihwan Kim <jhki...@gmail.com> 작성:

Jihwan Kim

unread,
Jan 16, 2012, 7:32:17 PM1/16/12
to ks...@googlegroups.com
네. 확실히 주입됩니다. 저도 뭔가 잘못생각하고있는지 몰라서 제가 설정한 내용을 좀 더 자세히 말씀드릴께요.

context 정의를 applicationContext.xml, dispatcher-servlet.xml 2개구요. 이외에
database라든지 다른 리소스관련 컨텍스트 들이 있습니다.

controller와 service는 applicationContext.xml에 정의했습니다. 아래와 같습니다.

[applicationContext.xml]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd"
default-lazy-init="true">
<context:annotation-config/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
<mvc:default-servlet-handler/>
<mvc:annotation-driven/>
<context:component-scan base-package="com.rsupport.remotecall.base">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Service" />
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Repository" />
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<context:component-scan
base-package="com.rsupport.remotecall.auth.base.service"/>
<context:component-scan
base-package="com.rsupport.remotecall.auth.operator.service"/>
<context:component-scan
base-package="com.rsupport.remotecall.auth.client.service"/>
<context:component-scan
base-package="com.rsupport.remotecall.auth.base.util"/>
<context:component-scan
base-package="com.rsupport.remotecall.auth.base.aop"/>
<context:component-scan
base-package="com.rsupport.remotecall.auth.base.controller"/>
<context:component-scan
base-package="com.rsupport.remotecall.auth.operator.controller"/>
<context:component-scan
base-package="com.rsupport.remotecall.auth.client.controller"/>
</beans>

[dispatcher-servlet.xml]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/oxm
http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
default-lazy-init="true">

<bean id="exceptionResolver"
class="com.rsupport.remotecall.auth.base.util.CustomSimpleMappingExceptionResolver">
</bean>

<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="2097152"/>
</bean>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver" />
<bean id="jsonView"
class="org.springframework.web.servlet.view.json.MappingJacksonJsonView"
/>
<bean id="jsonpView"
class="com.rsupport.remotecall.base.webapp.view.MappingJacksonJsonpView"
/>
<bean id="downloadView"
class="com.rsupport.remotecall.base.webapp.view.DownloadView" />

<!-- View Resolver for JSPs -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean id="urlFilenameViewController"
class="org.springframework.web.servlet.mvc.UrlFilenameViewController"
/>
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"
/>
<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
/**/*.html=urlFilenameViewController
</value>
</property>
<property name="order" value="3" />
</bean>

</beans>

[web.xml]
......
<!-- Context Configuration locations for Spring XML files -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:/spring/applicationContext*.xml
classpath*:/**/applicationContext.xml
/WEB-INF/applicationContext*.xml
/WEB-INF/security.xml
/WEB-INF/cxf-servlet.xml
/WEB-INF/cxf-client.xml
</param-value>
</context-param>
.......

<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
===============================================
처음에는 controller는 dispatcher-servlet.xml에 정의했었습니다. 그런데 controller에서
AOP가 동작하지 않아, applicaton-context.xml로 옮겼습니다.

위와같이 설정한 상태에서 @Controller나 @Service에서
@Autowired
private HttpServletRequest request2;

하게되면 잘 주입됩니다. 일반 Request는 문제없이 처리됩니다. 다만 multipart-form data만 문제가 있습니다.
Controller에서 넘겨받은 HttpServletRequest 는 ((MultipartHttpServletRequest)
request;) 캐스팅 하면 잘됩니다만 autowired로 주입받은 request객체는 타입 오류로 에러납니다.
그래서 multipartResolver를 써서
변환(multipartResolver.resolveMultipart(getHttpServletRequest());)시켜본건데
그것도 안됩니다. 에러는 안나지만 Multipart file을 못가져옵니다.

디버그를 걸어서 두객체(Controller에서 넘긴 request Vs autowired reqeust)를 비교해보니,
autowired request는 proxy 객체인데,
WebApplicationContextUtils$RequestObjectFactory에서 만들어주는데 type이
javax.servlet.ServletReuqest네요.

이미지 캡쳐해서 파일로 첨부했습니다.


위 문제로 현재는 Model 객체에 MultipartFile 변수를 추가하여 처리하는것으로 처리했습니다.

감사합니다.


2012년 1월 17일 오전 12:18, Kesarr <kes...@playxp.com>님의 말:

controller에서 넘긴 request.png
autowired_request.png

Sanghyuk Jung

unread,
Jan 16, 2012, 8:27:19 PM1/16/12
to ks...@googlegroups.com
 와, @Autowired로 받은 HttpServletRequest의 type이 궁금해서 한번 찍어보려고 했는데, 이렇게 상세하게 적어주셔서 많은 참고가 되네요 ^^


 예상대로 @Autowired하면 Proxy가 생성되기는하는데, 시도하신 코드는 MultipartHttpServletRequest와 상관없이 multipartResolver.resolveMultipart를 쓰는데도 file을 가지고 오지 못하는 것이였네요. 제가 처음에 코드를 대충봐서 어디선가 MultipartHttpServletRequest로 캐스팅하는 것 때문에 문제가 되는것인가하고.. 잘못 넘겨 짚었었습니다..

 
  Kesarr님 말씀대로 multipartResolver의 코드를 얼핏 봐서는 별 문제가 없어보이는데, 왜 @Autowired를 직접할때 안되는지는 좀 더 파봐야하겠네요;




2012년 1월 17일 오전 9:32, Jihwan Kim <jhki...@gmail.com>님의 말:
Reply all
Reply to author
Forward
0 new messages