티스토리 뷰

서블릿 필터

Filter란?

// 흐름
HTTP 요청 → WAS → 필터 → 서블릿 → 컨트롤러

// 제한
HTTP 요청 → WAS → 필터(서블릿 호출 x)

// 체인
HTTP 요청 → WAS → 필터 → 필터2 → 필터3 → 서블릿 → 컨트롤러

서블릿이 지원하는 기능으로 서블릿이 호출하기 전에 필터 로직이 실행되며,
특정 URL 패턴을 사용하여 특정 URL 요청에 대하여 적용할 수 있습니다.
필터는 로직에 의해서 적절하지 않은 요청이라고 판단할 경우 서블릿 호출을 하지 않습니다.
그리고 필터는 체인으로 구성되는데, 중간에 필터를 자유롭게 추가할 수 있습니다.
참고로 서블릿은 spring의 DispatcherServlet 입니다.

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() {}
}

필터 인터페이스를 구현하고 등록하면 서블릿 컨테이너가 필터를 싱글톤 객체로 생성하고, 관리합니다.

  • init : 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출합니다.
  • doFilter : 요청이 올 떄 마다 해당 메서드가 호출됩니다.
  • destory : 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출합니다.
@Slf4j
public class LogFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String requestURI = httpRequest.getRequestURI();

        log.info("REQUEST [{}]", requestURI);
        chain.doFilter(request, response);
    }
}

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public FilterRegistrationBean logFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LogFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }

 /*
     필터 추가등록 방법
    @Bean
    public FilterRegistrationBean loginCheckFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new LoginCheckFilter());
        filterRegistrationBean.setOrder(2);
        filterRegistrationBean.addUrlPatterns("/posts/*");
        return filterRegistrationBean;
    }   
 */
}

SpringBoot의 내장 톰캣을 사용하는 경우에만 쓸 수 있는 방법입니다.
그 이유는 FilterRegistrationBean은 스프링 부트에서 필터를 내장 톰캣의 서블릿 컨텍스트에 추가할 수 있도록 지원하는 빈입니다.
만약 다른 was를 사용하는 경우 해당 was의 서블릿 컨텍스트에 필터를 등록할 수 있는 방법을 찾으시면 됩니다.
ex) jeus를 was로 사용하는 경우 web.xml을 통해 필터를 등록합니다.

doFilter

  • chain.doFilter()를 호출하여 다음 필터를 호출합니다. (다음 호출할 필터가 없는 경우 서블릿이 호출됩니다.)
  • ServletRequest, ServletResponse가 파라미터인 이유는 HTTP 요청이 아닌 경우까지 고려해서 만든 인터페이스이기 때문입니다.
    그래서 HTTP를 사용하면 HTTPServletRequest, HTTPServletResponse로 인스턴스가 생성됩니다.
    참고로 HTTPServletRequest, HTTPServletResponse는 ServletRequest, ServletResponse 상속합니다.

FilterRegistrationBean

  • setFilter : 필터를 등록합니다.
  • setOrder : 필터 순번을 정합니다.
  • addUrlPatterns : 필터를 적용시킬 url 패턴을 등록합니다.

스프링 인터셉터

Interceptor란?

// 흐름
HTTP 요청 → WAS → 필터 → 서블릿 → 인터셉터 → 컨트롤러

// 제한
HTTP 요청 → WAS → 필터 → 서블릿 → 인터셉터(컨트롤러 x)

// 체인
HTTP 요청 → WAS → 필터 → 서블릿 → 인터셉터 → 인터셉터2 → 인터셉터3 → 컨트롤러

스프링 MVC가 컨트롤러가 호출되기 전에 인터셉터가 호출이 됩니다.
인터셉터도 URL 패턴을 사용하여 특정 URL 요청에 대하여 적용할 수 있습니다.
서블릿 URL패턴과 다르게 매우 정밀하게 설정할 수 있습니다.
인터셉터도 로직에 의해서 적절하지 않은 요청이라고 판단할 경우 컨트롤러을 호출하지 않습니다.
인터셉터도 체인으로 구성되어 중간에 인터셉터를 자유롭게 추가할 수 있습니다.

Interceptor 사용

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 {
    }
}

스크린샷 2023-03-26 오전 1 33 20

호출 흐름

  • preHandle : 핸들러 어댑터 호출전에 호출됩니다.
  • postHandle : 핸들러 어댑터 호출 후에 호출됩니다.
  • afterCompletion : 뷰 렌더링 이후에 호출됩니다.

스크린샷 2023-03-26 오전 1 57 14

예외가 발생시

  • preHandle : 핸들러 어댑터 호출전에 호출됩니다.
  • postHandle : 컨트롤러에서 예외가 발생할 경우 호출되지 않습니다.
  • afterCompletion : 항상 호출 되고, 예외 발생시 파라미터로 예외를 받아서 확인 할 수 있습니다.
@Sl4fj
public class LogInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
        Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        log.error("preHandle {} {}", requestURI, handler);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
        ModelAndView modelAndView) throws Exception {
        String requestURI = request.getRequestURI();
        log.error("postHandle {} {}", requestURI, handler);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
        Object handler, Exception ex) throws Exception {
        String requestURI = request.getRequestURI();
        log.error("afterCompletion {} {}", requestURI, handler);
    }
}

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        WebMvcConfigurer.super.addInterceptors(registry);
        registry.addInterceptor(new LogInterceptor())
            .order(1)
            .addPathPatterns("/**");
            .excludePathPatterns("/", "/auth/login", "/auth/sign-up", "/auth/logout"
                , "/posts", "/css/**", "/img/**", "/error", "/favicon.ico");
    }
}

preHandle

  • 리턴 타입이 true일 경우 다음 인터셉터를 호출합니다.(인터셉터가 없을경우 컨트롤러가 호출 됩니다.)
  • 리턴 타입이 false일 경우 인터셉터나 컨트롤러를 호출하지 않습니다.

postHandle

  • 컨트롤러가 호출 후에 호출이 되기때문에 ModelAndView를 확인할 수 있습니다.

afterCompletion

  • 예외가 발생하여도 호출되기 때문에 Exception을 확인할 수 있습니다.

handler

  • @Controller, @RequestMapping인 경우 HandlerMethod 인스턴스가 생성됩니다.
  • /resources/static와 같은 정적 리소스인 경우 ResourceHttpRequestHandler 인스턴스가 생성됩니다.

addInterceptors

  • order : 인터셉터의 호출 순서를 정합니다.
  • addPathPatterns : 인터셉터를 적용할 URL 패턴을 지정합니다.
  • excludePathPatterns : 인터셉터에서 제외할 패턴을 지정합니다.

Filter와 Interceptor의 차이점

관리하는 컨테이너

Filter : spring과 상관없이 servlet에서 제공하는 기능이기 때문에 Servlet 컨테이너(was)에서 관리합니다.

Intercptor : spring mvc에서 제공되는 기능으로 spring 컨테이너에서 관리합니다.

처리 순서

Filter : 등록된 순서대로 처리되며(지정한 순번이 있으면 순번대로 처리), 마지막 Filter에서 처리가 완료되면
실제 요청을 처리하는 Servlet이 호출됩니다.

Interceptor : 지정한 순번대로 처리되며, 마지막 Interceptor에서 처리가 완료되면 실제 요청을 처리하는
HandlerApdator(controller)가 호출됩니다.

요청과 응답

Filter : HTTP 요청과 응답의 처리 전/후로 조작합니다.

Interceptor : HandlerApdaptor(controller) 요청과 응답의 처리 전/후로 조작합니다.

등록 방법

Filter : was에 따라 등록 방법이 다릅니다.

Interceptor : was에 상관없이 spring mvc에서 제공되는 방법으로 동일합니다.

구현 방법

Filter : java.servlet 패키지의 Filter 인터페이스를 통해 구현합니다.

Interceptor : org.springframework.web.servlet 패키지의 HandlerInterceptor 인터페이스를 통해 구현합니다.

Reference

https://www.baeldung.com/spring-mvc-handlerinterceptor-vs-filter
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-mvc-2/dashboard
https://mangkyu.tistory.com/173