【JavaEE进阶】 @ControllerAdvice源码分析

简介: 【JavaEE进阶】 @ControllerAdvice源码分析

🍃前言

在前面的项目开发中,我们使用了统一数据返回和统一异常的功能

它们都是基于 @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 {
    //...
    @Override
    protected void onRefresh(ApplicationContext context) {
        initStrategies(context);
    }
    /**
     * Initialize the strategy objects that this servlet uses.
     * <p>May be overridden in subclasses in order to initialize further
     strategy 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 AbstractHandlerMethodAdapter
        implements 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 for
                        ControllerAdviceBean: " + 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 extends
        AbstractHandlerMethodExceptionResolver
        implements 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 for
                                        ControllerAdviceBean: " + adviceBean);
                        }
                        ExceptionHandlerMethodResolver resolver = new
                                ExceptionHandlerMethodResolver(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源码分析》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

相关文章
|
5月前
|
SQL Java 关系型数据库
【JavaEE进阶】 @Transactional详解
【JavaEE进阶】 @Transactional详解
【java常见的面试题】讲一讲SpringBoot自动装配的原理
Java基础的面试题【SpringBoot&SpringCloud篇】
111 0
|
6月前
|
前端开发 Java Spring
【JavaEE进阶】 @RequestMapping注解
【JavaEE进阶】 @RequestMapping注解
|
6月前
|
XML Java 数据格式
JAVAEE框架之Spring注解
JAVAEE框架之Spring注解
61 0
|
JSON 前端开发 Java
|
XML Java 数据库连接
【Spring学习笔记 五】Spring注解及Java类配置开发
【Spring学习笔记 五】Spring注解及Java类配置开发
94 0
|
XML 缓存 安全
java面试题(十七)spring
2.1 请你说说Spring的核心是什么 参考答案 Spring框架包含众多模块,如Core、Testing、Data Access、Web Servlet等,其中Core是整个Spring框架的核心模块。Core模块提供了IoC容器、AOP功能、数据绑定、类型转换等一系列的基础功能,而这些功能以及其他模块的功能都是建立在IoC和AOP之上的,所以IoC和AOP是Spring框架的核心。 IoC(Inversion of Control)是控制反转的意思,这是一种面向对象编程的设计思想。在不采用这种思想的情况下,我们需要自己维护对象与对象之间的依赖关系,很容易造成对象之间的耦合度过高,在一个大
121 0
|
Java Spring 容器
Java学习路线-56:Spring与Ioc
Java学习路线-56:Spring与Ioc
80 0
|
Java Spring 自动驾驶
java面试题:在Spring中如何注入一个java集合?
java面试题:在Spring中如何注入一个java集合?
java面试题:在Spring中如何注入一个java集合?