2013년 2월 16일 토요일

Spring MVC Controller 선후처리기 만들기

ASP.NET 웹 어플리케이션은 어플리케이션 Lifecycle, Page의 Lifecycle 에 상세한 event 를 정의하고 있어, event handler를 정의하면, 여러가지 선처리나 후처리를 수행할 수 있습니다.

Spring MVC 에서는 어떻게 하나 봤더니 Controller 에 Interceptor 를 등록하면 되더군요.

단계를 요약하자면...

  1. org.springframework.web.servlet.HandlerInterceptor 또는 org.springframework.web.servlet.handler.HandlerInterceptorAdapter 를 상속받아 preHandler, postHandler, afterComletion 등에 원하는 작업을 구현합니다.
  2. servlet.xml 에 위에서 작성한 Interceptor 를 등록합니다.

아주 쉽죠?

그럼 실제 예제와 함께 보시죠. 예제는 Spring Framework 3.2.1.RELEASE 와 Hibernate 4.1.9 Final 로 제작했습니다.

UnitOfWorkInterceptor 는 사용자 요청이 있으면 Start 하고, 요청 작업이 완료되면 Close 하도록 합니다. 이는 Hibernate 를 이용하여 Unit Of Work 패턴을 구현하여, 하나의 요청 중에 모든 작업을 하나의 Transaction으로 묶을 수 있고, 웹 개발자에게는 Unit Of Work 자체를 사용하기만 하면 되고, 실제 Lifecycle 은 Spring MVC 에서 관리하도록 하기 위해서입니다.


package kr.nsoft.data.hibernate.springmvc;
import kr.nsoft.data.hibernate.unitofwork.IUnitOfWork;
import kr.nsoft.data.hibernate.unitofwork.UnitOfWorks;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Spring MVC 에서 servlet 시작과 완료 시에 UnitOfWork를 시작하고, 완료하도록 합니다.
* User: sunghyouk.bae@gmail.com
* Date: 13. 2. 15.
*/
@Slf4j
public class UnitOfWorkInterceptor implements HandlerInterceptor {
/**
* Controller 가 수행되기 전에 호출됩니다.
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
UnitOfWorks.start();
return true;
}
/**
* Controller의 메소드가 수행이 완료되고, View 를 호출하기 전에 호출됩니다.
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) throws Exception {
// Nothing to do
}
/**
* View 작업까지 완료된 후 Client에 응답하기 바로 전에 호출됩니다.
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) throws Exception {
IUnitOfWork unitOfWork = UnitOfWorks.getCurrent();
if (unitOfWork != null) {
UnitOfWorks.closeUnitOfWork(unitOfWork);
if(log.isDebugEnabled())
log.debug("Client 요청 처리를 완료하였으므로, UnitOfWork를 종료합니다.");
}
}
}
보시다 시피, preHandle 에서는 Controller의 메소드가 호출되기 전에 (즉 Unit Of Work 를 사용하기 전에) UnitOfWorks.start() 를 호출하여, hibernate의 session을 열어, Hibernate session 작업을 할 수 있도록 미리 준비해줍니다.

postHandle 에서는 아무 작업도 하지 않는데, 실제 View 에서 ViewModel 를 binding 할 시에, lazy initialized 되는 associated entity를 후에 가져 올 수 있기 때문입니다. 여기서 unit of work 를 끝내버리면, session을 닫아버려, lazy initialize 작업에서 예외가 발생하게 됩니다.

afterCompletetion 에서는  View 작업까지 완료되고, Client 에 Reponse 를 보내기 바로 직전에 호출됩니다. 여기서 unit of work 를 close 합니다.

다음으로는 웹 어플리케이션에 Spring MVC Context 를 설정 시 interceptor를 등록하기만 하면 됩니다.


<context:annotation-config/>
<context:component-scan base-package="kr.nsoft.contact"/>
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/view/"/>
<property name="suffix" value=".jsp"/>
<property name="contentType" value="text/html;charset=UTF-8"/>
</bean>
<!-- UnitOfWorks.start(), close() 를 servlet의 시작과 완료에 처리하도록 한다 -->
<mvc:interceptors>
<bean class="kr.nsoft.data.hibernate.springmvc.UnitOfWorkInterceptor"/>
</mvc:interceptors>
위와 같이 bean 등록 시에 interceptor를 등록해 주시면 됩니다^^
Spring AOP 를 사용해도 되지만, 요 방법이 가장 간단하고, 쉽게 이해할 수 있을 것 같습니다.

다른 블로거의 글에는 logging 으로 예제를 많이 많들었더군요...
asp.net 으로는 제가 간단하게 성능 측정하는 코드를 예제로 만들어보긴 했지만, 쓸모는 없지요^^
위의 unit of work관련 코드는 asp.net 으로 구현한 것은 상용 제품의 코드로 쓰이고, 아직도 많은 회사에서 구동되고 있습니다...
이를 spring mvc, hibernate 로 포팅한 작업입니다.