예외 - 필터, 인터셉터

바그다드·2023년 5월 23일
0

예외

목록 보기
2/9

앞서 예외와 그 흐름에 대해서 알아보았다. 그 흐름은 아래와 같다.

1. WAS(여기까지 전파) <- 필터 <- 서블릿 <- 인터셉터 <- 컨트롤러(예외발생)
2. WAS `/error-page/500` 다시 요청 -> 필터 -> 서블릿 -> 인터셉터 -> 컨트롤러(/errorpage/
500) -> View
  • 위와 같은 흐름으로 예외가 발생하면, WAS에서 에러 페이지를 다시 요청하는데 이 과정에서 필터, 인터셉터, 컨트롤러 등이 다시 호출이 된다.
    그런데 로그인 체크의 경우를 생각해보면 이미 로그인 체크를 끝냈는데, 에러 페이지를 호출하면서 또 다시 해당 필터나 인터셉터가 호출되는 것은 비효율적이다.
    따라서 클라이언트로부터 발생한 요청이 정상적인 요청인지, 에러 페이지 호출을 위한 요청인지 구분이 필요하다.
    이런 문제를 위해 DispatcherType이라는 추가 정보가 제공된다.

DispatcherType

package org.springframework.boot.web.servlet;

/**
 * Enumeration of filter dispatcher types, identical to
 * {@link javax.servlet.DispatcherType} and used in configuration as the servlet API may
 * not be present.
 *
 * @author Stephane Nicoll
 * @since 2.0.0
 */
public enum DispatcherType {

	/**
	 * Apply the filter on "RequestDispatcher.forward()" calls.
	 */
	FORWARD,

	/**
	 * Apply the filter on "RequestDispatcher.include()" calls.
	 */
	INCLUDE,

	/**
	 * Apply the filter on ordinary client calls.
	 */
	REQUEST,

	/**
	 * Apply the filter under calls dispatched from an AsyncContext.
	 */
	ASYNC,

	/**
	 * Apply the filter when an error is handled.
	 */
	ERROR

}
  • DispatcherType은 enum으로 구성이 되어 있다.
  1. Request : 클라이언트 요청
  2. Error : 오류 요청
  3. Forward : 서블릿이나 다른 서블릿이나 JSP를 호출할 때,
    RequestDispatcher.forward(request, response);
  4. INCLUDE : 서블릿에서 다른 서블릿이나 jsp를 포함할 때
  5. ASYNC : 서블릿 비동기 호출할 때
  • 그럼 DispatcherType을 활용해보자

1. 필터

log필터 추가

@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) {
            log.info("EXCEPTION {}", e.getMessage());
            throw e;
        }finally{
            log.info("RESPONSE [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
        }
    }

    @Override
    public void destroy() {
        log.info("log filter destroy");

    }
}
  • log.info("REQUEST [{}][{}][{}]", uuid, request.getDispatcherType(), requestURI);
    여기서 두 번째 파라미터로 DispatcherType을 넘겨준 것을 기억해두자.

필터 등록

@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에 따라 필터를 적용할지 설정하는 부분으로, default값은 요청이다.
    따라서 이 코드에서는 DispatcherType을 REQUEST와 ERROR로 설정해줬으므로 에러 페이지 요청 시에 필터가 적용되는데, 따로 설정해주지 않으면 클라이언트의 요청(DispatcherType.REQUEST)이 있을 때만 필터가 적용한다.

2. 인터셉터

로그 인터셉터 생성

@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("postHandler [{}]", 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);

        }
    }
}

로그 인터셉터 등록

  • 기존의 WebConfig에 인터셉터를 등록해주자
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LogInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/css/**","*.ico","/error", "/error-page/**"); // 오류 페이지 경로
    }
    // 기존 필터는 등록해제 
    //    @Bean
    public FilterRegistrationBean logFilter() {

  • 로그를 확인해보면 처음 요청이 들어왔을 때는 인터셉터가 정상적으로 호출되지만, WAS에서 예외를 표시하고 다시 에러페이지 요청을 보낼 때는 인터셉터가 호출되지 않고 컨트롤러가 바로 호출되는 것을 확인할 수 있다.
  • 또한 컨트롤러에서 예외가 발생하여 인터셉터에서 postInterceptor는 수행되지 않고, afterCompletion만 수행된 것을 확인할 수 있다.

이것으로 필터와 인터셉터를 적용한 예외 처리를 해보았는데, 예외 종류에 따라 각 에러 페이지를 등록하고, 컨트롤러를 만드는 등의 복잡한 과정이 필요했다. 다음 포스팅에서는 스프링 부트를 활용해서 이런 과정이 어떻게 생략되는지 확인해보자.

출처 : 김영한 스프링MVC2편

profile
꾸준히 하자!

0개의 댓글