스프링이 제공하는 인터셉터도 서블릿 필터와 같은 기능을 한다. 웹과 관련되 공통 관심 사항을 효과적으로 해결할 수 있다.
다른점은 필터는 서블릿이 제공하는 기술이고, 인터셉터는 스프링 MVC가 제공하는 기술이다. 적용되는 순서와 범위 그리고 사용방법에 있어 차이가 있다.
HTTP 요청 WAS 필터 서블릿 인터셉터 컨트롤러
서블릿은 디스패처 서블릿을 가르킨다.
스프링 MVC의 시작점은 디스패처 서블릿이고 이 디스패처 서블릿을 지나 인터셉터가 적용된다.
따라서 디스패처 서블릿을 지났지만 스프링 인터셉터에서 적절하지 않은 요청이라 판단하면 컨트롤러까지 요청이 진행되지 않는다.
스프링 인터셉터 역시 체인 구조를 가진다.
HTTP 요청 WAS 필터 서블릿 인터셉터1 인터셉터2 컨트롤러
지금까지 살펴보면 필터와 그냥 순서차이만 나는거 아닌가? 이제 차이점을 천천히 알아보자.
스프링의 인터셉터 인터페이스는 HandlerInterceptor
이다. 이 인터페이스를 구현하면 된다.
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {}
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 {}
}
서블릿 필터는 doFilter()
만 제공했지만 스프링 인터셉터는 3가지 함수를 제공한다. 이 3가지 함수를 제공하는 이유는 순서에 있다.
컨트롤러 호출 전 preHandle
이, 호출 후에는 postHandle
이, 요청이 완료되고 나서(View까지 보냄)는 afterCompletion
이 기능을 한다.
파라미터를 보면 ServletRequest
가 아니고 HttpServletRequest
이다. 그리고 Object handler
는 어떤 컨트롤러가 호출되는지에 대한 정보가 있다. 그리고 어떤 ModelAndView
가 반환되는지에 대해도 알 수 있다. afterCompletion
은 예외도 파라미터로 받는다.
만약 컨트롤러에서 예외가 발생한 경우 preHandle
은 이미 컨트롤러 전에 호출됐으므로 상관이 없다. 그런데 postHandle
은 예외가 발생한 경우 호출되지 않는다. 마지막으로 afterCompletion
은 예외와 무관하게 항상 호출되는 함수이다.
preHandle
에서 받아오는 파라미터 중 Object Handler
는 핸들러 정보를 담고 있는데 어떤 핸들러 매핑을 사용하는가에 따라 달라진다.
@Controller
나 @RequestMapping
을 활용한 핸들러 매핑을 사용하면 핸들러 정보로 HandlerMethod
가 넘어온다.if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
}
/resources/static
과 같은 정적 리소스가 호출되는 경우인터셉터를 등록하기 위해서는 WebMvcConfigurer
를 구현해야한다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new 내 인터셉터())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**","/*.ico","/error");
}
WebMvcConfigurer
가 제공하는 addInterceptors
함수를 통해 내가 만든 인터셉터 정보를 추가하면 된다.
addInterceptor()
: 인터셉터를 등록order(1)
: 인터셉터 호출 순서 등록. 1부터 시작.addPathPatterns("/**")
: 인터셉터를 거칠 URL패턴을 등록한다.excludePathPatterns()
: 위에 addPathPatterns에서 /**
가 모든 경로를 의미한다. 필터에서 사용한 /*
와 표현 방식이 다르다. 따라서 등록한 경로 중에서 인터셉터를 거치지 않을 URL패턴을 등록하는 함수이다.클라이언트의 요청이 컨트롤러에 가기 전에 로그인을 한 사용자인지 확인하고 거르는 작업은 preHandle
에서 하는 것이 적절해 보인다. session을 이용한 로그인 처리를 사용했다는 가정하에 코드를 작성해보면 다음과 같다.
public class loginCheckInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object Handler) throws Exception {
String requestURI = request.getRequestURI();
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("내가정한세션이름") == null)
{
response.sendRedirect("/login?redirectURL=" + requestURI);
return false;
}
return true;
}
차례대로 요청이 들어온 URL에 대한 정보를 알아내고 이어서 세션에 대한 정보를 알기 위해 세션을 받았다. 로직대로 세션이 없거나 세션에 대한 정보가 없으니 로그인을 안한 사용자로 판단되어 /login
페이지로 리다이렉트한다. 이 때 쿼리 파라미터로 클라이언트가 원했던 페이지 값을 보내줌으로서 로그인 페이지에서 로그인 성공시 쿼리 파라미터의 주소로 이동할 수 있게 도와주기 위한 로직이다.
중요한 것은 return값인데 false
를 받게되면 이 요청은 더이상 컨트롤러까지 진행하지 못하고 response하게 된다. 다른 정보 없이 response에 리다이렉트 정보를 넘겨주었다. 반대로 true
값이라면 필터에서처럼 다음 인터셉터나 컨트롤러로 요청이 넘어간다.
서블릿 필터와 스프링의 인터셉터 모두 웹과 관련한 공통 관심 사항을 처리하기 위한 기술이다. 요청의 순서 흐름에 있어 어디서 처리하는지에 따라 다르긴 하지만 스프링의 인터셉터가 좀 더 세밀하게 처리해주는 느낌이다. 그리고 URL패턴을 등록하는데 더 개발자 친화적인 느낌을 받았다. 앞으로 프로젝트를 진행한다면 스프링 인터셉터를 사용할 일이 좀 더 많아 보인다.