ControllerAdvice 是如何起作用的
0. 背景
在controller 暴露的各个接口下通常会抛出各种异常,我们希望所有的异常处理都可以收口到一处。而ControllerAdvice 这个annotation标签就是起这种作用的。那为什么只要在一个类上打上这个标签就会起作用呢。
1. GlobalExceptionHandler示例
1 @ControllerAdvice 2 public class GlobalExceptionHandler { 3 4 @ResponseBody 5 @ExceptionHandler(MyExceptionA.class) 6 public MyResponse<Object> onMyExceptionA(HttpServletRequest req, MyExceptionA e) { 7 return MyResponse.of(e.getData(), e.getResultCode(), e.getMessage()); 8 } 9 10 @ResponseBody 11 @ExceptionHandler(MyExceptionB.class) 12 public MyResponse<Object> onMyExceptionB(HttpServletRequest req, MyExceptionB e) { 13 return MyResponse.of(null, e.getErrorCode(), e.getMessage()); 14 } 15 16 }
2. 顺藤摸瓜
我们顺着ControllerAdvice这个类的使用来自底向上挖掘它是如何发生作用的。
首先我们看ExceptionHandler这个注解类是在哪里处理的,
我们看到了在spring.web包下的这个类 org.springframework.web.method.annotation.ExceptionHandlerMethodResolver,进一步看下它的构造函数。
1 /** 2 * A constructor that finds {@link ExceptionHandler} methods in the given type. 3 * @param handlerType the type to introspect 4 */ 5 public ExceptionHandlerMethodResolver(Class<?> handlerType) { 6 for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) { 7 for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) { 8 addExceptionMapping(exceptionType, method); 9 } 10 } 11 } 12 13 /** 14 * A filter for selecting {@code @ExceptionHandler} methods. 15 */ 16 public static final MethodFilter EXCEPTION_HANDLER_METHODS = new MethodFilter() { 17 @Override 18 public boolean matches(Method method) { 19 return (AnnotationUtils.findAnnotation(method, ExceptionHandler.class) != null); 20 } 21 };
从构造函数可以看出是从一个handlerType的类中取出所有被ExceptionHanlder标注的方法,存储一个<Exception, Method>的方法映射。
其次我们看ExceptionHandlerMethodResolver这个类的构造函数是在哪里使用的,
在sping.webmvc包下这个类org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver的初始化动作里,我们看到ExceptionHandlerMethodResolver构造函数的入参正是ControllerAdviceBean的BeanType, 也就是前面我们提到的GlobalExceptionHandler示例。
1 private void initExceptionHandlerAdviceCache() { 2 if (getApplicationContext() == null) { 3 return; 4 } 5 if (logger.isDebugEnabled()) { 6 logger.debug("Looking for exception mappings: " + getApplicationContext()); 7 } 8 9 List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); 10 AnnotationAwareOrderComparator.sort(adviceBeans); 11 12 for (ControllerAdviceBean adviceBean : adviceBeans) { 13 ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType()); 14 if (resolver.hasExceptionMappings()) { 15 this.exceptionHandlerAdviceCache.put(adviceBean, resolver); 16 if (logger.isInfoEnabled()) { 17 logger.info("Detected @ExceptionHandler methods in " + adviceBean); 18 } 19 } 20 if (ResponseBodyAdvice.class.isAssignableFrom(adviceBean.getBeanType())) { 21 this.responseBodyAdvice.add(adviceBean); 22 if (logger.isInfoEnabled()) { 23 logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean); 24 } 25 } 26 } 27 }
3. 完整链条
从应用的GlobalExceptionHandler叶子节点一直向上追溯我们追查到ExceptionHandlerExceptionResolver这个类,那完成的链路是啥呢?
从入口 DispatcherServlet#doDispatch() -> DispatcherServlet#processDispatchResult() -> DispatcherServlet#processHandlerException() -> HandlerExceptionResolver#resolveException() -> AbstractHandlerExceptionResolver#doResolveException() -> AbstractHandlerMethodExceptionResolver#doResolveHandlerMethodException() -> ExceptionHandlerExceptionResolver#doResolveHandlerMethodException()
-> ExceptionHandlerMethodResolver#getMappedMethod().invoke()
以上。