【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源码分析》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

相关文章
|
6月前
|
SQL Java 关系型数据库
【JavaEE进阶】 @Transactional详解
【JavaEE进阶】 @Transactional详解
|
XML Java 数据库连接
【Spring学习笔记 五】Spring注解及Java类配置开发
【Spring学习笔记 五】Spring注解及Java类配置开发
102 0
|
存储 Java 应用服务中间件
SpringMVC源码分析 RequestContextHolder使用与源码分析
SpringMVC源码分析 RequestContextHolder使用与源码分析
SpringMVC源码分析 RequestContextHolder使用与源码分析
|
Java Spring
SpringAop学习笔记(三)——Aop源码分析
SpringAop学习笔记(三)——Aop源码分析
136 0
SpringAop学习笔记(三)——Aop源码分析
|
XML 前端开发 Java
【Spring框架】我终于读懂了ApplicationContext(案例详解)
本期重点分享ApplicationContext基础概念以及应用场景!
1067 0
【Spring框架】我终于读懂了ApplicationContext(案例详解)
|
XML 架构师 前端开发
SpringBoot从小白到精通(十)使用Interceptor拦截器,一学就会!
使用Spring Boot开发web项目有个非常重要的组件:拦截器。以前我们在做mvc 项目时也使用到的是filter过滤器也就是拦截器。其实Spring Boot 中的拦截器和SpringMVC中的拦截器也是类似的,只是配置上有些区别。那么下面我们就来看看Spring Boot 是怎么配置拦截器的。
SpringBoot从小白到精通(十)使用Interceptor拦截器,一学就会!
|
XML 前端开发 数据格式
springmvc源码分析
springmvc源码分析
130 0
springmvc源码分析
|
设计模式 前端开发 Java
SpringMVC教程1[原理分析及注解方式的使用]
模型-视图-控制器(MVC 是一个众所周知的以设计界面应用程序为基础的设计模式。它主要通过分离模型、视图及控制器在应用程序中的角色将业务逻辑从界面中解耦。通常,模型负责封装应用程序数据在视图层展示。视图仅仅只是展示这些数据,不包含任何业务逻辑。控制器负责接收来自用户的请求,并调用后台服务(manager或者dao)来处理业务逻辑。处理后,后台业务层可能会返回了一些数据在视图层展示。控制器收集这些数据及准备模型在视图层展示。MVC模式的核心思想是将业务逻辑从界面中分离出来,允许它们单独改变而不会相互影响。
SpringMVC教程1[原理分析及注解方式的使用]
|
Java 容器
SpringBoot源码分析系列之三:拦截器的优雅实现
所谓拦截器即为可以拦截HTTP请求的并做一些前置或者后置的通用处理手段,是一种AOP的处理方式,它不依赖于servlet容器,而依赖于web框架SpringMVC。主要用于拦截controller的请求接口。 基于URL实现拦截器 基于注解实现拦截器