본문 바로가기
Spring

[Spring] 필터(Filter)와 인터셉터(Interceptor)

by doodoom 2023. 4. 19.

0. 이 글을 쓰게 된 이유

스프링은 개발자가 비즈니스 코드에 더 집중할 수 있게 여러 기능들을 숨기고있다. 대표적으로 AOP, Filter, Interceptor라는 기술을 사용한다. AOP는 스프링의 대표 기술로써 프록시를 이용해 코드를 숨겨주는 기능을 한다는 것은 대부분 알고 있을 것이다. 그렇다면 Filter와 Interceptor는 무엇일까? 그 차이와 용도에 대해서 알아보자.

1. Filter

필터는 스프링의 기술이 아닌 JavaEE 표준 스펙 기능으로써 톰캣과 같은 WAS가 가지고있는 기술이다.
JavaEE 필터 공식문서를 보면 필터는 요청이나 응답 또는 둘 다에 대해 필터링을 수행하는 객체라고 소개한다. 즉, Web Context 단에서 Spring으로 요청을 보내기 전과 응답이 온 후로 특정 작업(필터링이라고 부른다)을 수행할 수 있는 기능이다. 사진으로 보면 다음과 같다.

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

필터는 Spring으로 닿기 전과 후에 처리하므로 디스패처 서블릿이 요청이 닿기도 전에 먼저 실행되고 응답이 디스패처 서블릿을 타고 나왔을 때에도 실행된다.

1.1 예제 코드

실제로 filter가 spring 밖에서 동작하는지 확인하는 예제 코드는 다음과 같다.
스프링 부트에서는 Filter는 Filter 인터페이스를 구현하고 이를 스프링 빈으로 등록한 후에 사용할 수 있다. 기존 레거시 스프링에서는 Config나 xml을 통해 따로 설정 해줬어야했다. 하지만 지금은 그냥 컴포넌트 스캔을 통해 바로 사용이 가능하다.

1.1.1 Filter

@Component
public class TestFilter implements Filter {


    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response,
        final FilterChain chain) throws IOException, ServletException {
        System.out.println("Pre Filter is running!!!"); // 요청이 spring context에 닿기 전에 실행
        chain.doFilter(request, response);
        System.out.println("Post Filter is running!!!"); // 응답이 spring context에서 나온 후 실행
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

요청 앞 뒤로 console에 요청을 찍었다.

1.1.2 Controller

@RestController
public class PracticeController {

    @GetMapping("/test")
    public ResponseEntity<String> test() {
        System.out.println("Controller is Running!!!");
        return ResponseEntity.ok().body("test");
    }
}

Spring에 요청이 들어오면 실행되는 메소드이다.

1.1.3 결과

filter 실행이 앞 뒤에서 되는 것을 알 수 있다.

2. Interceptor

필터랑 다르게 인터셉터는 스프링의 기술이어서 스프링 컨텍스트가 실행한다. 스프링에서 Dispatcher Servlet이 핸들러 매핑을 통해 적절한 컨트롤러를 찾는데 그 반환값으로 HandlerExecutionChain을 반환한다. HandlerExecutionChain은 Interceptor들을 가지고있고, 순서에 따라 각각 실행된 뒤 Controller를 실행하게 된다. 이런 과정으로 인터셉터는 스프링에 들어온 후 controller에 닿기 전 후 공통 전처리 작업을 할 수 있다.
그림으로 보면 다음과 같다.

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

2.1 예제 코드

인터셉터는 HandlerInterceptor 인터페이스를 구현하고 이를 config 파일을 통해 설정하여 사용할 수 있다. interceptor는 chain 패턴으로 구현되어 있기 때문에 순서가 매우 중요하다. 그리고 어떤 요청에만 응답할지 등 여러가지 옵션이 있다. 그런 설정을 유연하게 해야하기 때문에 꼭 config 파일을 통해 등록해야하지 않나싶다.

2.1.1 Interceptor

public class TestInterceptor implements HandlerInterceptor {

    // 컨트롤러 전 실행 -> true를 반환하면 그 다음 필터로 넘어가고 false를 반환하면 넘어가지 못한다.
    // handler에 접근이 가능하다.
    @Override
    public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response,
        final Object handler) throws Exception { 
        System.out.println("PreInterceptor is running!!!");
        return true;
    }

    // 컨트롤러 후 실행, handler와 modelAndView에도 접근이 가능하다.
    @Override
    public void postHandle(final HttpServletRequest request, final HttpServletResponse response,
        final Object handler, final ModelAndView modelAndView) throws Exception {
        System.out.println("PostInterceptor is running!!!");
    }

    // 모든 작업이 완료된 후 실행
    @Override
    public void afterCompletion(final HttpServletRequest request,
        final HttpServletResponse response,
        final Object handler, final Exception ex) throws Exception {
        System.out.println("AfterInterceptor is running!!!");
    }
}

2.1.2 Controller

@RestController
public class PracticeController {

    @GetMapping("/test")
    public ResponseEntity<String> test() {
        System.out.println("Controller is Running!!!");
        return ResponseEntity.ok().body("test");
    }
}

Spring에 요청이 들어오면 실행되는 메소드이다.

2.1.3 결과

3. Filter와 Interceptor의 차이

언뜻 보기에 Filter와 Interceptor는 거의 비슷한 기능을 하는 것으로 보인다. Spring boot를 사용하면 Spring의 빈도 Filter에 등록 가능하고, 두 기능 모두 Request, Response를 다루니 무슨 의미인가 싶다. 하지만 기능적으로나 논리적으로 차이가 분명히 있다.

3.1 Request, Response 조작 가능 여부

Filter는 다음 Filter로 책임을 넘길 때, Request와 Response를 직접 넘긴다.

@Override
    public void doFilter(final ServletRequest request, final ServletResponse response,
        final FilterChain chain) throws IOException, ServletException {
        chain.doFilter(request, response);
    }

하지만 Interceptor는 DispatcherServlet 내부적으로 for문으로 돌기 때문에 boolean을 반환한다. 하나의 Interceptor에서 false를 반환하면 요청이 중단된다.

 @Override
    public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response,
        final Object handler) throws Exception {
        return false;
    }

그렇기 때문에 Filter는 중간에 요청이나 응답을 가로채서 직접 가공 및 변경이 가능하다. Interceptor도 요청과 응답에 접근 가능하기 때문에 물론 가공이 가능하다. 하지만 요청과 응답을 감싸고았는 객체가 의도한대로만 가공이 가능하기 때문에 가공과 변경의 자유도는 Filter보다 떨어진다고 볼 수 있다.
예를 들어 Filter는 요청과 응답을 아예 새로운 객체로 바꿀 수 있다.

3.2 컨테이너 범위

앞서 말했다싶이 Filter는 Web Context에서 관리하고, Interceptor는 Spring에서 관리한다. 즉, Filter는 Spring 내부에서 발생하는 일을 알지 못한다. 만약 Spring에서 ExceptionHandler를 통해서 특정 예외를 잡고있는데 Filter에서 해당 예외가 터지면 그 ExceptionHandler는 당연히 동작하지 않는다.
Spring boot가 나오기 전에는 Filter에서 Spring Bean을 사용하지 못했다. Filter는 Web Context의 영역이기 때문에 당연했다. 하지만 Spring boot가 나온 시점부터는 가능하다. 왜냐하면 Spring boot는 Spring Context 뿐만 아니라 Web Context 영역까지 관리가 가능하기 때문이다. 즉, Spring boot를 사용하면 Filter에서도 Spring Bean을 주입 받고 사용이 가능하다.

4. Filter와 Interceptor 용도

Spring boot가 아닌 레거시 Spring을 사용할 때에는 Filter는 Spring Bean을 사용하지 못하기 때문에 Interceptor와 명확한 차이가 있었다. 하지만 대부분의 프로젝트가 Spring boot로 작동하는 현시점에서는 그 차이는 무의미해졌다.
그래서 Filter와 Interceptor를 사용하는 기준이 조금 모호해졌다고 생각하지만, 나름대로 생각하는 용도는 다음과 같다.

4.1 Filter의 용도

Filter는 Spring으로 가기 전과 Spring에서 나온 후의 데이터를 처리할 수 있기 때문에 조금 low level 데이터 처리가 가능하다. 예시는 다음과 같다.

  • 이미지/데이터 압축 및 문자열 인코딩
  • 공통으로 처리해야하는 보안 관련 작업
  • 로깅
  • Spring으로 들어갈 데이터 변경 및 가공 -> 자유도가 굉장히 높음, Spring으로 들어가기 전이라 raw한 데이터를 가공 가능
    즉, Spring과 무관한 작업을 처리하기에 좋다.

4.2 Interceptor의 용도

Interceptor는 DispatcherServlet에서 나온 요청과 Controller에서 나온 응답에 대한 데이터를 처리할 수 있다. 그렇기 때문에 Spring과 관련된 데이터(handler, modelAndView)들도 처리할 수 있다. 용도는 다음과 같다.

  • 비즈니스 로직이 들어간 보안
  • API 호출에 대한 로깅
  • Controller로 넘겨주는 정보(데이터)의 가공 -> 자유도가 떨어짐