【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详解
|
6月前
|
XML Java 数据格式
JAVAEE框架之Spring注解
JAVAEE框架之Spring注解
64 0
|
JSON 前端开发 Java
|
XML Java 数据库连接
【Spring学习笔记 五】Spring注解及Java类配置开发
【Spring学习笔记 五】Spring注解及Java类配置开发
94 0
|
前端开发 安全 Java
Java 最常见的面试题:spring mvc 和 struts 的区别是什么?
Java 最常见的面试题:spring mvc 和 struts 的区别是什么?
139 0
|
前端开发
java202304java学习笔记第六十四天-mvc的请求-ssm-拦截器的作用
java202304java学习笔记第六十四天-mvc的请求-ssm-拦截器的作用
58 0
|
设计模式 JSON 前端开发
java面试题(十八)spring MVC
3.1 什么是MVC? 参考答案 MVC是一种设计模式,在这种模式下软件被分为三层,即Model(模型)、View(视图)、Controller(控制器)。Model代表的是数据,View代表的是用户界面,Controller代表的是数据的处理逻辑,它是Model和View这两层的桥梁。将软件分层的好处是,可以将对象之间的耦合度降低,便于代码的维护。 3.2 DAO层是做什么的? 参考答案 DAO是Data Access Object的缩写,即数据访问对象,在项目中它通常作为独立的一层,专门用于访问数据库。这一层的具体实现技术有很多,常用的有Spring JDBC、Hibernate、JPA、
133 0
|
前端开发 Java Spring
Java 最常见的面试题:spring mvc 有哪些组件?
Java 最常见的面试题:spring mvc 有哪些组件?
|
安全 Java Spring
Java 最常见的面试题:spring 中的 bean 是线程安全的吗?
Java 最常见的面试题:spring 中的 bean 是线程安全的吗?
129 0