결론부터 말하자면 DispatcherServlet 덕분에 가능하다.
DispatcherServlet의 동작 방식을 확장하여 만들어진 여러 가지 전략 중 예외 처리 전략도 있다.
- HandlerMapping(URL과 요청정보를 기준으로 어떤 컨트롤러를 사용할 건지 결정)
- HandlerAdapter(HandlerMapping 전략에 따라 선택한 컨트롤러가 어떤 타입이든 DispatcherServlet가 사용할 수 있게 함)
- ViewResolver(컨트롤러가 리턴한 뷰 이름을 참고해서 적절한 뷰 오브젝트를 찾아줌)
- ...
- HandlerExceptionResolver(예외 처리)
예외가 발생했을 때 종류에 따라 클라이언트에게 알려주는 작업은 DispatcherServlet을 통해 처리되어야 하기 때문이다.
@Controller
public class HelloCon {
@RequestMapping("/hello")
public void hello() {
if (1==1) throw new DataRetrievalFailureException("hi");
}
// *DataRetrievalFailureException는 DataAccessException의 자식 클래스
@ExceptionHandler(DataAccessException.class)
public ModelAndView dataAccessExceptionHandler(DataAccessException ex) {
return new ModelAndView("dataexception").addObject("msg", ex.getMessage());
}
}
DispatcherServlet에서 예외 처리까지
public class DispatcherServlet extends FrameworkServlet {
...
protected void doDispatch(HttpServletRequest request ...) {
...
try {
try {
Exception dispatchException = null;
try {
...
// 컨트롤러 로직 실행
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
...
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
// 1. 디스패치 결과 처리
// 위에서 잡은 dispatchException 넘김
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
...
}
// 1. 디스패치 결과 처리
private void processDispatchResult(HttpServletRequest request ...) {
...
// 2. 핸들러 예외 처리
mv = this.processHandlerException(request, response, handler, exception);
...
}
}
...
}
// 2. 핸들러 예외 처리
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request ...) {
...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
Iterator var6 = this.handlerExceptionResolvers.iterator();
// 전략마다 예외 처리
while(var6.hasNext()) {
// 3. HandlerExceptionResolver 전략의 resolveException 메서드 실행
HandlerExceptionResolver resolver = (HandlerExceptionResolver)var6.next();
exMv = resolver.resolveException(request, response, handler, ex);
...
}
}
...
}
...
// 3. HandlerExceptionResolver 전략의 resolveException 메서드 실행
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
public ModelAndView resolveException(HttpServletRequest request ...) {
...
ModelAndView result = this.doResolveException(request, response, handler, ex);
}
}
HandlerExceptionResolver
HandlerExceptionResolver 인터페이스
HandlerExceptionResolver 인터페이스의 구현체들
AbstractHandlerExceptionResolver를 상속받은 네 개 중 세 개가 디폴트 전략이다.
- ExceptionHandlerExceptionResolver (AbstractHandlerMethodExceptionResolver 상속받음)
- ResponseStatusExceptionResolver
- DefaultHandlerExceptionResolver
ExceptionHandlerExceptionResolver가 바로 @ExceptionHandler를 사용하기 위한 클래스다.
AbstractHandlerMethodExceptionResolver 추상 클래스
아까 doDispatch에서부터 이어지던 doResolveException 메서드가 있다.
doResolveException는 doResolveHandlerMethodException 추상 메서드를 호출한다.
ExceptionHandlerExceptionResolver
ExceptionHandlerExceptionResolver(Spring 3.2 이전엔 AnnotationMethodHandlerExceptionResolver)는 @ExceptionHandler 어노테이션으로 메서드를 통해 예외를 해결하는 전략이다.
public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver implements ApplicationContextAware, InitializingBean {
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request ...) {
// 4. 예외에 적절한 메서드 찾기
ServletInvocableHandlerMethod exceptionHandlerMethod = this.getExceptionHandlerMethod(handlerMethod, exception);
...
ArrayList<Throwable> exceptions = new ArrayList();
try {
...
Throwable cause;
for(Throwable exToExpose = exception; exToExpose != null; exToExpose = cause != exToExpose ? cause : null) {
exceptions.add(exToExpose);
cause = ((Throwable)exToExpose).getCause();
}
Object[] arguments = new Object[exceptions.size() + 1];
exceptions.toArray(arguments);
arguments[arguments.length - 1] = handlerMethod;
// 5. 예외 메서드 호출 위임
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
...
}
}
// 4. 적절한 예외 메서드 찾기
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(@Nullable HandlerMethod handlerMethod, Exception exception) {
Class<?> handlerType = null;
if (handlerMethod != null) {
handlerType = handlerMethod.getBeanType();
// ExceptionHandlerExceptionResolver가 아닌 별도의 ExceptionHandlerMethodResolver 클래스
ExceptionHandlerMethodResolver resolver = (ExceptionHandlerMethodResolver)this.exceptionHandlerCache.get(handlerType);
if (resolver == null) {
resolver = new ExceptionHandlerMethodResolver(handlerType);
this.exceptionHandlerCache.put(handlerType, resolver);
}
// 예외에 매핑된 메서드 찾아서 반환
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method, this.applicationContext);
}
}
// @ControllerAdvice 사용 시
Iterator var9 = this.exceptionHandlerAdviceCache.entrySet().iterator();
while(var9.hasNext()) {
Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry = (Map.Entry)var9.next();
ControllerAdviceBean advice = (ControllerAdviceBean)entry.getKey();
if (advice.isApplicableToBeanType(handlerType)) {
ExceptionHandlerMethodResolver resolver = (ExceptionHandlerMethodResolver)entry.getValue();
Method method = resolver.resolveMethod(exception);
if (method != null) {
return new ServletInvocableHandlerMethod(advice.resolveBean(), method, this.applicationContext);
}
}
}
return null;
}
참고
<토비의 스프링 3.1>
'Study > Java' 카테고리의 다른 글
Custom Exception: 검사 예외, 런타임 예외 무엇을 써야할까 (0) | 2023.07.21 |
---|---|
자바의 함수형 프로그래밍 전략, 메서드 참조 (0) | 2023.05.04 |
[Java] 제네릭 (0) | 2023.02.01 |
JVM 명세 - Run-Time Data Areas (0) | 2022.06.25 |
자바로 간단한 http 웹 서버 구현 (0) | 2022.06.11 |