ControllerAdvice 是如何起作用的

Posted sinsonglew

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了 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()

 

 

以上。

 

以上是关于 ControllerAdvice 是如何起作用的的主要内容,如果未能解决你的问题,请参考以下文章

Spring @ControllerAdvice/@ExceptionHandler 不起作用

spring的@ControllerAdvice注解

Spring Boot2 系列教程 (十四) | 统一异常处理

没有控制器时的 Spring Boot 异常处理

@ControllerAdvice + @ExceptionHandler 全局处理 Controller 层异常

NoHandlerFoundException 的自定义异常处理程序在没有 @EnableWebMvc 的情况下不起作用