[Spring] 스프링 MVC(14) - HandlerInterceptor
HandlerInterceptor를 이용한 인터셉터 구현
요청 경로마다 접근 제어를 다르게 해야 한다거나 사용자가 특정 URL을 요청할 때마다 접근 내역을 기록하고 싶다면 HandlerInterceptor를 사용하면 된다.
HandlerInterceptor 인터페이스를 사용하면 다음의 세 가지 공통 기능을 넣을 수 있다.
- preHandle - 컨트롤러(핸들러) 실행 전
- postHandle - 컨트롤러(핸들러) 실행 후, 뷰 실행 전
- afterCompletion - 뷰 실행 후
preHandle() 메서드는 접근 권한이 없는 경우 컨트롤러를 실행하지 않거나, 컨트롤러를 실행하기 전에 컨트롤러에서 필요로 하는 정보를 생성하는 등의 작업을 수행한다. preHandle() 메서드가 false를 리턴하면 컨트롤러를 실행하지 않는다.
postHandle() 메서드는 컨트롤러가 정상적으로 실행된 후에 추가 기능을 구현할 때 사용된다. 만약 컨트롤러가 익셉션을 발생하면 postHandle() 메서드는 실행되지 않는다.
afterCompletion() 메서드는 클라이언트에 뷰를 전송한 뒤에 실행된다. 컨트롤러 실행 과정에서 익셉션이 발생하면 이 메서드의 네 번째 파라미터로 전달되는데 익셉션이 발생하지 않으면 null 전달된다.
HandlerInterceptor 설정하기
우선 간단하게 웹 요청 처리 시간을 측정하는 HandlerInterceptor를 만들어 보자.
public class MeasuringInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MI: preHandle()");
request.setAttribute("mi.beginTime", System.currentTimeMillis());
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MI: afterCompletion()");
Long beginTime = (Long) request.getAttribute("mi.beginTime");
long endTime = System.currentTimeMillis();
System.out.println(request.getRequestURI() + " 실행 시간: " + (endTime - beginTime));
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MI: postHandle()");
}
}
HandlerInterceptor를 구현했다면, 다음과 같이 설정에 추가해야 한다.
<mvc:interceptors>
<bean id="measuringInterceptor" class="com.mypackage.MeasuringInterceptor"/>
</mvc:interceptors>
<mvc:interceptors> 태그는 HandlerInterceptor 설정과 경로 설정을 함께 설정할 때 사용된다. <mvc:interceptors> 태그 내부에 정의한 빈 객체를 핸들러 인터셉터로 사용하고, DispatcherServlet이 처리하는 요청에 대해 핸들러 인터셉터를 적용하게 된다. 만약 특정 경로에 대해서만 핸들러 인터셉터를 적용하고 싶다면 다음과 같이 <mvc:interceptor> 태그를 사용한다.
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/event/**"/>
<mvc:mapping path="/folders/**"/>
<bean class="com.mypackage.MeasuringInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
자바 설정을 사용한다면 다음과 같이 WebMvcConfigure의 addInterceptors() 메서드와 addPathPatterns() 메서드를 사용하면 된다. addPathPatterns() 메서드의 파라미터는 가변 인자이므로 한 개 이상 목록을 지정해주면 된다.
@Configuration
@EnableWebMvc
public class SampleConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(measuringInterceptor())
.addPathPatterns("/event/**", "/folders/**");
}
// 이하 생략
...
}
HandlerInterceptor의 실행 순서
세 개의 핸들러 인터셉터를 다음과 같은 순서로 적용한다고 하자.
- /acl/로 시작하는 경로에 AuthInterceptor 적용
- 전체 경로에 MeasuringInterceptor 적용
- /acl/, /header/로 시작하는 경로에 CommonModelInterceptor 적용
위와 같은 순서로 적용하고 싶다면, 다음과 같이 설정하면 된다.
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/acl/**"/>
<bean class="com.mypackage.AuthInterceptor"/>
</mvc:interceptor>
<bean class="com.mypackage.MeasuringInterceptor"/>
<mvc:interceptor>
<mvc:mapping path="/acl/**"/>
<mvc:mapping path="/header/**"/>
<bean class="com.mypackage.CommonModelInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
위 설정을 사용하면 /acl/list 요청에 대해 다음과 같은 순서로 인터셉터와 컨트롤러가 실행된다.
- 핸들러 인터셉터 preHandle() 실행
- AuthInterceptor.preHandle()
- MeasuringInterceptor.preHandle()
- CommonModelInterceptor.preHandle()
- 컨트롤러 실행
- 핸들러 인터셉터 postHandle() 실행
- CommonModelInterceptor.postHandle()
- MeasuringInterceptor.postHandle()
- AuthInterceptor.postHandle()
- 핸들러 인터셉터 afterCompletion() 실행
- CommonModelInterceptor.afterCompletion()
- MeasuringInterceptor.afterCompletion()
- AuthInterceptor.afterCompletion()
실행 순서를 보면 preHandle() 메서드는 지정한 순서대로 실행되고, postHandle() 메서드와 afterCompletion() 메서드는 역순인 것을 알 수 있다.
만약 AuthInterceptor.preHandle() 메서드가 false를 리턴하면 컨트롤러 실행뿐만 아니라 1.2, 1.3 및 3, 4 과정이 모두 실행되지 않는다.
만약 1.2의 preHandle() 메서드에서 false를 리턴하면 어떻게 될까? 이 경우 먼저 실행된 AuthInterceptor의 afterCompletion() 메서드는 실행된다. 마찬가지로 1.3의 preHandle() 메서드가 false를 리턴하면 4.2와 4.3의 afterCompletion() 메서드는 실행된다. preHandle() 메서드에서 false를 리턴하면 컨트롤러는 실행되지 않으므로, 컨트롤러가 정상 실행된 후에 적용되는 postHandle() 메서드는 모두 실행되지 않는다.
Reference
- 웹 개발자를 위한 Spring 4.0 프로그래밍 (최범균 저)