스프링에서 공통 관심사를 처리하는데 AOP를 사용하는데요. 웹과 관련된 공통 관심사를 처리하는 방법으로 Filter와 Interceptor를 사용하게 됩니다. Filter와 Interceptor는 HttpServletRequest를 제공하기 때문에 웹 관련 공통 관심사를 처리하기에 유용합니다.
이번 포스팅에서는 Servlet Filter와 Spring Interceptor의 차이점에 대해서 정리하도록 해볼게요.
Filter
웹 요청이 들어오면 먼저 필터가 먼저 호출되고 서블릿이 호출됩니다. 필터는 체인으로 구성되어 여러 개의 필터를 추가하고 호출되도록 구성할 수 있습니다.
filter는 servlet에서 제공하고, 필터 인터페이스를 통해 구현하고 빈으로 등록하면 서블릿 컨테이너가 필터를 싱글톤으로 생성하고 관리하게 됩니다.
- init() : 서블릿 컨테이너가 생성될때 호출되며, 필터를 초기화하는 메서드입니다.
- doFilter() : 요청이 들어올 때마다 해당 메서드가 호출됩니다. 필터의 로직을 구현하는 메서드입니다.
- destroy() : 서블릿 컨테이너가 종료될 때 호출됩니다.
public class FirstLogFilter implements Filter {
private static final Logger LOGGER = LoggerFactory.getLogger(FirstLogFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
LOGGER.info("FirstLogFilter.init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws
IOException,
ServletException {
LOGGER.info("FirstLogFilter.doFilter");
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
String uuid = UUID.randomUUID().toString();
try{
LOGGER.info("[[{}][{}]]", uuid, requestURI);
chain.doFilter(request,response);
}catch(Exception e){
throw e;
}finally {
LOGGER.info("[[{}][{}]]", uuid, requestURI);
}
}
@Override
public void destroy() {
LOGGER.info("FirstLogFilter.destroy");
}
}
이런 식으로 servlet의 필터를 상속받아 해당 메서드를 구현해주고, 요청이 들어오면 doFilter를 실행하게 됩니다. 여기서 chain.doFilter 메서드의 호출을 통해 다음 필터가 있으면 필터를 호출하고, 없으면 서블릿을 호출하게 됩니다.
필터를 만들었으면 등록해야 하는데요. 빈 등록은 FilterRegistrationBean을 사용하는 걸 권장합니다. @ServletComponentScan, @WebFilter를 사용하는 방법도 있지만 순서지정이 불가능하기 때문에 FilterRegistrationBean을 사용합니다.
Interceptor
Interceptor는 SpringMVC가 제공하는 기능입니다. 웹과 관련된 공통 처리를 위한 기술은 동일하지만 Filter보다 더 많은 기능을 제공합니다. filter보다 조금 더 정교하고 다양한 기능을 바탕으로 공통부분들을 처리할 수 있습니다.
Interceptor의 호출 시점은 서블릿을 지나 컨트롤러 사이에서 인터셉터가 동작합니다. 필터와 동일하게 체인 형식으로 Interceptor를 여러 개 적용할 수 있습니다.
- preHandle() : 컨트롤러가 호출되기 전, 정확히는 HandlerAdapter를 호출하기 전에 실행됩니다. return 값으로 Boolean을 반환하는데 true인 경우 다음으로 진행하고, false이면 인터셉터, 핸들러 어댑터까지 호출되지 않습니다.
- postHandle() : 컨트롤러가 호출된 후에 호출됩니다. 정확히는 HandlerAdapter가 호출된 이후 호출됩니다. Controller에서 예외가 발생하면 해당 메서드는 호출되지 않습니다.
- afterCompletion() : 뷰가 렌더링 된 이후 호출됩니다. 가장 마지막에 호출됩니다. 예외가 발생해도 항상 호출하게 됩니다.
interceptor의 인터페이스만 봐도 호출 시점이 좀 더 세분화 되어있음을 느낄 수 있고, ModelAndView, Exception 을 파라미터로 넘겨주는걸 보면 좀더 다양한 처리를 할 수 있도록 제공하는 걸 볼 수 있습니다.
public class FirstLogInterceptor implements HandlerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(FirstLogInterceptor.class);
private static final String LOG_ID = "logId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
Exception {
String requestURI = request.getRequestURI();
String uuid = UUID.randomUUID().toString();
request.setAttribute("logId", uuid);
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod)handler;
}
LOGGER.info("preHandle : [[{}][{}][{}]]", uuid, requestURI, handler);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
LOGGER.info("postHandle : {}", modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
Exception ex) throws Exception {
String requestURI = request.getRequestURI();
String uuid = (String)request.getAttribute(LOG_ID);
LOGGER.info("afterCompletion : [[{}][{}][{}]]", uuid, requestURI, handler);
if (ex != null) {
LOGGER.error("afterCompletion error", ex);
}
}
}
preHandle의 handler의 경우 @RequestMapping을 사용해 매핑하면 Spring의 핸들러는 HandlerMethod타입으로 각 매핑에 대해 맞는 핸들러 타입을 검사하고 사용하면 됩니다. interceptor는 addInterceptors 메서드를 오버라이드 해서 interceptor를 등록하고 사용합니다.
Wrap-up
Filter와 Interceptor는 비슷한 개념이긴 하지만 차이점이 있습니다. 우선 filter는 servlet에서 제공하고 interceptor는 spring에서 제공하는 차이점이 있습니다. 또한 Filter는 doFilter만 제공하는 반면 Interceptor는 서블릿에서 컨트롤러 호출 사이의 시점에 따라 preHandle, postHandle, afterCompletion메서드를 제공해 세밀하고 많은 기능을 제공합니다. 특별한 이유가 없다면 Spring Interceptor를 사용하는 게 개발자 입장에서 더 편할 것 같다는 생각이 들었습니다.
'Spring' 카테고리의 다른 글
Annotation based Interceptor (0) | 2021.09.10 |
---|---|
Spring Transaction (0) | 2021.08.09 |
AOP를 직접 구현해보자 - Proxy적용 (0) | 2021.08.02 |
댓글