0. 예외 발생과 오류페이지 요청 흐름
컨트롤러 (예외 발생!) -> 인터셉터 -> 서블릿 -> 필터 -> WAS ->
WAS가 '/error-page/500' 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(errorPage/500) -> View를 통해 페이지 호출
예외가 발생하면 오류 페이지를 출력하기 위해 WAS 내부에서 호출이 다시 한 번 발생된다.
이때 서버 내부에서 필터나 인터셉터가 한 번 더 호출되는 것은 비효율적이다.
그래서 결국 클라이언트로부터 발생한 정상적인 요청인지, 아니면 오류 페이지를 출력하기 위한 내부 요청인지 구분하기 위하여 DispatcherType이라는 추가 정보를 제공한다.
필터는 이런 경우를 위해 DispatcherType이라는 옵션을 제공하는데, enum 형태로 지정되어 있다.
🥽DispatcherType
- REQUEST : 클라이언트에서 온 요청
- ERROR : 에러에 해당하는 요청
- FORWARD : 서블릿에서 다른 서블릿이나 JSP를 호출할 때
- INCLUDE : 서블릿에서 다른 서블릿이나 JSP의 결과를 포함할 때
- ASYNC : 서블릿 비동기 호출
@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,
request.getDispatcherType(), requestURI);
chain.doFilter(request, response);
} catch (Exception e) {
throw e;
} finally {
log.info("RESPONSE [{}][{}][{}]", uuid,
request.getDispatcherType(), requestURI);
}
}
@Override
public void destroy() {
log.info("log filter destroy");
}
}
>```
#### WebConfig
```java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean logFilter() {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new LogFilter());
filterRegistrationBean.setOrder(1);
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST
, DispatcherType.ERROR
);
return filterRegistrationBean;
}
}
filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
위의 두 가지를 모두 넣으면 클라이언트 요청은 물론이고 오류 페이지 요청에서도 필터가 호출된다. 아무 것도 넣지 않으면 디폴트 값인 DispatcherType.REQUEST가 호출된다. 물론 오류 페이지 요청 전용 필터를 적용하고 싶다면 DispatcherType.ERROR만 지정하면 된다.
@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);
log.info("REQUEST [{}][{}][{}][{}]", uuid, request.getDispatcherType(), requestURI, handler);
return true;
}
@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, request.getDispatcherType(),
requestURI);
if (ex != null) {
log.error("afterCompletion error!!", ex);
}
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns(
"/css/**", "/*.ico"
, "/error", "/error-page/**" //오류 페이지 경로
);
}
앞선 필터의 경우에는 필터를 등록할 때 어떤 DispatcherType에 따라 필터를 적용할지 선택할 수 있었지만 인터셉터는 스프링에서 제공하는 기능이므로 DispatcherType과 무관하게 항상 호출된다. 대신 인터셉터는 오류 페이지를 따로 빼줄 수 있다.
Link