Interceptor & Filter

Soonwoo Kwon·2024년 8월 22일

Spring 에서 각 엔드포인트 또는 특정 엔드포인트 요청 마다 공통된 작업을 수행하기 위해 사용되는 대표적인 방식으로
FilterInterceptor가 있다.

Filter 는 웹 컨테이너에 존재하며 DispatcherServlet 이전에 동작한다.
Interceptor 는 DispatcherServlet 이후에 동작하며 Spring 컨텍스트에 접근 할 수 있다.

이 두 Filter 와 Interceptor 의 활용 방법을 알아보자.

Filter

Filter 는 Spring Context 밖에 위치하며 웹 컨텍스트에 존재한다.
요청이 DispatcherServlet 에 전달되기 이전에 수행되며 Spring Context 에는 접근하지 못한다.

Filter 는 다음과 같은 인터페이스를 가지고 있다.

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

  • init() 서블릿 컨테이너에 의해 Filter가 처음 생성 될 때 호출된다.
  • Filter가 초기화 될 때 필요한 리소스나 설정을 로드하는데 사용되며, 주로 데이터베이스 연결 초기화, 서블릿 컨텍스트 접근, 로그 초기화 등과 같은 설정 작업을 수행한다.
  • FilterConfig를 통해 Filter의 정보나 서블릿 컨텍스트에 접근할 수 있다.

FilterConfig

public interface FilterConfig {
    String getFilterName();

    ServletContext getServletContext();

    String getInitParameter(String var1);

    Enumeration<String> getInitParameterNames();
}

doFilter()

  • 실제 필터링 로직을 수행하는 핵심 메서드이다.
  • 요청을 가로채어 필터링 작업을 수행하고, 응답을 수정 할 수 있다.
  • FilterChain 기반으로 동작하며 정의된 필터 체인을 따라 다음 필터로 요청을 전달한다.

destroy()

  • Filter 인스턴스가 destroy 될 때 호출된다.
  • init() 에서 초기화된 자원을 정리하는 데 주로 사용됩니다.

Filter 사용 방식

1. @Component 등록

@Component
@Order(1)
public class MyFilter implements Filter {

    // Filter 메서드 구현
}
  • 직접 @Component 로 등록하는 방식이다.
  • @Order 어노테이션을 통해 필터의 순서를 지정할 수 있다.

2. Filter 등록

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<MyFilter> loggingFilter() {
        FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();

        registrationBean.setFilter(new MyFilter());
        registrationBean.setOrder(1);
        registrationBean.addUrlPatterns("/api/*");

        return registrationBean;
    }
}
  • Configuration 클래스 내에서 filter를 빈으로 등록하는 방식이다.
  • FilterRegistrationBean 을 통해 필터를 등록하고, 필터의 순서와 적용할 URL 패턴을 지정할 수 있다.

Interceptor

Interceptor는 Spring context 내부에 위치하며 요청이 Dispatcher Servlet 에서 Controller 로 전달되기 이전에 수행된다.
또한 Controller로 요청이 전달 된 이후에 사용자에게 응답이 전달 될 때에도 영향을 줄 수 있다.

Spring Context 내부에 위치하여 빈 객체들에 접근이 가능하다.

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

preHandle()

  • Controller 에 요청이 전달되기 이전에 로직을 수행한다.
  • boolean 타입을 반환하며 true 값을 반환하면 해당 요청을 Controller 로 전달하고, false 라면 전달하지 않는다.
  • 주로 로깅, 인증과 같은 로직이 수행된다.

postHandle()

  • 컨트롤러에 요청이 전달된 이후 뷰를 렌더링 하기 이전에 로직을 수행한다.
    - ModelAndView 를 인자로 가지고 있으며 이를 통해 뷰에 대한 로직을 수행 가능
  • 요청이 종료된 이후 수행될 로직들을 처리하는데 사용되며 주로 로깅, 인증에 따른 별도의 뷰 제공 로직이 수행된다.

afterCompletion()

  • 뷰가 렌더링 된 이후에 수행되며 리소스 정리, 로깅, 예외 처리 등 후처리 작업에 사용된다.
  • 또한 요청의 생명주기 가장 마지막에 수행되므로, 성능 모니터링 로직 등에 활용하기 좋다.
  • 요청 처리 중 예외가 발생하면 postHandle()은 수행되지 않지만, afterCompletion()은 항상 수행된다.

Filter와 Interceptor의 비교

Filter

  • filter 는 Java Servlet API 에서 제공되는 기능으로, Spring 과 같은 FrameWork와 독립적으로 활용될 수 있다.
  • CORS(Cross-Origin Resource Sharing) 처리를 하는데 유리하다.
    • 그 이유는 preflight 요청은 서버의 엔드포인트에 도달하기 전에 처리되는데 Interceptor는 Spring의 핸들러 매핑 단계 이후에 동작하므로, 프리플라이트 요청을 적절하게 처리하기에는 부적합하다.

CORS Filter 예시

public class CORSFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {}

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;

        res.setHeader("Access-Control-Allow-Origin", "*"); // 허용하는 도메인 설정
        res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
        res.setHeader("Access-Control-Allow-Credentials", "true");

        if ("OPTIONS".equalsIgnoreCase(req.getMethod())) {
            res.setStatus(HttpServletResponse.SC_OK);
            return;
        }

        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {}
}

Intercetptor

  • Spring Context 내부에 위치하여 빈 객체를 주입받아 구현 할 수 있다.
  • preHandle, postHandle, afterCompletion 세 단계로 나누어 요청의 처리 전후 과정을 세분화하여 제어할 수 있다.

어떻게 선택하여 사용하면 좋을까?

Filter 는 Framework에 의존하지 않고 수행되어야 하는 기능들을 정의하기에 좋고, interceptor 보단 상대적으로 이른 타이밍에 로직이 수행된다.
요청의 이른 시점에 수행할 로직이나, 서버 엔드포인트에 도달하기 이전에 수행될 로직을 정의하기에 좋다.

Interceptor의 가장 큰 장점은 빈 객체에 접근할 수 있다는 점이다. 따라서 비즈니스 로직과 관련된 기능을 활용할 수 있으며, 요청에 대한 비즈니스 로직과 관련된 처리가 필요하다면 Interceptor가 적합해 보인다.

특별한 상황이 아니라면 Interceptor 를 활용하는 것이 더 편리하여 이를 주로 이용하는 편이다.

추가적인 Interceptor의 장점

특정 조건에 따라 Filter 를 적용하지 않을 요청을 선별하려 한다면, 주로 ServletRequest 에서 얻을 수 있는 요청의 메타 데이터 만을 이용하게 된다.
보통은 요청 경로, 헤더 등을 통해 선별을 하는 경우가 많다.

하지만 이는 새로운 경로가 추가되거나, 관련 헤더가 변경될 경우 현행화가 잘 되지 않거나, 놓치기 쉬운 부분이다.

따라서 Controller 메서드에 애노테이션을 사용하여 특정 요청에만 Filter가 적용되게 구현하려 하였지만, Filter 는 Controller에 요청이 도달하기 훨씬 이른 시점에 수행되며 구현이 불가능 했다.

반면 Interceptor 는 DispatcherServlet이 요청을 수행할 handler(Controller)를 결정한 이후에 수행되며, preHandle() 메서드의 handler 파라미터를 통해 Controller 메서드의 정보를 가져올 수 있다.
Reflection을 통해 Controller 메서드에 적용된 애노테이션 정보를 가져올 수 있고, 애노테이션 방식으로 Interceptor 적용 여부를 핸들링 할 수 있다.


사진 출처: https://mangkyu.tistory.com/173

0개의 댓글