필터 (Filter)는 말 그대로 요청과 응답을 거르는 역할을 합니다.
자세하게 말하자면 필터는 디스패처 서블릿(Dispatcher Servlet)에 요청이 전달되기 전/후에 url 패턴에 맞는 모든 요청에 대해 부가 작업을 처리할 수 있는 기능을 제공합니다.
디스패처 서블릿은 스프링의 가장 앞단에 존재하는 프론트 컨트롤러이므로, 필터는 스프링 범위 밖에서 처리가 되는 것입니다.
즉, 스프링 컨테이너가 아닌 톰캣과 같은 웹 컨테이너에 의해 관리가 되고, 디스패처 서블릿 전/후에 처리합니다.
스프링 컨테이너에 의해 관리되진 않지만 스프링 Bean으로 등록은 가능합니다.
필터를 사용하기 위해서는 javax.servlet의 Filter 인터페이스를 구현 해야 합니다.
public interface Filter {
public default void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
public default void destroy() {}
}
인터셉터 (Interceptor)는 필터와 달리 Spring이 제공하는 기술로써, 디스패처 서블이 컨트롤러를 호출하기 전과 후에 요청과 응답을 참조하거나 가공할 수 있는 기능을 제공합니다.
즉, 웹 컨테이너(서블릿 컨테이너)에서 동작하는 필터와 달리 인터셉터는 스프링 컨테이너에서 동작을 하는 것입니다.
인터셉터는 스프링 컨테이너 내에서 동작하므로 필터를 거쳐 프론트 컨트롤러인 디스패처 서블릿이 요청을 받은 이후에 동작하게 됩니다. 만약 인터셉터가 없다면 바로 컨트롤러가 실행됩니다.
인터셉터를 추가하기 위해서는 org.springframework.web.servlet의 HandlerInterceptor 인터페이스를 구현 해야 합니다.
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 {
}
}
세 메소드 모두 default로 구성되어 있기 때문에, 꼭 모든 메소드를 작성 안 하고 필요한 메소드만 오버라이딩해서 작성이 가능합니다.
앞서 살펴본 그림에서 보이듯이 필터와 인터셉터는 관리되는 영역이 다릅니다. 필터는 스프링 컨테이너 이전의 웹 컨테이너에서 관리되지만, 인터셉터는 스프링 컨테이너 영역에서 관리되는 영역이기 때문에 필터는 스프링의 지원을 받을 수 없습니다.
@RestControllerAdvice
public class GlobalExceptionHandler {
...
}
일반적으로 스프링을 사용한다면 위 코드와 같이 @RestControllerAdvice를 활용하여 예외처리 기능을 주로 사용합니다. 이를 통해 전역적으로 예외 처리 로직을 관리하고 일관된 에러 응답을 생성할 수 있습니다.
하지만 앞서 설명하였듯 필터는 스프링 앞의 서블릿 영역에서 관리되기 때문에 스프링의 지원을 받을 수 없다. 그래서 만약 필터에서 Exception이 발생 했다면, Exception이 처리되지 않고 서블릿까지 전달됩니다.
그에 반면, 인터셉터는 스프링 컨테이너에서 동작하므로 스프링의 예외 처리 기능을 활용할 수 있습니다.
필터는 Request와 Response를 조작할 수 있지만 인터셉터는 조작할 수 없습니다.
필터의 경우, 다음 필터를 호출하기 위해서는 필터 체이닝(다음 필터 호출)을 해주어야 한다. 그리고 이때 Request/Response 객체를 넘겨주므로 우리가 원하는 Request/Response 객체를 넣어줄 수 있습니다.
public MyFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 개발자가 다른 request와 response를 넣어줄 수 있음
chain.doFilter(new MockHttpServletRequest(), new MockHttpServletResponse());
}
}
하지만 인터셉터는 처리 과정이 필터와 다릅니다.
디스패처 서블릿이 여러 인터셉터 목록을 가지고 있고, 순차적으로 실행시킵니다.
그리고 true를 반환하면 다음 인터셉터가 실행되거나 컨트롤러로 요청이 전달되며, false가 반환되면 요청이 중단됩니다.
그러므로 다른 request, response 객체를 넘겨줄 수 없습니다.
public class MyInterceptor implements HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// Request/Response를 교체할 수 없고 boolean 값만 반환할 수 있다.
return true;
}
}
필터(Filter)의 사용 사례
필터는 기본적으로 스프링과 무관하게 전역적으로 처리해야 하는 작업들을 처리할 수 있다.
필터는 인터셉터보다 앞단에서 동작하기 때문에 보안 검사를 하여 올바른 요청이 아닐 경우 차단할 수 있습니다. 그러면 스프링 컨테이너까지 요청이 전달되지 못하고 차단되므로 안전성을 더욱 높일 수 있습니다. Filter를 사용하는 예시로 Spring Security가 있습니다.
또한, 필터는 이미지나 데이터의 압축, 문자열 인코딩과 같이 웹 어플리케이션에 전반적으로 사용되는 기능을 구현하기에 적당합니다.
인터셉터(Interceptor)의 사용 사례
인터셉터에서는 클라이언트의 요청과 관련되어 전역적으로 처리해야 하는 작업들을 처리할 수 있습니다.
예를 들어 특정 그룹의 사용자는 어떤 기능을 사용하지 못하는 경우가 있는데, 이러한 작업들은 컨트롤러로 넘어가기 전에 검사해야 하므로 인터셉터가 처리하기에 적합합니다.
또한, 해당 객체가 내부적으로 갖는 값은 조작할 수 있으므로 컨트롤러로 넘겨주기 위한 정보를 가공하기에 용이합니다.
필터(Filter)는 특정 요청과 컨트롤러에 관계없이 전역적으로 처리해야 하는 작업이나
웹 어플리케이션에 전반적으로 사용되는 기능을 구현할 때 적용하고, 인터셉터(Interceptor)는 클라이언트의 요청과 관련된 작업에 대해 추가적인 요구사항을 만족해야 할 때 적용합니다.