Filter
는 공통 관심사(Cross-cutting)의 문제를 해결하기 위해 사용됩니다.
개발을 하다보면 여러 로직에서 공통적으로 처리해야하는 것들이 있는데, 이를 해결하지 못하면, 중복된 코드가 많아지고 가독성, 일관성 등이 문제가 될 수 있습니다. 이렇게 공통적으로 생기는 해결해야 되는 것들을 공통 관심사,횡단 관심사(Cross-cutting)라고 합니다.
이런 문제들을 해결할 수 있는 것 중의 하나가 Filter
개념입니다. 이와 같은 개념으로 Interceptor
가 있는데 Filter
와 Interceptor
둘 다 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에 대해 알아야하는 특징은 아래와 같습니다.
@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
를 유심히 봐야한다.
간단하게 Client요청시 발생하는 Request/Response에 대한 LogFiler를 만들어서 동작원리를 이해해보자.
Filter
를 만들 때는 implements Filter
를 해주고 doFilter
를 오버라이드해주면 된다. 그리고 @Component
Annotation을 붙여서 Bean 등록을 해주면 된다. @Component
사용시 한가지 주의할 점은, 원래 Filter는 Spring MVC의 관리는 받을 수 없었지만, 현재 Springboot에선 DelegatingFilterProxy
의 도입으로 Filter도 Spring에서 관리할 수 있도록 됐다. 이것은 나중에 알아보자.
Controller는 알아서 만드시길..
@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를 확인해보자.
Security Manager 체크여부 조건문이 있고 에러가 없다면 최종적으로 ApplicationFilterChain.internalDoFilter()
를 실행한다는 것을 알 수 있습니다.
ApplicationFilterChain.internalDoFilter()
는 실제로 FilterChain
과 Filter
어떻게 동작되는지 확인할 수 있는 부분입니다. Filter
와 FilterChain
의 시작과 끝을 모두 확인할 수 있습니다. 동작의 이해를 위해 필요한 부분만 간추려서 보여드리겠습니다.
LogFilter
를 예시로 설명하겠습니다.
pos
Index로 조회합니다. 증감연산자를 통해 순회하게 됩니다.filterConfig.getFilter()
로 LogFilter
객체를 가져오고LogFilter
객체의 오버라이드된 doFilter
를 실행하게 됩니다. 그리고 오버라이드된 doFilter
메소드안에 구현된 filterChain.doFilter
를 실행하여 다음 필터를 실행합니다. 이때, LogFilter
의 doFilter
는 아직 끝나지 않은 상태입니다.filter.doFilter
가 물린 상태에서 pos
가 7이 되면 Dispatcher Servlet이 실행되고 Spring Container에서의 작업을 끝마치게 됩니다.filter.doFilter
를 종료하고 return
을 하며 ApplicationFilterChain
의 메소드를 빠져나오게 됩니다. 이전에 끝내지 못한 LogFilter
의 filterChain.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");
}
}
}
필터의 동작원리를 다음과 같이 그림으로 정리해보았습니다.
좋은 정보 얻어갑니다, 감사합니다.