![[Spring] 서블릿 필터와 스프링 인터셉터](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbclxFs%2FbtsIkCa4Y1Y%2Fy9iHkkX3WfKStzCAMbPnq0%2Fimg.jpg)
웹 컨테이너란?
웹 컨테이너는 서블릿과 JSP 등 웹 애플리케이션을 실행시키기 위한 환경을 제공한다. HTTP 요청을 처리하기 위한 기능을 제공하고, 대표적인 웹 컨테이너로 Apache Tomcat, Jetty, GlassFish 등이 있다.
역할
- 서블릿 생명 주기
- 서블릿 생성, 초기화, 요청 처리, 소멸
- 클라이언트 요청 및 응답 처리
- HTTP 요청을 서블릿으로 전달하고, 서블릿으로부터 응답을 전달받아 클라이언트로 전달한다.
1. Servlet Filter
클라이언트의 요청이 Dispatcher Servlet으로 전달되기 전, url 패턴에 맞는 요청을 먼저 받아 처리한다.
사진에서 알 수 있듯이 Servlet Filter는 Spring 영역에 존재하는 것이 아니다.
서블릿 필터를 사용하기 위해서는 아래의 Filter 인터페이스를 구현해야 한다.
package jakarta.servlet;
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {
}
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
default void destroy() {
}
}
- init()
- 애플리케이션이 시작되면 최초 1회에 한하여 필터 객체를 초기화한다.
- 서블릿 필터로 들어오는 요청은 doFilter()에서 처리된다.
- doFilter()
- URI 패턴과 일치하는 HTTP 요청을 받아 처리한다.
- HTTP 요청이 디스패처 서블릿으로 전달되기 전에, 먼저 받아서 처리한다.
- doFilter()의 매개변수로 FilterChain이 있는데 요청을 여러 필터를 통해 연쇄적으로 처리할 수 있다.
- 마지막 필터를 호출한 후, 클라이언트의 요청을 디스패처 서블릿으로 전달한다.
- destroy()
- 필터 객체를 제거한다.
1-1. @WebServlet을 사용하여 필터 등록하기
@WebServlet 애너테이션을 사용하여 서블릿 필터를 등록할 수 있다. 이때, urlPatterns를 사용하여 HTTP 요청의 URI와 일치하는 경우에만 서블릿 필터를 호출할 수 있도록 할 수 있다.
주의할 점은, @WebServlet 애너테이션을 사용한다고 해서 서블릿이 등록되지 않는다. @SpringBootApplication을 사용하는 클래스에서 @ServletComponentScan 애너테이션을 추가해야 서블릿 필터가 정상적으로 호출된다.
Filter1
@Slf4j
@WebFilter(urlPatterns = "/hello")
public class ExampleFilter1 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("ExampleFilter1 init");
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
log.info("ExampleFilter1 Request url: {}", httpRequest.getRequestURI());
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
log.info("ExampleFilter1 destroy");
Filter.super.destroy();
}
}
Filter2
@Slf4j
@WebFilter(urlPatterns = "/hello")
public class ExampleFilter2 implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("ExampleFilter2 init");
Filter.super.init(filterConfig);
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
log.info("ExampleFilter2 Request url: {}", httpRequest.getRequestURL());
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
log.info("ExampleFilter2 destroy");
Filter.super.destroy();
}
}
Application
@SpringBootApplication
@ServletComponentScan
public class CsStudyApplication {
public static void main(String[] args) {
SpringApplication.run(CsStudyApplication.class, args);
}
}
HTTP 요청 결과
1. 애플리케이션을 실행하면 필터 객체가 생성 및 초기화된다.
2. HTTP 요청이 들어오면 doFilter()가 호출된다.
이때, 웹 컨테이너에 등록된 모든 필터가 호출된다. 왜냐하면 Filter 인터페이스를 구현하면서 doFilter()에 필터 체인을 통해 연쇄적으로 호출되게 했기 때문이다.
3. 애플리케이션이 종료되면 필터 객체가 삭제된다.
1-2. @Bean을 사용하여 필터 등록하기
@WebServlet 애너테이션을 제거하고, @Bean을 사용하여 등록한다.
Filter1
@Slf4j
public class ExampleFilter1 implements Filter {
내부 코드는 이전과 동일하다.
}
Filter2
@Slf4j
public class ExampleFilter2 implements Filter {
내부 코드는 이전과 동일하다.
}
Config
@Configuration
public class FilterConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean<ExampleFilter1> filter1() {
FilterRegistrationBean<ExampleFilter1> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new ExampleFilter1());
filterRegistrationBean.addUrlPatterns("/hello");
return filterRegistrationBean;
}
@Bean
public FilterRegistrationBean<ExampleFilter2> filter2() {
FilterRegistrationBean<ExampleFilter2> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new ExampleFilter2());
filterRegistrationBean.addUrlPatterns("/hi");
return filterRegistrationBean;
}
}
HTTP 요청 결과
“/hello”를 요청하면 “/hi” URI를 처리하는 filter2가 호출되지 않는다.
2. Spring Interceptor
Controller로 HTTP 요청을 보내기 전에, 이 요청을 가로채어 처리한다. 서블릿 필터의 경우 스프링 영역이 아닌 웹 컨테이너 영역이었다. 반대로 스프링 인터셉터의 경우 사진에서 알 수 있듯이 스프링 영역이다.
스프링 인터셉터를 사용하기 위해서는 HandlerInterceptor 인터페이스를 구현해야 한다.
package org.springframework.web.servlet;
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()
- Controller가 호출되기 전에 실행된다.
- 컨트롤러로 요청을 전달하기 전에 전처리 작업이 필요한 경우 사용한다.
- postHandle()
- Controller가 호출된 후에 실행된다. 단, View가 렌더링 되기 전이다.
- 매개변수로 ModelAndView를 반환하는데 이는 @Controller를 사용하는 경우에 렌더링 될 View를 전달받는다. 반대로 @RestController는 View가 아닌 JSON 형태의 데이터를 전달받기 때문에 사용되지 않는다.
- afterCompletion()
- View가 렌더링 되는 이후에 실행된다. 즉, 모든 작업이 종료되면 호출된다.
2-1. Spring Interceptor 등록하기
1. HandlerInterceptor 인터페이스를 구현한다.
@Slf4j
public class ExampleInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
log.info("ExampleInterceptor preHandle");
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
log.info("ExampleInterceptor postHandle");
log.info("handler={}", handler);
log.info("modelAndView={}", modelAndView);
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
log.info("ExampleInterceptor afterCompletion");
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
2. 구현한 인터셉터를 등록한다.
WebMvcConfiguer를 구현한 클래스에서 addInterceptors()를 통해 스프링 인터셉터를 등록한다. addPathPatterns()를 통해 특정 요청 URI에 대해서만 스프링 인터셉터가 호출될 수 있도록 할 수 있다.
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new ExampleInterceptor())
.addPathPatterns("/hello");
}
}
HTTP 요청 결과
1. 애플리케이션을 실행한다.
서블릿 필터와 달리 애플리케이션 실행 시점에 preHandle()이 호출되지 않는다. 앞서 말했듯이, 컨트롤러가 호출되기 전에 처리되는 메서드이다.
2. URI가 "/hello"인 HTTP 요청이 들어온다.
HTTP 요청을 컨트롤러로 전달하기 전에 preHandle()이 호출된다.
다음으로 Controller에 HTTP 요청을 전달하여 처리된 결과를 반환받는다. “/hello”를 처리하는 컨트롤러의 경우 JSON 데이터를 반환하는 @RestController이다. 따라서 modelAndView = null을 반환받는다.
3. 스프링 인터셉터가 종료된다.
위 과정이 HTTP 요청이 들어올 때마다 반복된다.
3. 서블릿 필터와 스프링 인터셉터 차이점
1. 관리되는 컨테이너가 다르다.
서블릿 필터는 스프링 이전의 웹 컨테이너 영역에서 관리되고, 스프링 인터셉터는 스프링 영역에서 관리된다. 따라서 필터의 경우 스프링이 제공하는 기능을 사용할 수 없다. 예를 들면, 스프링이 제공하는 예외처리를 사용할 수 없다. 스프링에서는 @ControllerAdvice를 통해 전역에서 예외를 처리할 수 있지만 서블릿 필터는 그렇지 못한다.
2. Request, Response 객체 조작 가능 여부
서블릿 필터의 doFilter()의 매개변수에는 ServletRequest, ServletResponse를 전달받는다.
package jakarta.servlet;
public interface Filter {
default void init(FilterConfig filterConfig) throws ServletException {
}
void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;
default void destroy() {
}
}
스프링 인터셉터는 매개변수로 HttpServletRequest, HttpServletResponse를 받는다.
package org.springframework.web.servlet;
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 {
}
}
필터의 ServletRequest는 최상위 인터페이스이고, 인터셉터의 HttpServletRequest는 ServletRequest의 구현체이다. 서블릿 필터의 경우 필터 체이닝 과정에서 다른 구현체를 바꿔서 넘길 수 있으나, 인터셉터는 그럴 수 없다.
참고
https://velog.io/@ddangle/Spring-Servlet-과-Servlet-Container
https://dev-coco.tistory.com/173