[Spring] 예외 발생에 따른 필터와 인터셉터에서 처리하기 (feat. DispatcherType)

DyungE_100·2022년 6월 3일
0

Spring

목록 보기
7/7

0. 예외 발생과 오류페이지 요청 흐름

컨트롤러 (예외 발생!) -> 인터셉터 -> 서블릿 -> 필터 -> WAS ->
WAS가 '/error-page/500' 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(errorPage/500) -> View를 통해 페이지 호출

예외가 발생하면 오류 페이지를 출력하기 위해 WAS 내부에서 호출이 다시 한 번 발생된다.
이때 서버 내부에서 필터나 인터셉터가 한 번 더 호출되는 것은 비효율적이다.

그래서 결국 클라이언트로부터 발생한 정상적인 요청인지, 아니면 오류 페이지를 출력하기 위한 내부 요청인지 구분하기 위하여 DispatcherType이라는 추가 정보를 제공한다.

필터는 이런 경우를 위해 DispatcherType이라는 옵션을 제공하는데, enum 형태로 지정되어 있다.

🥽DispatcherType

  • REQUEST : 클라이언트에서 온 요청
  • ERROR : 에러에 해당하는 요청
  • FORWARD : 서블릿에서 다른 서블릿이나 JSP를 호출할 때
  • INCLUDE : 서블릿에서 다른 서블릿이나 JSP의 결과를 포함할 때
  • ASYNC : 서블릿 비동기 호출



LogFilter - DispatcherType 로그를 추가한 예시

@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과 무관하게 항상 호출된다. 대신 인터셉터는 오류 페이지를 따로 빼줄 수 있다.




https://develop-writing.tistory.com/97

0개의 댓글