오늘 다룰 주제는 인터셉터와 필터의 차이.
둘 다 요청을 가로채서 처리할 수 있는 기능을 가졌다는 공통점이 있어서 혼동하하기 쉬운 개념이라고 생각한다.
필터
오라클 공식문서에서는 다음과 같이 설명이 되어있다. 즉 요청과 응답에 대해 필터링 작업을 수행하는 주체.
필터는 DispatcherServlet 전, 후로 요청을 가로채서 동작하기에 스프링 영역 밖에서 관리된다는 특징이 있다.
자바에서는 인터페이스 형태로 필터를 제공하며 아래 3개의 메서드를 오버라이드 하여 사용할 수 있다.
1. init : 초기화 작업을 진행. (서블릿 컨테이너에서 필터 인스턴스를 만든 뒤 단 한 번만 호출된다.)
2. doFilter : 실제 필터링 로직을 구현. 요청을 처리하기 전후로 request, response를 수정할 수 있다.
3. destroy : 필터를 제거할 때 사용. 즉 자원을 해제한다.(예를 들면 데이터베이스 연결이나 파일 핸들러 같은 리소스 정리)
간단하게 특정 uri를 가로채는 필터를 만들어보자.
@Slf4j
@WebFilter(urlPatterns = "/hello")
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
log.info("로그 필터 초기화 진행");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
log.info("Request URI : {}", httpServletRequest.getRequestURI());
// 다음 필터나 서블릿으로 요청 전달
chain.doFilter(request, response);
}
@Override
public void destroy() {
Filter.super.destroy();
log.info("로그 필터 소멸");
}
}
@WebFilter라는 어노테이션을 사용해서 해당 클래스를 서블릿 필터로 등록시킨다.
@ServletComponentScan
public class CsApplication{
public static void main(String[] args) {
SpringApplication.run(CsApplication.class, args);
}
}
그리고 메인 어플리케이션에 @ServletComponentScan을 붙여야 서블릿 컴포넌트를 스캔하도록 만들 수 있다.
순서는 아래와 같이 동작한다.
1. 현재 코드의 순서로 가면 서블릿 컨테이너가 생성되면 서블릿 컴포넌트를 스캔하여 필터로 등록한다.
2. 스캔된 필터를 인스턴스화 시켜 init() 메서드를 호출하여 초기화를 진행한다.
3. 클라이언트한테 경로에 맞는 요청이 들어오면 doFilter() 실행
4. 서블릿 컨테이너 종료되면 destory() 실행
처음에 초기화를 진행하고
호출하면 doFilter 메서드를 실행해서 로그가 찍히는 걸 볼 수 있다.
이후 스프링을 종료시키면 destroy() 메서드가 호출된다.
파라미터를 보면 FilterChain을 받고 있다. 서블릿에 등록된 필터는 배열로 관리가 되고, pos라는 인덱스를 통해 필터를 찾아서 순서대로 실행시킨다.
서블릿에 현재 등록된 필터는 5개가 있고, 내가 커스텀으로 등록한 LogFilter도 3번 인덱스로 등록이 되어있다. 이렇게 순차대로 실행한 다음 doFilter를 호출해서 다음 인덱스를 부르게 되는것
스프링 인터셉터
이름에서 느껴지는 스프링 냄새. 말 그대로 스프링에서 사용되는 개념.
인터셉터 또한 인터페이스이며 인터페이스 기능을 오버라이딩해서 구현할 수 있다. 여기서는 HandlerInterceptor를 구현해서 사용한다.
(Adapter를 상속해서 구현하는 방법도 있으나 옛날 방식이다.)
인터셉터는 위 사진과 같이 DispatcherServlet과 Controller 사이에 위치해서 요청을 가로챈다.
즉 Controller의 전, 후의 작업을 처리하고 싶을 때 사용하는 방식.
인터셉터도 총 3가지 메서드를 구현해서 사용할 수 있다.
1. preHandle : 컨트롤러로 요청 보내기 전에 가로채서 로직 동작. boolean이 반환타입이며 true면 진행시키고 false면 요청을 반려시킨다.
2. postHandle : 컨트롤러 처리가 끝나고 응답을 가로채서 로직 동작. 컨트롤러에서 예외가 발생하면 실행되지 않는다.
3. afterCompletion : 뷰가 클라이언트한테 응답을 전송한 뒤 실행. 컨트롤러에서 예외가 발생해도 동작한다.
간단하게 특정 컨트롤러를 가로채는 인터셉터를 만들어보자.
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("{}로 요청 가로채기", request.getRequestURI());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("컨트롤러 응답 가로채기");
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
컨트롤러 응답을 가로채는 인터셉터를 만들었다. 여기서는 HandlerInterceptor를 구현하는 방법을 선택.
이제 어떤 컨트롤러에 오는 요청을 가로챌지 정해는 부분이 필요하다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.addPathPatterns("/hello");
}
}
그 작업은 WebConfig를 하나 만들고, WebMvcConfigurer를 구현해서 addInterceptors 메서드를 오버라이딩하면 특정 경로로 들어오는 요청에 대해 인터셉터를 실행하도록 설정할 수 있다.
(WebMvcConfigurer 인터페이스는 스프링 MVC의 기본 설정을 확장하거나 재정하는 데 사용한다. cors, 뷰리졸버, 인터셉터 등등... 설정이 가능해진다)
그중에 addInterceptors를 사용해서 개발자가 만든 커스텀 인터셉터를 추가해 준다. 또 허용 경로를 설정해 주었다.
이렇게 하고 http://localhost:8080/hello로 요청을 보내면 LogInterceptor가 동작하게 되고, preHandle 메서드가 실행돼서 로그 출력 -> true를 반환하니 컨트롤러로 넘겨준다.
컨트롤러가 리턴되면 LogInterceptor가 다시 가로채서 postHandle을 동작해서 응답에 대한 인터셉터를 처리한다.
최종 정리
필터 | 인터셉터 | |
스펙 | 서블릿 | 스프링 |
위치 | 디스패쳐 서블릿 전, 후 | 컨트롤러 전, 후 |
대상 | 모든 서블릿 요청 | 스프링 컨트롤러 요청 |
사용 목적 | 인증/권한 검사 요청 수정/응답 변경 로깅 ... |
인증/권한 검사 비즈니스 로직 실행 전, 후 작업 로깅 특정 핸들러만 필터링 ... |
제어 | 체인 방식(다음 필터 호출 여부 결정) | preHandle에서 boolean으로 컨트롤러 접근 제어 |
'CS지식' 카테고리의 다른 글
Tomcat은 정확히 어떤 역할을 하는 도구일까? (0) | 2024.05.12 |
---|---|
@Transactional 어노테이션의 역할 (0) | 2024.05.09 |
스프링 IOC와 DI를 어떻게 설명해야 할까? (0) | 2024.05.05 |
final 키워드를 사용하면 어떤 이점이 존재할까?(JAVA) (0) | 2024.04.29 |