본 포스트는 김영한 님의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의를 토대로 작성하였습니다.
서블릿 필터란 스프링이 아닌 자바 서블릿에서 제공하는 기능으로 클라이언트 요청이 들어오면 가장 최전방에서 응답을 필터링 하는 곳이다.
필터는 체인처럼 연결되어 있으며, 해당 필터를 통과하면 다음 필터를 호출하는 식으로 이루어져 있다.
개발자는 Filter 인터페이스를 상속받아 원하는 필터를 만들 수 있으며 이를 원하는 곳에 등록할 수 있다.
Filter 인터페이스
public interface Filter {
public default void init(FilterConfig filterConfig) throws ServletException{
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
public default void destroy() {
}
}
세 개의 함수를 오버라이딩 하면 되는데,
다음 필터를 호출하기 위해서는 doFilter의 인자로 들어오는 chain을 이용하여
chain.doFilter(request, response)를 호출하면 된다.
package hello.login.web.filter;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.UUID;
@Slf4j
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("log filter init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String requestURI = httpRequest.getRequestURI();
String uuid = UUID.randomUUID().toString();
try {
log.info("REQUEST [{}][{}]", uuid, requestURI);
chain.doFilter(request, response);
} catch (Exception e) {
throw e;
} finally {
log.info("RESPONSE [{}][{}]", uuid, requestURI);
}
}
@Override
public void destroy() {
log.info("log filter destroy");
}
}
다음 예시처럼 Filter를 상속받아 구현하면 된다.
이렇게 구현하고 나면 필터를 등록해야 한다.
필터를 등록하는 건 스프링 부트를 이용하도록 하자.
//WebConfig 클래스
package hello.login;
import hello.login.web.filter.LogFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new
FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
return filterRegistrationBean;
}
}
환경설정을 할 수 있도록 @Configuration을 붙여주고 해당 필터를 등록하면 된다.
setOrder는 필터 체인 순서이다. 숫자가 낮을수록 먼저 동작한다.
addUrlPatterns는 필터를 적용할 url 패턴을 지정한다. 한 번에 여러 패턴을 지정할 수도 있다.
서블릿 필터가 가장 전방에서 응답을 처리한다면 스프링 인터셉터는 Dispatcher Servlet에서 컨트롤러를 호출하는 과정에서 작동한다.
작동 흐름은
다음과 같은데, 중간에 preHandle, postHandle, afterCompletion 등의 메소드들이 실행된다.
@Slf4j
public class LogInterceptor implements HandlerInterceptor {
public static final String LOG_ID = "logId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse
response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
String uuid = UUID.randomUUID().toString();
request.setAttribute(LOG_ID, uuid);
//@RequestMapping: HandlerMethod
//정적 리소스: ResourceHttpRequestHandler
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler; //호출할 컨트롤러 메서드의 모든 정보가 포함되어 있다.
}
log.info("REQUEST [{}][{}][{}]", uuid, requestURI, handler);
return true; //false 진행X }
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse
response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle [{}]", modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse
response, Object handler, Exception ex) throws Exception {
String requestURI = request.getRequestURI();
String logId = (String)request.getAttribute(LOG_ID);
log.info("RESPONSE [{}][{}]", logId, requestURI);
if (ex != null) {
log.error("afterCompletion error!!", ex);
}
}
}
각 함수마다 request, response 외에 인자로 하나씩 더 들어오는데, 실행 시점에 따라 갖고 있을 수 있는 정보들이다.
preHandle의 경우 handler 정보가 들어오는데 보통 @Controller, @RequestMapping 을 사용하면 HandlerMethod가 넘어온다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*.ico", "/error");
}
//...
}
이렇게 만든 인터셉터를 등록하려면 WebMvcConfigurer가 제공하는 addInterceptors를 활용하면 된다.