文章目录
- 🍃前言
- 🎄@ControllerAdvice源码分析
- 🚩initHandlerAdapters(context)
- 🚩initHandlerExceptionResolvers(context)
- ⭕总结
🍃前言
在前面的项目开发中,我们使用了统一数据返回和统一异常的功能
它们都是基于 @ControllerAdvice 注解来实现的,接下来我们将通过分析
@ControllerAdvice 的源码,来了解他们的执行流程.
🎄@ControllerAdvice源码分析
首先我们点击@ControllerAdvice,查看其实现源码
Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {@AliasFor("basePackages")String[] value() default {};@AliasFor("value")String[] basePackages() default {};Class<?>[] basePackageClasses() default {};Class<?>[] assignableTypes() default {};Class<? extends Annotation>[] annotations() default {};
}
从上述源码可以看出 @ControllerAdvice 派⽣于@Component组件,这也就是为什么没有五⼤注解,ControllerAdvice 就⽣效的原因.
下⾯我们看看Spring是怎么实现着两项功能的,我们还是从 DispatcherServlet 的代码开始分析.
DispatcherServlet 对象在创建时会初始化⼀系列的对象
public class DispatcherServlet extends FrameworkServlet {//...@Overrideprotected void onRefresh(ApplicationContext context) {initStrategies(context);}/*** Initialize the strategy objects that this servlet uses.* <p>May be overridden in subclasses in order to initialize furtherstrategy objects.*/protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}
//...
}
对于 @ControllerAdvice 注解,我们重点关注initHandlerAdapters(context) 和initHandlerExceptionResolvers(context) 这两个⽅法.
🚩initHandlerAdapters(context)
initHandlerAdapters(context) 方法会取得所有实现了 HandlerAdapter 接⼝的bean并保存起来,其中有⼀个类型为 RequestMappingHandlerAdapter 的bean,这个bean就是@RequestMapping 注解能起作⽤的关键,这个bean在应⽤启动过程中会获取所有被@ControllerAdvice 注解标注的bean对象,并做进⼀步处理,关键代码如下
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapterimplements BeanFactoryAware, InitializingBean {
//.../*** 添加ControllerAdvice bean的处理*/private void initControllerAdviceCache() {if (getApplicationContext() == null) {return;}
//获取所有所有被 @ControllerAdvice 注解标注的bean对象List<ControllerAdviceBean> adviceBeans =ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type forControllerAdviceBean: " + adviceBean);}Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType,MODEL_ATTRIBUTE_METHODS);if (!attrMethods.isEmpty()) {this.modelAttributeAdviceCache.put(adviceBean, attrMethods);}Set<Method> binderMethods =MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);if (!binderMethods.isEmpty()) {this.initBinderAdviceCache.put(adviceBean, binderMethods);}if (RequestBodyAdvice.class.isAssignableFrom(beanType) ||ResponseBodyAdvice.class.isAssignableFrom(beanType)) {requestResponseBodyAdviceBeans.add(adviceBean);}}if (!requestResponseBodyAdviceBeans.isEmpty()) {this.requestResponseBodyAdvice.addAll(0,requestResponseBodyAdviceBeans);}if (logger.isDebugEnabled()) {int modelSize = this.modelAttributeAdviceCache.size();int binderSize = this.initBinderAdviceCache.size();int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount== 0) {logger.debug("ControllerAdvice beans: none");}else {logger.debug("ControllerAdvice beans: " + modelSize + "@ModelAttribute, " + binderSize +" @InitBinder, " + reqCount + " RequestBodyAdvice, " +resCount + " ResponseBodyAdvice");}}}
//...
}
这个方法在执行时会查找使用所有的 @ControllerAdvice 类,把 ResponseBodyAdvice 类放在容器中,当发⽣某个事件时,调用相应的Advice⽅法,就比如如返回数据前调用统⼀数据封装
🚩initHandlerExceptionResolvers(context)
DispatcherServlet 的 initHandlerExceptionResolvers(context) ⽅法,这个⽅法会取得所有实现了 HandlerExceptionResolver 接⼝的bean并保存起来,其中就有⼀个类型为 ExceptionHandlerExceptionResolver 的bean,这个bean在应⽤启动过程中会获取所有被 @ControllerAdvice 注解标注的bean对象做进⼀步处理,代码如下:
public class ExceptionHandlerExceptionResolver extendsAbstractHandlerMethodExceptionResolverimplements ApplicationContextAware, InitializingBean {//...private void initExceptionHandlerAdviceCache() {if (getApplicationContext() == null) {return;}
// 获取所有所有被 @ControllerAdvice 注解标注的bean对象List<ControllerAdviceBean> adviceBeans =ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type forControllerAdviceBean: " + adviceBean);}ExceptionHandlerMethodResolver resolver = newExceptionHandlerMethodResolver(beanType);if (resolver.hasExceptionMappings()) {this.exceptionHandlerAdviceCache.put(adviceBean, resolver);}if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {this.responseBodyAdvice.add(adviceBean);}}if (logger.isDebugEnabled()) {int handlerSize = this.exceptionHandlerAdviceCache.size();int adviceSize = this.responseBodyAdvice.size();if (handlerSize == 0 && adviceSize == 0) {logger.debug("ControllerAdvice beans: none");}else {logger.debug("ControllerAdvice beans: " +handlerSize + " @ExceptionHandler, " + adviceSize + "ResponseBodyAdvice");}}}
//...
}
当Controller抛出异常时, DispatcherServlet 通过ExceptionHandlerExceptionResolver 来解析异常,⽽ExceptionHandlerExceptionResolver 又通过ExceptionHandlerMethodResolver来解析异常,ExceptionHandlerMethodResolver最终解析异常找到适⽤的@ExceptionHandler标注的⽅法是这⾥,如下代码所示:
public class ExceptionHandlerMethodResolver {//...private Method getMappedMethod(Class<? extends Throwable> exceptionType) {List<Class<? extends Throwable>> matches = new ArrayList();//根据异常类型, 查找匹配的异常处理⽅法//⽐如NullPointerException会匹配两个异常处理⽅法://handler(Exception e) 和 handler(NullPointerException e)for (Class<? extends Throwable> mappedException :this.mappedMethods.keySet()) {if (mappedException.isAssignableFrom(exceptionType)) {matches.add(mappedException);}}//如果查找到多个匹配, 就进⾏排序, 找到最使⽤的⽅法. 排序的规则依据抛出异常相对于声明异常的深度//⽐如抛出的是NullPointerException(继承于RuntimeException,RuntimeException⼜继承于Exception)//相对于handler(NullPointerException e) 声明的NullPointerException深度为0,//相对于handler(Exception e) 声明的Exception 深度 为2//所以 handler(NullPointerException e)标注的⽅法会排在前⾯if (!matches.isEmpty()) {if (matches.size() > 1) {matches.sort(new ExceptionDepthComparator(exceptionType));}return this.mappedMethods.get(matches.get(0));}else {return NO_MATCHING_EXCEPTION_HANDLER_METHOD;}}
//...
}
⭕总结
关于《【JavaEE进阶】 @ControllerAdvice源码分析》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!