[Spring] 필터와 인터셉터
필터 (Filter)
필터(Filter)는 스프링이 아닌 서블릿 스펙(Jakarta EE / Java EE)에서 제공하는 인터페이스이다. 이러한 서블릿 필터는 스프링이 아닌 서블릿 컨테이너 레벨에서 동작하며 클라이언트의 요청을 가장 먼저 처리한다.
참고로 스프링 부트 2.x 버전까지는 javax.servlet에서 제공했지만 3.x 버전부터는 jarkarta.servlet로 패키지 이름이 변경되었다.
스프링에서 가장 먼저 클라이언트의 요청을 처리하는 것은 DispatcherServlet이다. 필터는 위 그림에서 볼 수 있듯 Spring Context 밖에서 DispatcherServlet 보다 먼저 클라이언트의 요청을 처리하고, DispatcherServlet의 후처리를 담당한다.
Filter 인터페이스
package jakarta.servlet;
import java.io.IOException;
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {
}
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
default void destroy() {
}
}
jakarta.servlet 내부의 Filter 인터페이스를 확인해보면 위와 같은 구조를 가진다.
init 메서드는 필터가 처음으로 초기화될 때 실행된다. 서블릿 컨테이너(서블릿을 처리하는 환경을 제공)에서 필터 인스턴스를 생성한 후, 필터가 첫 번째 요청을 처리하기 전에 한 번만 호출된다.
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 필터링 작업 수행 (요청 전 처리)
// 요청을 다음 필터나 서블릿으로 전달
chain.doFilter(request, response);
// 필터링 작업 수행 (응답 후 처리)
}
}
doFilter 메서드는 필터 생성 이후 HTTP 요청이 DispatcherServlet으로 전달되기 전에 전처리를 수행한다. 파라미터로 받는 FilterChain은 여러 필터들이 순차적으로 실행되는 구조를 갖는 구조체이다. 각 필터는 doFilter 메서드를 호출하면서 요청을 처리하고 FilterChain의 doFilter 메서드를 통해 다음 필터나 서블릿으로 전달한다.
destory 메서드는 필터가 종료될 때 호출되는 메서드로 필터의 자원을 해제하고 종료 전에 수행해야 할 로직을 위한 메서드다. 예를 들어 DB연결 종료나 파일 리소스 해제, 세션이나 캐시 정리 작업 등을 수행할 수 있다.
커스텀 필터 적용하기
@Slf4j
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
log.info("필터 로직 수행");
filterChain.doFilter(servletRequest, servletResponse);
log.info("필터 후처리");
}
}
// MyFilter 이후에 적용할 필터
@Slf4j
public class MyFilterAfter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
log.info("필터 로직 수행");
filterChain.doFilter(servletRequest, servletResponse);
log.info("필터 후처리");
}
}
먼저 jakarta.servlet 패키지의 Filter 인터페이스를 구현하여 커스텀 필터 2개를 만들었다. MyFilterAfter는 실제로 필터 체인 인에 의한 필터의 순서를 보장하는지 확인하기 위해 구현했다.
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean<MyFilter> myFilterRegistrationBean() {
FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new MyFilter()); // 필터 객체 등록
registrationBean.addUrlPatterns("/test-filter"); // 필터가 적용될 URL 패턴
registrationBean.setOrder(1); // 우선순위 설정 (낮을수록 먼저 실행)
return registrationBean;
}
@Bean
public FilterRegistrationBean<MyFilterAfter> myFilterAfterRegistrationBean() {
FilterRegistrationBean<MyFilterAfter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new MyFilterAfter()); // 필터 객체 등록
registrationBean.addUrlPatterns("/test-filter"); // 필터가 적용될 URL 패턴
registrationBean.setOrder(2); // 우선순위 설정 (MyFilter 이후에 실행되도록)
return registrationBean;
}
}
이제 구현한 커스텀 필터를 순서에 맞게 등록해주어야 한다. 필터의 등록은 FilterRegistrationBean을 사용하여 등록할 수 있다. 등록할 필터에 대한 FilterRegistrationBean 객체를 생성하고 해당 객체에 적용할 필터 객체와 URL 패턴, 우선순위를 설정하여 필터를 등록한다.
결과 확인
위에서 등록한 URL 패턴인 /test-filter 를 통해 HTTP GET 요청을 수행해보면 위와 같은 결과를 얻을 수 있다. 가장 먼저 URL 패턴을 만족하는 필터 중 가장 낮은 우선순위를 가지는 MyFilter가 수행되고 doFilter 메서드 내부에서 수행한 filterChain.doFilter 메서드에 의해 MyFilterAfter의 doFilter가 순차적으로 수행된다. 필터들의 전처리 이후 DispatcherServlet에 의해 핸들러 메서드가 실행되고 반환된 결과는 다시 필터체인의 역순으로 후처리를 수행한다.
인터셉터
인터셉터(Interceptor)는 서블릿 스펙(Jakarta EE / Java EE)에서 제공하는 필터(Filter)와 달리 Spring이 제공하는 기술이다. 필터와 비슷하게 요청의 전/후 처리를 담당하지만, DispatcherServlet에 의해 관리되며 컨트롤러가 요청을 처리하기 전에 동작한다.
springframework에서 제공하는 HandlerInterceptor
package org.springframework.web.servlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
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 {
}
}
스프링 프레임워크에서 제공하는 HandlerInterceptor 인터페이스의 구조는 위와 같다. preHandle 메서드는 요청이 컨트롤러로 전달되기 전에 수행되고, postHandle은 컨트롤러가 실행된 후와 뷰 렌더링 이전에 실행된다. 마지막으로 afterCompletion 메서드는 뷰 렌더링 완료 후에 요청 처리가 모두 완료된 후에 실행되는 메서드다.
커스텀 인터셉터 적용하기
@Slf4j
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("컨트롤러 실행 전");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("컨트롤러 실행 후, 뷰 랜더링 전");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("뷰 랜더링 후, 요청 처리 완료 후");
}
}
커스텀 필터 구현과 마찬가지로 먼저 인터셉터로 적용할 HandlerInterceptor 구현체를 생성해준다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
// ...
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()) // 인터셉터 등록
.addPathPatterns("/test-interceptor"); // URL 패턴 등록
}
}
커스텀한 인터셉터의 등록은 WebMvcConfigurer 구현체의 addInterceptors 메서드를 통해 가능하다.
결과 확인
설정한 URL 패턴인 /test-interceptor 을 통해 요청해보면 위와 같이 로그를 통해 실행 순서를 확인해 볼 수 있다.
정리
필터 (Filter) | 인터셉터 (Interceptor) | |
스펙 | Jakarta/javax EE / Java EE | Springframework에 의해 제공 |
적용 위치 | DispatcherServlet 이전 | DispatcherServlet 이후, 컨트롤러 이전 |
역할 | 클라이언트 요청에 대한 전/후 처리 |