Spring은 공통적으로 여러 작업을 처리함으로써 중복된 코드를 없애도록 많은 기능을 지원하고 있습니다. 그 중에서도 Filter
와 Interceptor
라는 개념의 차이에 대해 알아보겠습니다.
Filter는 J2EE 표준스펙 기능으로 Dispatcher Servlet
에 요청이 전달되기 전/후에 URL 패턴에 맞는 모든 요청에 대해 부가작업을 처리할 수 있는 기능을제공합니다. Dispatcher Servlet
은 스프링의 가장 앞단에 존재하는 프론트 컨트롤러이기 떄문에, 필터는 스프링 범위 밖에서 처리가 됩니다.
정리를 하자면 Filter
는 스프링 컨테이너가 아닌 Tomcat과 같은 웹 컨테이너에 의해 관리가 되며, 아래의 사진과 같습니다.
Filter를 추가하기 위해서는 javax.servlet의 Filter 인터페이스를 구현해야 하며 3개의 메소드를 가지고 있습니다.
아래는 Filter의 메소드입니다.
public interface Filter {
//init
public default void init(FilterConfig filterConfig) throws ServletException {}
//doFilter
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain throws IOException, ServletException;
//destroy
public default void destroy() {}
}
init()
init
메서드는 필터 객체를 초기화하고 service에 추가하기 위한 메소드입니다. 웹 컨테이너가 1번 init()
메소드를 호출하여 Filter
객체를 초기화하면 이후의 요청은 doFilter를 통해 처리됩니다.
doFilter()
doFilter
메소드는 URL Pattern에 맞는 HTTP 요청이 Dispatcher Servlet에 전달되기 전에 웹 컨테이너에 의해 실행되는 메소드입니다. doFilter
의 파라미터로는 FilterChain
이 있는데, FilterChain의 doFilter를 통해 다음 대상으로 요청을 전달하게 됩니다.
destroy()
destroy
메소드는 필터 객체를 service에서 제거하고 사용하는 자원을 반환받기 위한 메소드입니다. 웹 컨테이너에 의해 1번 호출되며 이후에는 doFilter
에 의해 처리되지 않습니다.
Interceptor는 J2EE 표준스펙인 Filter와는 달리 Spring이 제공하는 기술입니다. 이는 Dispatcher Servlet이 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 기능을 제공합니다. 웹 컨테이너에서 동작하는 Filter와는 달리, Interceptor는 Spring Context에서 동작을 합니다.
Dispatcher Servlet은 핸들러 매핑을 통해 적절한 컨트롤러를 찾도록 요청합니다. 그리고 그 결과로 HandleExecutionChain(실행체인)을 돌려줍니다. 그래서 실행을 하면 1개 이상의 Interceptor가 등록되어있다면 순차적으로 Interceptor를 거쳐 컨트롤러가 실행되도록 하고, 만약 Interceptor가 없다면 바로 컨트롤러를 실행합니다.
Interceptor는 스프링 컨테이너 내에서 동작합니다. 그리고 Filter를 거쳐 프론트 컨트롤러인 Dispatcher Servlet이 요청을 받은 후에 동작을 하는데, 아래의 그림과 같습니다.
Interceptor를 구현하기 위해선 org.springframework.web.servlet의 HandlerInterceptor 인터페이스를 구현(implements)해야 하며, 3개의 메소드를 가지고 있습니다.
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()
preHandle 메소드는 컨트롤러가 호출되기 전에 실행됩니다. 이 때문에 컨트롤러 이전에 처리해야 하는 전처리 작업이나 요청정보를 가공하거나 추가하는 경우에 사용할 수 있습니다.
postHandle
postHandle 메소드는 컨트롤러를 호출한 후에 실행됩니다. 이 때문에 컨트롤러가 이후에 처리해야 하는 작업이 있을 때 사용할 수 있습니다.
afterCompletion
afterCompletion 메소드는 모든 뷰에서 최종 결과를 생성하는 일을 포함해 모든 작업이 완료된 후에 실행됩니다. 요청 처리 중에 사용한 리소스를 반환할 때 사용하기 적합합니다.
Filter와 Interceptor는 각각이 관리되는 컨테이너와 Request/Response의 조작가능 여부가 다르고, 그에 따라 용도가 다릅니다.
Request/Response 객체 조작
Fitler는 Request와 Response를 조작할 수 있지만 Interceptor는 조작할 수 없습니다. 여기서 조작의 의미는 내부 상태의 변경을 의미하는 것이 아닌, 다른 객체로 바꿔친다는 말입니다. 아래는 Filter와 Interceptor를 사용한 코드 예시입니다.
public ExampleFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 다른 request와 response를 가능
chain.doFilter(request, response);
}
}
Filter가 다음 Filter를 호출하기 위해선 Filter Chaining(다음 필터 호출)을 해주어야 합니다. 그리고 Request/Response 객체를 넘겨주기 때문에 원하는 Request/Response 객체를 넣어줄 수 있습니다.
public class ExampleInterceptor implements HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// Request/Response 교체 X, boolean 값만 반환 O
return true;
}
}
Interceptor의 처리과정은 다음과 같습니다. Dispatcher Servlet이 여러 Interceptor 목록을 가지고 있고, for문으로 순차적으로 실행합니다. 그리고 true를 반환하면 다음 Interceptor가 실행되거나 컨트롤러로 요청이 전달되고, false가 반환되면 요청이 중단됩니다. 이 과정 때문에 Response/Request 객체를 넘겨줄 수 없기에 객체조작이 불가능합니다.
Filter
Filter에서는 스프링과 무관하게 전역으로 다루어야 하는 작업을 처리할 수 있습니다. 또한 웹 어플리케이션에 전반적으로 사용되는 기능을 구현하기에 적당합니다.
Filter의 용도와 예시는 아래와 같습니다.
대표적으로 Filter를 인증/인가로 사용하는 도구로 SpringSecurity가 있습니다. SpringSecurity의 특징 중 하나로 Spring MVC에 종속되지 않는 점인데, 이는 필터 기반으로 인증/인가를 처리하기 때문입니다.
Interceptor
Interceptor에서는 클라이언트의 요청과 관련되어 전역으로 처리해야 하는 작업들을 처리할 수 있습니다.
Interceptor의 용도와 예시는 아래와 같습니다.