두 가지의 예외 상황을 통해 인터셉터의 afterCompletion 메서드와 ExceptionResolver 작동여부 확인해보겠습니다.
상황 1. 컨트롤러에서 RuntimeException이 발생
@RequestMapping(value = "/error-test", method = RequestMethod.GET)
public void errorTest(HttpServletRequest req) {
throw new NullPointerException("NPE 발생"); // RuntimeException을 상속받은 Exception
}
1번 상황에서는 ExceptionResolver가 작동하고 afterCompletion에는 Exception 객체에 null이 반환됩니다.
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("ex : " + ex); // ex : null
}
afterCompletion 메서드에서 null이 호출되는 이유는 DispatcherServlet 요놈 때문입니다.
// DispatcherServlet의 코드 일부
try {
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
} catch (Exception ex) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
DispatcherServlet은 HandlerExceptionResolver가 정의되어 있는 경우 Exception 객체에 null을 세팅하여 던집니다.
상황 2. 컨트롤러에서는 예외가 발생하지 않았지만 view를 렌더링하는 과정에서 Exception 발생
2번 상황에서는 HandlerExceptionResolver가 작동하지 않아 DispatcherServlet이 afterCompletion 객체에 null이 아닌 Exception 객체를 넣어줍니다.
주의해야 할 점은 afterCompletion 메서드를 호출을 안한다는 것이 아니라 호출은 하지만 Exception 객체에 null이 넘어온다는 의미입니다.
이렇게 작동하는 이유는 HandlerExceptionResolver는 컨트롤러에서 발생하는 예외만 처리하는 역할을 하기 때문에 뷰를 처리하는 과정에서 발생하는 예외를 처리하지 못하기 때문이라 생각합니다.
이로 인해 DispatcherServlet도 HandlerExceptionResolver가 처리를 하지 못한 상태이므로 afterCompletion에서라도 처리를 하라고 null이 아닌 Exception 객체를 넣어주는 것 같습니다.
2번에 대해서는 제 개인적인 의견이 들어가 있는데 잘못된 부분이라면 댓글 부탁드리겠습니다.