12-3. DispatcherServlet → ExceptionHandler: MethodArgumentNotValidException 처리
검증 오류가 발생하면 MethodArgumentNotValidException이 던져지고, DispatcherServlet은 일반 컨트롤러 흐름 대신 예외 처리 체인으로 분기합니다.
시퀀스 다이어그램
sequenceDiagram
participant DS as DispatcherServlet
participant ResolverChain as HandlerExceptionResolver[]
participant EHER as ExceptionHandlerExceptionResolver
participant EH as @ExceptionHandler / @ControllerAdvice
participant Resp as Response
DS->>ResolverChain: processHandlerException(request, response, handler, ex)
ResolverChain->>EHER: resolveException(...)
EHER->>EHER: getExceptionHandlerMethod(...)
alt 매칭되는 @ExceptionHandler 존재
EHER->>EH: invokeAndHandle(...)
EH-->>EHER: ModelAndView / ResponseEntity
EHER-->>DS: 예외 처리 결과 반환
else 매칭 없음
EHER-->>ResolverChain: null
ResolverChain->>ResolverChain: 다음 Resolver 시도
end
DS-->>Resp: 400 응답 또는 에러 응답 렌더링
DispatcherServlet의 예외 처리 분기
// DispatcherServlet.java
protected @Nullable ModelAndView processHandlerException(
HttpServletRequest request,
HttpServletResponse response,
@Nullable Object handler,
Exception ex) throws Exception {
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break; // 첫 매칭 resolver 채택
}
}
}
if (exMv != null) {
return exMv;
}
throw ex;
}
즉, Step 9의 HandlerMapping과 유사하게 예외 처리도 “리졸버 체인”에서 first-match-wins 방식으로 동작합니다.
ExceptionHandlerExceptionResolver의 핵심
// ExceptionHandlerExceptionResolver.java
protected @Nullable ModelAndView doResolveHandlerMethodException(..., Exception exception) {
ServletInvocableHandlerMethod exceptionHandlerMethod =
getExceptionHandlerMethod(handlerMethod, exception, webRequest);
if (exceptionHandlerMethod == null) {
return null;
}
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
...
}
getExceptionHandlerMethod는 다음 순서로 탐색합니다.
- 해당 컨트롤러 내부의 @ExceptionHandler
- @ControllerAdvice에 등록된 전역 @ExceptionHandler
MethodArgumentNotValidException의 일반적인 결과
- 기본 리졸버 또는 @ExceptionHandler가 예외를 처리
- 보통 HTTP 400(Bad Request) 응답
- 응답 본문에는 필드 오류 목록을 담도록 커스터마이징 가능
탐구 질문
- Validation 예외를 컨트롤러별 @ExceptionHandler로 나누는 방식과 전역 @ControllerAdvice 방식 중, 지금 프로젝트에 더 맞는 방식은 무엇일까요?
- 오류 응답 포맷을 고정하려면 어떤 계층에서 표준화하는 것이 가장 안정적일까요?
- 예외가 처리된 경우 인터셉터의 postHandle, afterCompletion 호출 순서는 어떻게 될까요?