点击上方蓝色“方志朋”,选择“设为星标”回复“666”获取独家整理的学习资料!

来源:blog.csdn.net/andy_zhang2007/

article/details/100041219

Spring MVC中,通过组合使用注解@ControllerAdvice和其他一些注解,我们可以为开发人员实现的控制器类做一些全局性的定制,具体来讲,可作如下定制 :

  • 结合@ExceptionHandler使用 ==> 添加统一的异常处理控制器方法

  • 结合@ModelAttribute使用 ==> 使用共用方法添加渲染视图的数据模型属性

  • 结合@InitBinder使用 ==> 使用共用方法初始化控制器方法调用使用的数据绑定器

    数据绑定器涉及到哪些参数/属性需要/不需要绑定,设置数据类型转换时使用的PropertyEditor,Formatter等。

那么,@ControllerAdvice的工作原理又是怎样的呢 ?这篇文章,我们就一探究竟。

1. 注解@ControllerAdvice是如何被发现的 ?

首先,容器启动时,会定义类型为RequestMappingHandlerAdapterbean组件,这是DispatcherServlet用于执行控制器方法的HandlerAdapter,它实现了接口InitializingBean,所以自身在初始化时其方法#afterPropertiesSet会被调用执行。

@Override
public void afterPropertiesSet() {// Do this first, it may add ResponseBody advice beansinitControllerAdviceCache();// 省略掉无关代码 // ...
}

从以上代码可以看出,RequestMappingHandlerAdapter bean组件在自身初始化时调用了#initControllerAdviceCache,从这个方法的名字上就可以看出,这是一个ControllerAdvice相关的初始化函数,而#initControllerAdviceCache具体又做了什么呢?我们继续来看 :

private void initControllerAdviceCache() {if (getApplicationContext() == null) {return;}// 找到所有使用了注解 @ControllerAdvice 的bean组件 List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());// 排序AnnotationAwareOrderComparator.sort(adviceBeans);// this. requestResponseBodyAdvice : //   用于记录所有 @ControllerAdvice + RequestBodyAdvice/ResponseBodyAdvice bean // this.modelAttributeAdviceCache : //   用于记录所有 @ControllerAdvice bean组件中的 @ModuleAttribute 方法// this.initBinderAdviceCache : //  用于记录所有 @ControllerAdvice bean组件中的 @InitBinder 方法// 用于临时记录所有 @ControllerAdvice + RequestResponseBodyAdvice beanList<Object> requestResponseBodyAdviceBeans = new ArrayList<>();// 遍历每个使用了注解 @ControllerAdvice 的 bean 组件for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}// 获取当前  ControllerAdviceBean 中所有使用了 @ModelAttribute 注解的方法Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);if (!attrMethods.isEmpty()) {this.modelAttributeAdviceCache.put(adviceBean, attrMethods);}// 获取当前 ControllerAdviceBean 中所有使用了 @InitMethod 注解的方法Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);if (!binderMethods.isEmpty()) {this.initBinderAdviceCache.put(adviceBean, binderMethods);}// 如果当前 ControllerAdviceBean 继承自 RequestBodyAdvice,将其登记到 requestResponseBodyAdviceBeansif (RequestBodyAdvice.class.isAssignableFrom(beanType)) {requestResponseBodyAdviceBeans.add(adviceBean);}// 如果当前 ControllerAdviceBean 继承自 ResponseBodyAdvice,将其登记到 requestResponseBodyAdviceBeans  if (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");}}
}

从以上#initControllerAdviceCache方法的实现逻辑来看,它将容器中所有使用了注解@ControllerAdvicebean或者其方法都分门别类做了统计,记录到了RequestMappingHandlerAdapter实例的三个属性中 :

  • requestResponseBodyAdvice

  • 用于记录所有@ControllerAdvice + RequestBodyAdvice/ResponseBodyAdvice bean组件

  • modelAttributeAdviceCache

  • 用于记录所有 @ControllerAdvice bean组件中的 @ModuleAttribute 方法

  • initBinderAdviceCache

  • 用于记录所有@ControllerAdvice bean组件中的 @InitBinder 方法

到此为止,我们知道,使用注解@ControllerAdvicebean中的信息被提取出来了,但是,这些信息又是怎么使用的呢 ?我们继续来看。

2. @ControllerAdvice 定义信息的使用

1. requestResponseBodyAdvice的使用

/*** Return the list of argument resolvers to use including built-in resolvers* and custom resolvers provided via {@link #setCustomArgumentResolvers}.*/
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();// ... 省略无关代码        resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));// ... 省略无关代码resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));  // ... 省略无关代码resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));// ... 省略无关代码return resolvers;
}

#getDefaultArgumentResolvers方法用于准备RequestMappingHandlerAdapter执行控制器方法过程中缺省使用的HandlerMethodArgumentResolver,从上面代码可见,requestResponseBodyAdvice会被传递给RequestResponseBodyMethodProcessor/RequestPartMethodArgumentResolver/HttpEntityMethodProcessor这三个参数解析器,不难猜测,它们在工作时会使用到该requestResponseBodyAdvice,但具体怎么使用,为避免过深细节影响理解,本文我们不继续展开。

方法#getDefaultArgumentResolvers也在RequestMappingHandlerAdapter初始化方法中被调用执行,如下所示 :

@Override
public void afterPropertiesSet() {// Do this first, it may add ResponseBody advice beansinitControllerAdviceCache();if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); // <==this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}// 省略无关代码
}

2. modelAttributeAdviceCache的使用

private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);Class<?> handlerType = handlerMethod.getBeanType();Set<Method> methods = this.modelAttributeCache.get(handlerType);if (methods == null) {// 获取当前控制器类中使用了 @ModelAttribute 的方法methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);this.modelAttributeCache.put(handlerType, methods);}List<InvocableHandlerMethod> attrMethods = new ArrayList<>();// Global methods first// 遍历@ControllerAdvice bean中所有使用了 @ModelAttribute 的方法,// 将其包装成 InvocableHandlerMethod 放到 attrMethods// ********* 这里就是 modelAttributeAdviceCache 被使用到的地方了 ************this.modelAttributeAdviceCache.forEach((clazz, methodSet) -> {if (clazz.isApplicableToBeanType(handlerType)) {Object bean = clazz.resolveBean();for (Method method : methodSet) {attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));}}});// 遍历当前控制器类中中所有使用了 @ModelAttribute 的方法,// 也将其包装成 InvocableHandlerMethod 放到 attrMethods        for (Method method : methods) {Object bean = handlerMethod.getBean();attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));}// 此时  attrMethods 包含了两类 InvocableHandlerMethod, 分别来自于 :// 1. @ControllerAdvice bean 中所有使用了 @ModelAttribute 的方法// 2. 当前控制器类中中所有使用了 @ModelAttribute 的方法return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}/ 从指定 bean 的方法 method ,其实是一个使用了注解 @ModelAttribute 的方法,
/ 构造一个 InvocableHandlerMethod 对象
private InvocableHandlerMethod createModelAttributeMethod(WebDataBinderFactory factory, Object bean, Method method) {InvocableHandlerMethod attrMethod = new InvocableHandlerMethod(bean, method);if (this.argumentResolvers != null) {attrMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}attrMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);attrMethod.setDataBinderFactory(factory);return attrMethod;
}

从此方法可以看到,#getModelFactory方法使用到了modelAttributeAdviceCache,它会根据其中每个元素构造成一个InvocableHandlerMethod,最终传递给要创建的ModelFactory对象。而#getModelFactory又在什么时候被使用呢 ? 它会在RequestMappingHandlerAdapter执行一个控制器方法的准备过程中被调用,如下所示 :

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {// 构造调用 handlerMethod 所要使用的数据绑定器工厂  WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);// 构造调用 handlerMethod 所要使用的数据模型工厂  ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);// 省略无关代码 ...
}

3. initBinderAdviceCache的使用

private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {Class<?> handlerType = handlerMethod.getBeanType();Set<Method> methods = this.initBinderCache.get(handlerType);if (methods == null) {// 获取当前控制器类中使用了 @InitBinder 的方法  methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);this.initBinderCache.put(handlerType, methods);}List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();// Global methods first// 遍历@ControllerAdvice bean中所有使用了 @InitBinder 的方法,// 将其包装成 InvocableHandlerMethod 放到 initBinderMethods// ********* 这里就是 initBinderAdviceCache 被使用到的地方了 ************        this.initBinderAdviceCache.forEach((clazz, methodSet) -> {if (clazz.isApplicableToBeanType(handlerType)) {Object bean = clazz.resolveBean();for (Method method : methodSet) {initBinderMethods.add(createInitBinderMethod(bean, method));}}});// 遍历当前控制器类中所有使用了 @InitBinder 的方法,// 将其包装成 InvocableHandlerMethod 放到 initBinderMethods        for (Method method : methods) {Object bean = handlerMethod.getBean();initBinderMethods.add(createInitBinderMethod(bean, method));}// 此时  initBinderMethods 包含了两类 InvocableHandlerMethod, 分别来自于 :// 1. @ControllerAdvice bean 中所有使用了 @InitBinder 的方法// 2. 当前控制器类中中所有使用了 @InitBinder 的方法        return createDataBinderFactory(initBinderMethods);
}/ 从指定 bean 的方法 method ,其实是一个使用了注解 @InitBinder 的方法,
/ 构造一个 InvocableHandlerMethod 对象
private InvocableHandlerMethod createInitBinderMethod(Object bean, Method method) {InvocableHandlerMethod binderMethod = new InvocableHandlerMethod(bean, method);if (this.initBinderArgumentResolvers != null) {binderMethod.setHandlerMethodArgumentResolvers(this.initBinderArgumentResolvers);}binderMethod.setDataBinderFactory(new DefaultDataBinderFactory(this.webBindingInitializer));binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);return binderMethod;
}/*** Template method to create a new InitBinderDataBinderFactory instance.* <p>The default implementation creates a ServletRequestDataBinderFactory.* This can be overridden for custom ServletRequestDataBinder subclasses.* @param binderMethods {@code @InitBinder} methods* @return the InitBinderDataBinderFactory instance to use* @throws Exception in case of invalid state or arguments*/
protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)throws Exception {return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
}

从此方法可以看到,#getDataBinderFactory方法使用到了initBinderAdviceCache,它会根据其中每个元素构造成一个InvocableHandlerMethod,最终传递给要创建的InitBinderDataBinderFactory对象。而#getDataBinderFactory又在什么时候被使用呢 ? 它会在RequestMappingHandlerAdapter执行一个控制器方法的准备过程中被调用,如下所示 :

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {ServletWebRequest webRequest = new ServletWebRequest(request, response);try {// 构造调用 handlerMethod 所要使用的数据绑定器工厂  WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);// 构造调用 handlerMethod 所要使用的数据模型工厂  ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);// 省略无关代码 ...
}

到此为止,我们基本上可以看到,通过@ControllerAdvice注解的bean组件所定义的@ModelAttribute/@InitBinder方法,或者RequestBodyAdvice/ResponseBodyAdvice,是如何被RequestMappingHandlerAdapter提取和使用的了。虽然我们并未深入到更细微的组件研究它们最终的使用,不过结合这些组件命名以及这些更深一层的使用者组件的名称,即便是猜测,相信你也不难理解猜到它们如何被使用了。

不知道你注意到没有,关于@ControllerAdvice@ExceptionHandler这一组合,在上面提到的RequestMappingHandlerAdapter逻辑中,并未涉及到。那如果使用了这种组合,又会是怎样一种工作机制呢 ?事实上,@ControllerAdvice@ExceptionHandler这一组合所做的定义,会被ExceptionHandlerExceptionResolver消费使用。不过关于ExceptionHandlerExceptionResolver我们会另外行文介绍,通过这篇文章中的例子,理解@ControllerAdvide的工作原理已经不是问题了。

热门内容:
使用雪花id或uuid作为Mysql主键,被老板怼了一顿!
再见了,收费的Navicat。写了个牛逼的日志切面,甩锅更方便了!请谨慎使用Arrays.asList、ArrayList的subList最近面试BAT,整理一份面试资料《Java面试BAT通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。获取方式:点“在看”,关注公众号并回复 666 领取,更多内容陆续奉上。

明天见(。・ω・。)ノ♡

SpringMVC:注解@ControllerAdvice的工作原理相关推荐

  1. Spring注解解析及工作原理、自定义注解

    注解(Annotation) 提供了一种安全的类似注释的机制,为我们在代码中添加信息提供了一种形式化得方法,使我们可以在稍后某个时刻方便的使用这些数据(通过解析注解来使用这些 数据),用来将任何的信息 ...

  2. SpringMVC执行流程及工作原理

    1.SpringMVC的原理和组成 从上图中可以看出:SpringMVC是属于SpringWeb里面的一个功能模块(SpringWebMVC).专门用来开发SpringWeb项目的一种MVC模式的技术 ...

  3. javax.ws.rs.Path注解@Path的工作原理解析

    这个annotation和Spring里的@RequestMapping作用完全一样.下图是Spring里的annotation: 在Eclipse里单击练习代码的@Path: 发现这个path的va ...

  4. SpringMVC→简介、MVC、SpringMVC工作原理、Maven搭建第一个SpringMVC、请求参数接收、重定向、文件上传、AJAX异步访问、请求参数接收绑定JSON、@注解及传参

    MVC SpringMVC工作原理 Maven搭建第一个SpringMVC 目录结构 web.xml *-servlet.xml Controller请求处理类 跳转页面 Maven运行服务器项目 浏 ...

  5. SpringMVC工作原理详解

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 先来看一下什么是 MVC 模式 MVC 是一种设计模式. MVC 的原理图如下: SpringMV ...

  6. SpringMVC工作原理 1

    大家好,我是IT修真院深圳分院第十一期学员,一枚正直纯洁善良的JAVA程序员. 今天给大家分享一下,修真院官网JAVA任务二的一个知识点:SpringMVC工作原理 1.背景介绍 一:背景介绍 Jav ...

  7. springmvc工作流程_SpringMVC工作原理

    买了好多书,但是没有一本是看完的,这是看完的第一本书,虽然页数不多.技术早就用了老多遍了,还是总结一下吧! 一.MVC模式 MVC是 model.view.和controller的缩写,分别代表web ...

  8. springmvc工作流程详解_SpringMVC工作原理详解

    点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 先来看一下什么是 MVC 模式 MVC 是一种设计模式. MVC 的原理图如下: SpringMV ...

  9. springmvc工作流程_springMVC工作原理及流程详细讲解

    简述 本文主要介绍springMVC工作原理. 工作原理 客户端发送HTTP请求,DispatcherServlet控制器拦截到请求,调用HandlerMapping 解析请求对应的Handler,H ...

最新文章

  1. XLearning - 深度学习调度平台
  2. Depth by Poking:从自监督抓取学习深度估计
  3. (Eclipse)(STM32) STM32在Eclipse編程
  4. 【Android 逆向】Android 逆向通用工具开发 ( Windows 平台静态库程序类型 | 编译逆向工具依赖的 Windows 平台静态库程序 )
  5. MXD文档保存和地图浏览
  6. java math rint_Java Math.rint() 方法
  7. Java Spring ClassPathXmlApplicationContext是如何判断容器内包含某个Bean的
  8. android浏览SD卡的文件,简单实现浏览Android SD卡中的文件
  9. JS关键字和保留字汇总
  10. c语言 静态链表插入排序,数据结构 - 表插入排序 具体解释 及 代码(C++)
  11. python实现绘制信号序列语谱图
  12. 颗粒状糖果(巧克力)包装机设计
  13. FileUpload1 在部分浏览器中实现多选
  14. maya阿诺德渲染失败_maya2018无法加载ARNOLD渲染器?
  15. 凸优化理论基础2——凸集和锥
  16. 信道容量迭代算法验证
  17. 通过命令行使用bandizip压缩与解压
  18. A Tutorial on Learned Multi-dimensional Indexes
  19. 导出期刊对应格式的参考_中文参考文献怎么一键导出正确格式?写作必看!
  20. html5 video视频标签

热门文章

  1. NOIP模拟题 斐波那契数列
  2. mysql汉字转拼音函数
  3. 学习Spring Boot
  4. iOS7系统iLEX RAT冬青鼠安装教程:无需刷机还原纯净越狱系统
  5. WannaCry的UWP版,哈哈哈
  6. cf-Sasha and Array
  7. C#中Request.servervariables参数
  8. 必须掌握的八个DOS命令 [转]
  9. tensorflow 1
  10. 完全免费,简化版Plotly推出,秒绘各类可视化图表