SpringMVC:注解@ControllerAdvice的工作原理

不装逼的程序员 今天

Spring MVC中,通过组合使用注解@ControllerAdvice和其他一些注解,我们可以为开发人员实现的控制器类做一些全局性的定制,具体来讲,可作如下定制 :
  • 结合@ExceptionHandler使用 ==> 添加统一的异常处理控制器方法

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

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

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

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

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

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

@Overridepublic void afterPropertiesSet() { // Do this first, it may add ResponseBody advice beans initControllerAdviceCache();

   // 省略掉无关代码  // ...}

从以上代码可以看出,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 bean List<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,将其登记到 requestResponseBodyAdviceBeans  if (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初始化方法中被调用执行,如下所示 :

@Overridepublic void afterPropertiesSet() { // Do this first, it may add ResponseBody advice beans initControllerAdviceCache();

 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执行一个控制器方法的准备过程中被调用,如下所示 :

@Nullableprotected 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执行一个控制器方法的准备过程中被调用,如下所示 :

@Nullableprotected 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的工作原理已经不是问题了。

(0)

相关推荐

  • @Repository注解的作用

    @Repository和@Controller.@Service.@Component的作用差不多,都是把对象交给spring管理.@Repository用在持久层的接口上,这个注解是将接口的一个实现 ...

  • 年轻人不讲武德,竟然重构出这么优雅后台 API 接口

    Hello,早上好,我是楼下小黑哥~ 最近偶然间在看到 Spring 官方文档的时候,新学到一个注解 @ControllerAdvice,并且成功使用这个注解重构我们项目的对外 API 接口,去除繁琐 ...

  • SpringMVC的工作原理

    SpringMVC的工作原理图: SpringMVC流程 1.  用户发送请求至前端控制器DispatcherServlet. 2.  DispatcherServlet收到请求调用HandlerMa ...

  • [Java] SpringMVC工作原理之一:DispatcherServlet

    一.DispatcherServlet 处理流程 在整个 Spring MVC 框架中,DispatcherServlet 处于核心位置,它负责协调和组织不同组件完成请求处理并返回响应工作.在看 Di ...

  • 【感应式IC卡】S50卡技术资料和工作原理

             S50卡,采用NXP MF1 IC S50制作的非接触智能卡,通常简称S50卡.Mifare 1K卡.M1卡或直接简称感应式IC卡,符合ISO14443A标准,拥有4字节UID号,是 ...

  • 一文轻松看懂区块链的工作原理

    作者注:这篇文章是写给非计算机专业的朋友的科普文,尽量避开了比较专业的细节.如果你是计算机相关专业,或者具备一定数据结构.算法.密码学的知识,建议直接看文末参考资料中的三篇博文,或者更专业的资料. 区 ...

  • PLC 工作原理与内部存储器使用规则(一)

    编前语:这是一篇对PLC的认识提出全新概念的.并把PLC工作原理解释得准确.清楚.明白的技术文章.其理论分析的方法,探讨问题的角度,与通常可见的书籍文章有较大的不同.现在推荐给<电子报>的 ...

  • 什么是锁相环?读懂它的基本组成和工作原理

    什么是锁相环?读懂它的基本组成和工作原理

  • 浪涌保护器的作用和工作原理

    浪涌保护器也叫避雷器.防雷器.是一种为低压供电系统,电子设备,仪器仪表,通讯线路等提供的安全防护的电子装置.当电气回路中,因雷电等外界因素,突然产生尖峰电压或电流时,浪涌保护器能在极短的时间内导通分流 ...

  • 【第2273期】搜索引擎工作原理

    前言 正文从这开始~~~ 搜索引擎的工作过程大体可以分为三个阶段: 1.对网页进行抓取建库 搜索引擎蜘蛛通过抓取页面上的链接访问其他网页,将获得的HTML代码存入数据库 2.预处理 索引程序对抓取来的 ...

  • SAP Fiori Elements 框架里 Smart Table 控件的工作原理介绍

    这是 Jerry 2021 年的第 34 篇文章,也是汪子熙公众号总共第 310 篇原创文章. Jerry 前一篇文章 深入掌握 SAP Fiori Elements 工作原理系列之二:如何给 Fio ...