[Springboot] Servlet Filter

xox·2023년 8월 18일
1

Springboot

목록 보기
1/2

Servlet Filter 개요

Filter공통 관심사(Cross-cutting)의 문제를 해결하기 위해 사용됩니다.
개발을 하다보면 여러 로직에서 공통적으로 처리해야하는 것들이 있는데, 이를 해결하지 못하면, 중복된 코드가 많아지고 가독성, 일관성 등이 문제가 될 수 있습니다. 이렇게 공통적으로 생기는 해결해야 되는 것들을 공통 관심사,횡단 관심사(Cross-cutting)라고 합니다.

이런 문제들을 해결할 수 있는 것 중의 하나가 Filter개념입니다. 이와 같은 개념으로 Interceptor가 있는데 FilterInterceptor 둘 다 Cross-cutting 문제를 해결할 수 있지만, 기능과 사용방식, 허용범위가 다릅니다. 가장 큰 차이점은 Filter는 Servlet Container영역에서 동작하고, Interceptor는 Spring Container에서 동작하는 점입니다.

이후 포스팅에서 Interceptor와 그 차이점에 대해서 이야기할 것입니다. Spring Document에 적혀있는 Filter의 정의는 아래와 같습니다.

[해석] 필터는 리소스(서블릿 또는 정적 컨텐츠)요청/응답에 대해 필터링 작업을 수행하는 객체입니다.필터는 dofilter 메서드에서 필터링 작업을 수행합니다. 모든 필터들은FilterConfig 객체에 접근해서 초기화된 매개변수들을 얻어올 수 있습니다.

[사용예시]
1. 인증 필터
2. 로깅
3. 이미지 변환
4. 데이터 압축
5. 암호화
6. 토큰화
7. 리소스접근
8. XSL/T
9. MIME-type 체인 필터

Filter에 대해 알아야하는 특징은 아래와 같습니다.

  • 자바 표준 스펙
  • Servlet Container 영역에서 관리된다
  • Dispatcher Servlet 전/후에 처리한다.
  • Servlet Request/Response를 필터 체이닝 중간에 새로운 객체로 변경가능
  • 예외 발생시 @ControllerAdvice를 사용할 수 없다.
public interface Filter {

    default void init(FilterConfig filterConfig) throws ServletException {}

    void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException;

    default void destroy() {}
}

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

  • init(): 필터 초기화 메서드, 서블릿 컨테이너가 생성될 때 호출된다.
  • doFilter(): 고객의 요청이 올 때 마다 해당 메서드가 호출된다. 필터의 로직을 구현하면 된다.
  • destroy(): 필터 종료 메서드, 서블릿 컨테이너가 종료될 때 호출된다.

이렇게 3개의 메서드가 구현되어 있는데, 3번째 입력변수 FilterChain chain를 유심히 봐야한다.

Filter의 동작원리

간단하게 Client요청시 발생하는 Request/Response에 대한 LogFiler를 만들어서 동작원리를 이해해보자.
Filter를 만들 때는 implements Filter를 해주고 doFilter를 오버라이드해주면 된다. 그리고 @Component Annotation을 붙여서 Bean 등록을 해주면 된다. @Component 사용시 한가지 주의할 점은, 원래 Filter는 Spring MVC의 관리는 받을 수 없었지만, 현재 Springboot에선 DelegatingFilterProxy의 도입으로 Filter도 Spring에서 관리할 수 있도록 됐다. 이것은 나중에 알아보자.
Controller는 알아서 만드시길..

Filter

@Slf4j
@Component
public class LogFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    /*  Log Filter 예제 */
        try {
            log.info("[Filter 1] REQUEST");
     /* Log Filter가 동작하는 부분 */       
            chain.doFilter(request, response);
        }catch (Exception e){
            throw e;
        }finally {
            log.info("[Filter 1] RESPONSE");
        }
    }
}

동작원리는 chain.doFilter()코드 전/후로 나뉜다.
chain.doFilter()는 FilterChain의 doFilter를 사용하는데, 이는 FilterChain에 등록된 다음 Filter를 호출하는 로직이다. 구현된 Filter의 모든 로직을 수행하지 않고, chain.doFilter()지점인 로직의 중간에 빠져나와서 다음 필터를 실행하는 것이다. FilterChain의 doFilter를 확인해보자.

ApplicatioFilterChain

internalDoFilter


Security Manager 체크여부 조건문이 있고 에러가 없다면 최종적으로 ApplicationFilterChain.internalDoFilter()를 실행한다는 것을 알 수 있습니다.
ApplicationFilterChain.internalDoFilter()는 실제로 FilterChainFilter 어떻게 동작되는지 확인할 수 있는 부분입니다. FilterFilterChain의 시작과 끝을 모두 확인할 수 있습니다. 동작의 이해를 위해 필요한 부분만 간추려서 보여드리겠습니다.

LogFilter를 예시로 설명하겠습니다.

  1. FilterConfig에 Array로 등록된 모든 Filter들을 pos Index로 조회합니다. 증감연산자를 통해 순회하게 됩니다.
  2. filterConfig.getFilter()LogFilter 객체를 가져오고
  3. 해당 LogFilter객체의 오버라이드된 doFilter를 실행하게 됩니다. 그리고 오버라이드된 doFilter 메소드안에 구현된 filterChain.doFilter를 실행하여 다음 필터를 실행합니다. 이때, LogFilterdoFilter는 아직 끝나지 않은 상태입니다.
  4. filter.doFilter가 물린 상태에서 pos가 7이 되면 Dispatcher Servlet이 실행되고 Spring Container에서의 작업을 끝마치게 됩니다.
  5. Spring Container의 모든 작업을 마치면 아직 끝나지 않았던 3번의 filter.doFilter를 종료하고 return을 하며 ApplicationFilterChain의 메소드를 빠져나오게 됩니다. 이전에 끝내지 못한 LogFilterfilterChain.doFilter 부분으로 돌아가 남아있는 코드들을 수행하게 됩니다.
@Slf4j
@Component
public class LogFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
        /* Request시 수행되는 코드 */
            log.info("[Filter 1] REQUEST");
            
        /* 다음 Filter를 실행 */
            chain.doFilter(request, response);
        }catch (Exception e){
            throw e;
        }finally {
        /* Dispatcher Servlet의 모든 작업을 끝내고 Response시 수행되는 나머지 코드 */
            log.info("[Filter 1] RESPONSE");
        }
    }
}

정리

필터의 동작원리를 다음과 같이 그림으로 정리해보았습니다.

  • 요청시 FilterChain을 통해 전달된다. FilterChain은 Filter들로 구성되있다.
  • 필터는 요청을 받으면 순차적으로 실행중간에 빠져나와 다음 필터를 실행하게된다.
  • 응답시에는 끝내지 못했던 필터 구현체들을 역순으로 실행하며 작업을 마친다.

profile
xxx software developer

2개의 댓글

comment-user-thumbnail
2023년 8월 18일

좋은 정보 얻어갑니다, 감사합니다.

1개의 답글