필터와 인터셉터

조현근·2022년 10월 25일
0
post-thumbnail

테코톡

테코톡

우아한테크코스를 수료하려면 테코톡을 해야한다. 테코톡은 관심있는 기술을 정하고 조사한 다음 크루들 앞에서 발표를 하는것이다. 스프링을 공부하면서 필터와 인터셉터라는 개념을 접했는데, 둘의 차이가 무엇인지 언제 써야하는지 궁금했었다. 그래서 테코톡 주제를 필터와 인터셉터로 정하고 공부해보았다.
테코톡을 준비하면서 공부했던 내용을 정리해보자!

필터와 인터셉터

필터와 인터셉터가 무엇인지 먼저 알아보자.

필터

필터는 Dispatcher Servlet에 요청이 전달되기 전, 후에 부가작업을 처리하는 객체라고 한다.
우테코에서 팀 프로젝트를 진행하면서 Request의 body를 두 번 읽어야 하는 일이 있었는데(팀원들과 논의끝에 이 방법을 사용하지 않기로했다..), 이때 필터를 통해 해결하려 했던 기억이 있다. 간략하게 매커니즘을 설명하면 필터의 매개변수로 들어오는 Request를 Custom한 Request로 바꿔치기해서 InputStream에 저장된 body를 여러번 읽을 수 있게 만드는 것이다. 다른 크루는 토큰을 검증하는 용도로 필터를 사용했다고 한다.
필터는 이와같이 전처리가 필요한 작업이나, 비즈니스 로직에서의 공통의 관심사항을 처리하는 용도로 사용할 수 있다.

인터셉터

인터셉터는 Dispatcher Servlet이 Controller를 호출하기 전/후 요청에 대해 부가적인 작업을 처리하는 객체라고 한다.
인터셉터는 실제로 몇 번 사용했는데, 팀 프로젝트에서 로그인 토큰을 검증하는 용도로 사용하거나 사용자가 권한이 없는 요청을 사용하려고 할 때 사용했다(권한이 없는 요청에 대한 인터셉터 사용도 팀원들과의 논의끝에 다른 방법을 사용했다..).

그래서 차이가?

필터와 인터셉터에 대해 처음 접하면 도대체 둘의 차이가 무엇인지, 언제 어떤것을 써야하는지 기준이 서질 않는다(아직도 명확하게 구분하진 못하겠다). 당장 위의 예시만 봐도 결국에 똑같은 작업을 처리하기 위한 것이 아닌가?라는 생각이 든다.
이제 테코톡을 준비하면서, 그리고 김영한님의 강의와 여러 블로그를 보면서 얻은 지식을 통해 어떤 차이가 있는지 알아보자!

디버깅을 이용해 알아본 필터와 인터셉터

테코톡을 준비하면서 쿤과 함께 필터와 인터셉터가 언제 어떻게 동작하는지 알아보았다. 디버깅 포인트를 설정하고 테스트코드를 동작시켜 직접 눈으로 확인하니 필터와 인터셉터의 실행시점에 대해 알 수 있게 되었다.

필터

필터에는 2가지 default메서드와 1개의 인터페이스 메서드(?)가 있다. 하나하나 살펴보자.

  1. init (default)

    필터 인터페이스에 달린 주석을 해석해보면 web 컨테이너에 의해 딱 한번 호출된다. 필터가 서비스에 위치했다는 것을 의미한다. init메서드는 필터가 작업을 하기 전에 반드시 성공적으로 완료되어야 한다.가 된다. 이 메서드를 override해 log를 찍어보면 어플리케이션이 실행될때 호출되는 것을 확인할 수 있다.

  2. destroy (default)

    destroy의 주석도 해석해보자! web 컨테이너에 의해 딱 한번 호출된다. 필터가 서비스에서 제거되었다는 의미를 가진다. 모든 스레드가 필터의 doFilter메서드를 완료했거나 timeout이 났을때 호출된다. destroy메서드 호출 이후엔 doFilter메서드를 호출할 수 없다.
    역시 log를 찍어 어플리케이션을 실행하면, 어플리케이션이 종료될때 호출되는 것을 알 수 있다.

  3. doFilter

    필터에서 가장 중요한 메서드이다! 필터가 처리할 작업이 실행되는 곳이다. 주석을 해석해보면 doFilter메서드는 컨테이너에 의해 호출된다. chain의 마지막에 있는 클라이언트의 자원에 대한 요청을 위해 request/response쌍이 chain을 통해 전달된다. FilterChain의 필터는 request와 response를 chain의 다음 entity에 전달할 수 있다.라고 이해할 수 있다.
    doFilter의 특징은 매개변수로 받은 request, response를 chain.doFilter(req, res)를 통해 다음 순번으로 넘길 수 있다는 것인데, 이로인해 request와 response를 필터에서 바꿔치기 할 수 있다. 또한 doFilter 이전에 로직을 넣게 되면 비즈니스 로직이 실행되기 이전에 전처리를 할 수 있고, doFilter 이후에 로직을 넣으면 후처리를 할 수 있다.

서블릿 필터의 동작방식

필터의 동작방식을 디버깅을 통해 알아보자!
필터는 ApplicationFilterChain이라는 곳에서 실행된다.

위와 같이 몇몇 정책을 확인한 후 해당사항이 없으면 internalDoFilter라는 메서드를 호출한다.

internalDoFilter에선 등록된 필터 수 만큼 인스턴스 변수 n을 유지하고 pos라는 인스턴스 변수로 현재 필터가 몇 번째 필터인지 식별한다. 그리고 몇몇 정책을 확인한 후 필터의 doFilter메서드를 호출한다. 위에서 살펴본것 처럼, doFilterchainthis가 넘겨지기때문에 이후 chain.doFilter를 호출하면 다시 ApplicationFilterChaindoFilter가 호출되는 것이다.
모든 필터가 동작하면 실행흐름은 DispatcherServlet으로 넘어가게 된다. 대략적인 동작방식을 그림으로 그리면 아래와 같다!

인터셉터

인터셉터에는 3가지 default 메서드가 있다.

각 메서드의 주석을 해석해보자!
1. preHandle
handler가 실행되기 전에 실행된다. 핸들러 매핑을 통해 적절한 핸들러를 가져온 후 실행된다. DispatcherServlet은 handler를 여러개의 인터셉터들로 구성된 execution chain안에서 실행한다. 각각의 인터셉터은 execution chain을 중지할지 결정할 수 있다.
2. postHandle
핸들러가 성공적으로 실행된 이후에 인터셉션이 이루어지는 곳이다. Handler를 실행하는 handlerAdapter 이후에 실행된다. 하지만 DispatcherServlet이 view를 render하기 전에 실행된다. 매개변수로 주어지는 modelAndView객체를 통해 추가적인 model을 노출시킬 수 있다. 각 인터셉터는 실행의 후처리를 할 수 있다. execution chain에 등록된 역순으로 실행된다.(preHandle이 실행되는 순서의 역순)
3. afterCompletion
request 처리가 끝난 후 callback된다. 즉, view를 rendering한 후 실행된다. handler execution의 결과로 부터 불리기 때문에 적절한 resource를 정리할 수 있다. 인터셉터의 preHandle이 성공적으로 완료된 경우에만 afterCompletion이 실행된다. preHandle이 실행된 역순으로 afterCompletion이 실행된다.

인터셉터의 동작방식

인터셉터는 DispatcherServletdoService메서드로 부터 실행된다. doService메서드는 doDispatch메서드를 호출한다.

doDispatch메서드 내부에서 우선 알맞은 핸들러를 찾아오는 작업이 이루어진다.

이는 아래 사진에서 핸들러 매핑 과정이다.

핸들러를 찾아온 이후에는 핸들러를 실행시킬 수 있는 핸들러 어뎁터를 가져온다.

이는 아래 사진에서 핸들러 어댑터를 가져오는 과정이다.

핸들러 어뎁터까지 가져온 후 이제 인터셉터들의 preHandle을 실행하는 메서드가 호출된다.


매서드 내부를 보면 인터셉터 리스트를 순서대로 돌면서 각각의 preHandle을 실행시키는 것을 알 수 있다.

모든 인터셉터의 preHandle이 성공적으로 실행된 후 핸들러 어뎁터를 통해 핸들러가 실행된다.
이는 아래 사진에서 핸들러 어뎁터를 통해 실제 핸들러를 실행시키는 과정이다.


핸들러가 예외없이 동작했으면 이제 postHandle을 실행시키는 메서드를 호출한다.

postHandle은 인터셉터 리스트의 역순으로 실행되는 것을 알 수 있다.

postHandle이 모두 실행된 이후엔 processDispatchResult라는 메서드가 호출된다.

processDispatchResult에선 우선 view관련 로칙을 처리하는 메서드가 호출된다.
이는 아래 그림에서 빨간 네모박스 부분에 해당된다.


이제 마지막으로 afterCompletion을 실행하는 메서드가 호출된다.

afterCompletion 역시 인터셉터 리스트의 역순으로 실행되는 것을 알 수 있다.

필터와 인터셉터의 차이가?

장황하게 동작방식에 대해서만 이야기한거 같다. 그래서 필터와 인터셉터의 차이는 무엇일까? 김영한님의 강의에서도 언급되었는데 다음과 같이 정리할 수 있을거 같다.
1. 다음 필터를 실항하기 위해 doFilter내부에서 chain.doFilter를 개발자가 명시적으로 호출해야 한다. 하지만 인터셉터는 그럴 필요가 없다.
2. 필터의 매개변수로 들어오는 ServletRequestServletResponse를 바꿔치기 할 수있다. 하지만 인터셉터에선 지역변수로 Request/Response가 유지되니 바꿔치기 할 수 없다.
3. 필터에서 예외가 발생하면 @ControllerAdvice에서 처리하지 못한다. 하지만 인터셉터에서 발생한 예외는 처리할 수 있다.(이는 @ControllerAdvice의 동작범위가 DispatcherServlet으로 한정되었기 때문에 DispatcherServlet외부에서 동작하는 필터는 처리하지 못하는 것이라고 한다.)

사실 아직은 어떤 전처리/후처리 과정을 할때 필터와 인터셉터 중 어느것이 더 적절한지 딱 집어 말하진 못하겠다. 웹 어플리케이션을 개발하면서 공통의 관심사는 웬만해선 필터와 인터셉터 둘 다 이용해 처리할 수 있었다. 내가 경험한 딱 한가지, 필터에서만 처리가능했던 기능은 Request객체를 바꿔치기 할 일이 필요했던 RequestBody를 두 번 읽는 작업이였다. 인터셉터에서는 Request객체를 바꿀 수 없기 때문에 이 기능을 구현할 수 없었다. 앞으로 더 공부하면서 어떤 상황에 어떤 기능을 사용하는게 더 적절한지 나만의 기준을 찾아보아야겠다!

참고한 곳

https://docs.oracle.com/javaee/7/api/javax/servlet/Filter.html
https://www.baeldung.com/spring-mvc-handlerinterceptor-vs-filter#key-differences-and-use-cases
https://jaehun2841.github.io/2018/08/25/2018-08-18-spring-filter-interceptor/#Spring-Request-Flow
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-handlermapping-interceptor
https://docs.oracle.com/javaee/6/api/javax/servlet/FilterConfig.html
https://docs.oracle.com/javaee/7/api/javax/servlet/FilterChain.html
스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 (김영한님)
토비의 스프링 3.1 Vol 2 (이일민님)
https://mangkyu.tistory.com/173

profile
안녕하세요!

0개의 댓글