14

写了一套优雅接口之后,领导让我给大家讲讲这背后的技术原理

 3 years ago
source link: http://www.cnblogs.com/goodAndyxublog/p/14071856.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Hello,各位小伙伴们,早上好~

上周文章 年轻人不讲武德,竟然重构出这么优雅后台 API 接口 我们使用 @ControllerAdviceResponseBodyAdvice 重构后端的 API 接口,降低了复杂度,减少了重复代码,后续接口开发非常简洁优雅。

知其然而知其所以然,今天这篇文章来聊聊这个注解背后的原理,让我们彻底掌握这个注解,避免后续踩坑。

另外,有个小伙伴看完上篇文章,觉得这个注解的跟 Spring Interceptor 功能很类似,再加上之前还学习了 Servlet 体系 Filter 功能,不知道这几个有什么区别,感觉很混乱。

所以今天这篇文章下面两个部分出发,详细解释一下。

  1. @ControllerAdviceResponseBodyAdvice 注解原理
  2. FilterInterceptorResponseBodyAdvice 区别

欢迎关注我的公众号:程序通事,获得日常干货推送。如果您对我的专题内容感兴趣,也可以关注我的博客: studyidea.cn

从源码解析背后的原理

上篇文章中我们看到 ResponseBodyAdvice 的子类使用 @ControllerAdvice 注解,大家有没有好奇,如果我将 @ControllerAdvice 换成 @Controller 注解,还能达到上篇文章的效果吗?

感兴趣的小伙伴可以自己尝试下,这里小黑哥自己告诉大家结果了,实际测试结果是不行的。

那为什么一定要与 @ControllerAdvice 搭配才会生效?

首先我们先查看一下 @ControllerAdvice 的源码:

eiEjyya.jpg!mobile

可以看到这个注解上还存在一个我们非常熟悉的 @Component 注解。这里我们可以将 @ControllerAdvice 理解成 @Component 子类,所以其修饰的类也会成为 Spring 中 Bean

ps:大家可以看下 @Controller / @Service / @Repository ,其实也是这个原理。

Spring 容器初始化过程,如果扫描到 @ControllerAdvice 注解,将会将其生成一个 ControllerAdviceBean Bean。

这个过程代码主要位于 RequestMappingHandlerAdapter#initControllerAdviceCache :

YRf6R3U.jpg!mobile

这段代码主要分为两步:

第一步使用 ControllerAdviceBean#findAnnotatedBeans 获取所有被 @ControllerAdvice 修饰的类。

r2uqamJ.jpg!mobile

第二步将所有实现了 ResponseBodyAdvice 接口的 Bean 放入到 requestResponseBodyAdviceBeans 集合中,后续将会使用该集合。

Qn2mU3J.jpg!mobile

这就解释了为什么实现 ResponseBodyAdvice 接口的子类一定要与 @ControllerAdvice 一起使用的原因了。

接下来我们来看下 ResponseBodyAdvice 的执行流程。

这里教给大家一个代码调试的小技巧,当我们不知道一个类在源码中如何被调用的时候,我们可以使用 IDEA 代码调试功能,然后查看代码调用栈。

2IJvYbQ.jpg!mobile

如上面的所示,我们可以很清楚观察 ResponseBodyAdvice 调用关系。这里的类调用关系相对还是比较复杂,下面给大家简化一下。

Rr2uMr2.jpg!mobile

前面的逻辑就不说了,就是 Spring MVC 通用流程。重点逻辑位于 RequestResponseBodyAdviceChain ,我们具体看下源码:

eUFjmeY.jpg!mobile

嗯呐嗯呐,请忽略上图的 ③

其实逻辑非常简单,遍历所有的 ResponseBodyAdvice 的子类,首先调用其 supports 判断是否支持,如果支持的调用的 beforeBodyWrite 修改返回信息。

FilterInterceptorResponseBodyAdvice 区别

Filter 属于 Servlet 组件,所有请求将会先进入 Filter ,判断通过之后才会在进入到真正的具体的请求中。

MjAZ7rv.jpg!mobile

上图代表是用 Spring MVC 的一个 Web 项目,所有请求将会先进入到 Filter ,通过之后才会进入到 SpringMVC 中最重要的组件 DispatchServlet

Interceptor 是 SpringMVC 的组件,它的作用实际上与 Filter 类似, 只不过的它的作用是位于自定义的 Controller 前后。

U7VZBnQ.jpg!mobile

不管是 Filter 还是 Interceptor ,它们的作用方法域内只能拿到 ServletResponse 的参数,这个时候返回值已经被写入 ServletResponse ,我们很难再去修改。

ResponseBodyAdvice 作用时机位于写入之前,所以这个时候可以很容易拿到原值进行修改。

MrQf2iv.jpg!mobile

总结

SpringMVC 初始化的过程中,将会扫描所有带有 @ControllerAdvice 注解的类,将其生成为 ControllerAdviceBean 。如果这类刚好为 ResponseBodyAdvice 接口的子类,Spring 将会为其单独保存起来,后续将会封装到的 RequestResponseBodyAdviceChain ,使用责任链的模式对请求、响应进行处理。

最后我们解释了一下 FilterInterceptorResponseBodyAdvice 区别,从作用范围上来讲:

Filter>Interceptor>ResponseBodyAdvice

但是前两者没办法修改返回值(时机太晚),只有后者才可以真正在返回值返回之前做到修改。

好了,今天文章就到这里了,下次我们分享一下如何写出优雅的 Dubbo 接口,下次见。

欢迎关注我的公众号:程序通事,获得日常干货推送。如果您对我的专题内容感兴趣,也可以关注我的博客: studyidea.cn


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK