본문 바로가기

Spring/Spring

[Spring] 서블릿 예외 처리 - 필터

반응형

이전 글에서 서블릿을 통한 예외 처리에 대해 알아보았고 이번 글에서는 아래의 과정에서 필터가 중복 호출되는 것을 막기 위한 방법에 대해 알아보겠습니다.

1. WAS - 필터 - 서블릿 - 인터셉터 - 컨트롤러  // 컨트롤러에서 예외가 발생하면?

2. 컨트롤러 - 인터셉터 - 서블릿 - 필터 - WAS  //  컨트롤러는 예외를 WAS까지 전달한다.

3. WAS - 필터 - 서블릿 - 인터셉터 - 컨트롤러  //  WAS는 예외를 확인하고 에러 페이지를 호출하기 위해 다시 컨트롤러를 호출

(여기서 중복 호출은 1번 3번 과정으로 필터의 기능을 하도록 하는 메서드를 호출하는 것을 말합니다.)

 

우선 코드와 실행 결과를 먼저 보여드리고 상세한 설명을 덧붙이겠습니다.

1. 예외 컨트롤러

public class ServletExController {

    @GetMapping("/error-ex")
    public void errorEx() {
        throw new RuntimeException("예외 발생!");
    }

    @GetMapping("/error-404")
    public void error404 (HttpServletResponse response)throws IOException {
        response.sendError(404, "404 오류");

    }

    @GetMapping("/error-500")
    public void error500 (HttpServletResponse response) throws IOException {
        response.sendError(500);
    }
}

 

2. 로그 필터

@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);
        }
    }

 

3. 에러 페이지

    @RequestMapping("/error-page/404")
    public String errorPage404 (HttpServletRequest request, HttpServletResponse response) {
        log.info("errorPage 404");
        printErrorInfo(request);
        return "error-page/404";
    }

    @RequestMapping("/error-page/500")
    public String errorPage500 (HttpServletRequest request, HttpServletResponse response) {
        log.info("errorPage 500");
        printErrorInfo(request);
        return "error-page/500";
    }

 

실행 결과

 INFO 13228 --- [exception] [nio-8080-exec-5] hello.exception.filter.LogFilter         : REQUEST  [aa68afcf-1be5-434d-b635-fd2994e7841f][REQUEST][/error-500]
 INFO 13228 --- [exception] [nio-8080-exec-5] hello.exception.filter.LogFilter         : RESPONSE [aa68afcf-1be5-434d-b635-fd2994e7841f][REQUEST][/error-500]
 INFO 13228 --- [exception] [nio-8080-exec-5] hello.exception.filter.LogFilter         : REQUEST  [11a9513b-6f6d-4827-8d52-6e01169f596c][ERROR][/error-page/500]
 INFO 13228 --- [exception] [nio-8080-exec-5] h.exception.servlet.ErrorPageController  : errorPage 500
 INFO 13228 --- [exception] [nio-8080-exec-5] h.exception.servlet.ErrorPageController  : dispatchType=ERROR
 INFO 13228 --- [exception] [nio-8080-exec-5] hello.exception.filter.LogFilter         : RESPONSE [11a9513b-6f6d-4827-8d52-6e01169f596c][ERROR][/error-page/500]

 

필터, 인터셉터의 흐름에 대해 간략히 말하자면

HTTP 요청 - WAS - 필터 - 서블릿 - 인터셉터 - 컨트롤러

이렇게 흘러갑니다.

1. HTTP 요청이 오면 WAS를 거쳐서 필터에 도착한다. 필터는 REQUEST 로그를 찍고 chain.doFilter로 인하여 다음 단계로 넘어간다.

2.요청은 서블릿, 인터셉터를 거쳐서 ServletExController 에 도착을 하고  컨트롤러는 URL이 error-500이기 때문에 response에  500 에러를 담아서 다시 WAS까지 거꾸로 보낸다.

3. RESPONSE를 전달 받은 WAS는 에러 페이지를 호출하기 위해 다시 Controller로 REQUEST를 보낸다. (이때 첫번째 REQUEST와는 달리 request.getDispatcherType이 [REQUEST]에서 [ERROR]로 바뀐 것을 볼 수 있다.)

4. 에러 페이지 컨트롤러는 500 에러 페이지를 호출하게 된다.

 

위 과정에서 필터는 2번 호출된 것을 볼 수 있다.

여기서 필터를 1번 호출하게 하려면 Config 클래스에 있는

 @Bean
    public FilterRegistrationBean logFilter() {
        FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();

        filterRegistrationBean.setFilter(new LogFilter());
        filterRegistrationBean.setOrder(1);
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST);
        return filterRegistrationBean;
    }

setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR) 부분을

setDispatcherTypes(DispatcherType.REQUEST)로 바꾸면 된다.

  INFO 30288 --- [exception] [nio-8080-exec-2] hello.exception.filter.LogFilter         : REQUEST  [dc9906c6-39d4-4265-ba1e-1e7030ca38dc][REQUEST][/error-500]
  INFO 30288 --- [exception] [nio-8080-exec-2] hello.exception.filter.LogFilter         : RESPONSE [dc9906c6-39d4-4265-ba1e-1e7030ca38dc][REQUEST][/error-500]
  INFO 30288 --- [exception] [nio-8080-exec-2] h.exception.servlet.ErrorPageController  : errorPage 500
  INFO 30288 --- [exception] [nio-8080-exec-2] h.exception.servlet.ErrorPageController  : dispatchType=ERROR

그러면 위의 실행 결과와 비교했을 때 REQUEST가 ERROR로 요청되는 부분이 없어진 것을 볼 수 있다.

즉, 맨 처음 HTTP 요청에서는 정상 요청이 실행되지만 컨트롤러에서 에러가 터지고 다시 WAS로 내려간 뒤 오류 페이지로 향하는 과정에서는 필터가 동작하지 않는 것이다.

 

 

출처 : 인프런 - 스프링 MVC 2편 (김영한)

반응형