Handler Intercepter
Spring interceptor is a class that either extends the
HandlerInterceptorAdapter
class or implements the HandlerInterceptor
interface.
- DispatcherServlet을 기준으로 Filter쪽은 WAS, DispatcherServlet부터는 Spring 의 영역
- Handler Interceptor는 HandlerAdapter가 handler의 실행을 트리거 하기 직전에 호출됨
- 해당 메커니즘은 전처리의 영역에서 많이 활용되는데 예를 들어 인증 확인, locale과 theme 변경 같은 일반적인 handler의 행위를 할 수 있음
- 주요 목적은 handler code의 반복을 피하기 위해서임
- Filter는 Web Application에 등록되기 때문에 SpringContext를 이용할수가 없음
- Intercepter는 Filter와 매우 유사한 형태로 존재하지만, 차이점은 Spring Context에 등록 됨→ Spring에 대한 기능들을 활용할 수 있음
- Controller mapping까지 이루어졌기 때문에 어떠한 메써드를 사용하는지에 대한 정보도 가지고 있음(annotation이 붙어있는지, 어떤 class인지)
- 주로 인증 단계를 처리할 수 있음, Logging도 가능함
- Service business logic과 분리
public interface HandlerInterceptor { default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } }
- 각 메서드별 설명
preHandle
: 요청에 대한 추가적인 처리를 진행 후, true를 반환하면 계속 진행. false면 멈춤postHandle
: request를 process한 후 view를 만들기 전에 호출되는 함수afterCompletion
: request process 가 끝난 후 커스텀 로직을 수행할 수 있음
서블릿 필터, MVC interceptor, AOP interceptor의 실행 순서
- Servlet Filter
- MVC interceptor
- AOP interceptor
Intercepter사용예시
//Auth.java import java.lang.annotation.*; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Auth{ } // AuthIntercepter.java // 여기서 Handler가 어떤 메써드에 매핑되는지, 타입, 포맷팅, 모델, 서비스 등 정보 가지고 있음 @Slf4j @Component public class AuthIntercepter implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String url = request.getRequestURI(); URI uri = UriComponentsBuilder.fromUriString(url).query(request.getQueryString()) .build().toUri(); if(checkAnnotation(handler, Auth.class)){ String query = uri.getQuery(); if(query.contains("named=steve")){ log.info("auth pass"); return true; } log.info("auth fail"); return false; } return true; } private boolean checkAnnotation(Object handler, Class clazz){ //resource javascript, html, -> pass! no Auth if(handler instanceof ResourceHttpRequestHandler) return true; // annotation check HandlerMethod handlerMethod = (HandlerMethod) handler; if(null != handlerMethod.getMethodAnnotation(clazz) || null != handlerMethod.getBeanType().getAnnotation(clazz)){ // Auth annotation 이 있을 때 true return true; } return false; } }
Intercepter 등록
@Configuration @RequiredArgsConstructor // final 로 선언된 객체들을 생성자에서 주입받을 수 있도록 해줌 // authIntercepter에 @Autowired를 붙여주는 것도 방법이 되지만 순환참조가 일어날 수 있음 public class MvcConfig implements WebMvcConfigurer { private final AuthIntercepter authIntercepter; @Override public void addInterceptors(InterceptorRegistry registry) { WebMvcConfigurer.super.addInterceptors(registry); registry.addInterceptor(authIntercepter); // addPathPatterns() // registry.addInterceptor(authIntercepter); // 이렇게 그다음에 interceptor추가되면 // 순차적으로 진행됨 } }
- @Configuration annotation, WebMvcConfigurer 구현
- addInterceptors 메써드에 생성한 intercepter 클래스 추가
- Interceptor가 동작하는 부분을 설정하기 위해서는 addPathPatterns()나 excludePathPatterns()를 통해 가능함, 또한 @Auth annotation을 붙임으로써도 가능함 ↔ Filter는 annotation 붙인것에 대해서 적용하도록 하는 것 불가능함. Spring context 이용 불가