1. 程式人生 > >寫了一套優雅介面之後,領導讓我給大家講講這背後的技術原理

寫了一套優雅介面之後,領導讓我給大家講講這背後的技術原理

Hello,各位小夥伴們,早上好~ 上週文章[年輕人不講武德,竟然重構出這麼優雅後臺 API 介面](https://studyidea.cn/spring-reactor)我們使用 `@ControllerAdvice`與 `ResponseBodyAdvice` 重構後端的 API 介面,降低了複雜度,減少了重複程式碼,後續介面開發非常簡潔優雅。 知其然而知其所以然,今天這篇文章來聊聊這個註解背後的原理,讓我們徹底掌握這個註解,避免後續踩坑。 另外,有個小夥伴看完上篇文章,覺得這個註解的跟 `Spring Interceptor` 功能很類似,再加上之前還學習了 `Servlet` 體系 `Filter` 功能,不知道這幾個有什麼區別,感覺很混亂。 所以今天這篇文章下面兩個部分出發,詳細解釋一下。 1. `@ControllerAdvice`與 `ResponseBodyAdvice` 註解原理 2. `Filter`,`Interceptor`,`ResponseBodyAdvice` 區別 > 歡迎關注我的公眾號:程式通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的部落格:[studyidea.cn](https://studyidea.cn) ## 從原始碼解析背後的原理 上篇文章中我們看到 `ResponseBodyAdvice`的子類使用 `@ControllerAdvice`註解,大家有沒有好奇,如果我將`@ControllerAdvice`換成 `@Controller` 註解,還能達到上篇文章的效果嗎? 感興趣的小夥伴可以自己嘗試下,這裡小黑哥自己告訴大家結果了,實際測試結果是不行的。 那為什麼一定要與`@ControllerAdvice` 搭配才會生效? 首先我們先檢視一下 `@ControllerAdvice` 的原始碼: ![image-20201128152447563](https://img2020.cnblogs.com/other/1419561/202012/1419561-20201202085639042-517543487.jpg) 可以看到這個註解上還存在一個我們非常熟悉的 `@Component` 註解。這裡我們可以將 `@ControllerAdvice` 理解成`@Component` 子類,所以其修飾的類也會成為 Spring 中 `Bean`。 > ps:大家可以看下 `@Controller`/`@Service`/`@Repository`,其實也是這個原理。 Spring 容器初始化過程,如果掃描到 `@ControllerAdvice` 註解,將會將其生成一個 `ControllerAdviceBean` Bean。 這個過程程式碼主要位於 `RequestMappingHandlerAdapter#initControllerAdviceCache`: ![](https://img2020.cnblogs.com/other/1419561/202012/1419561-20201202085639281-88415342.jpg) 這段程式碼主要分為兩步: 第一步使用 `ControllerAdviceBean#findAnnotatedBeans`獲取所有被 `@ControllerAdvice`修飾的類。 ![](https://img2020.cnblogs.com/other/1419561/202012/1419561-20201202085639670-1521883550.jpg) 第二步將所有實現了`ResponseBodyAdvice` 介面的 Bean 放入到 `requestResponseBodyAdviceBeans` 集合中,後續將會使用該集合。 ![](https://img2020.cnblogs.com/other/1419561/202012/1419561-20201202085640003-726968884.jpg) 這就解釋了為什麼實現 `ResponseBodyAdvice`介面的子類一定要與`@ControllerAdvice`一起使用的原因了。 接下來我們來看下 `ResponseBodyAdvice` 的執行流程。 這裡教給大家一個程式碼除錯的小技巧,當我們不知道一個類在原始碼中如何被呼叫的時候,我們可以使用 IDEA 程式碼除錯功能,然後檢視程式碼呼叫棧。 ![](https://img2020.cnblogs.com/other/1419561/202012/1419561-20201202085640282-1475841540.jpg) 如上面的所示,我們可以很清楚觀察 `ResponseBodyAdvice` 呼叫關係。這裡的類呼叫關係相對還是比較複雜,下面給大家簡化一下。 ![](https://img2020.cnblogs.com/other/1419561/202012/1419561-20201202085640533-135505846.jpg) 前面的邏輯就不說了,就是 Spring MVC 通用流程。重點邏輯位於 `RequestResponseBodyAdviceChain`,我們具體看下原始碼: ![](https://img2020.cnblogs.com/other/1419561/202012/1419561-20201202085640676-111976884.jpg) > 嗯吶嗯吶,請忽略上圖的 ③ 其實邏輯非常簡單,遍歷所有的 `ResponseBodyAdvice` 的子類,首先呼叫其 `supports`判斷是否支援,如果支援的呼叫的 `beforeBodyWrite`修改返回資訊。 ## `Filter`、`Interceptor`、`ResponseBodyAdvice` 區別 `Filter`屬於 Servlet 元件,所有請求將會先進入 `Filter` ,判斷通過之後才會在進入到真正的具體的請求中。 ![](https://img2020.cnblogs.com/other/1419561/202012/1419561-20201202085640893-1265455715.jpg) 上圖代表是用 Spring MVC 的一個 Web 專案,所有請求將會先進入到 `Filter`,通過之後才會進入到 SpringMVC 中最重要的元件 `DispatchServlet`。 而 `Interceptor` 是 SpringMVC 的元件,它的作用實際上與 `Filter`類似, 只不過的它的作用是位於自定義的 `Controller` 前後。 ![](https://img2020.cnblogs.com/other/1419561/202012/1419561-20201202085641125-174262714.jpg) 不管是 `Filter` 還是 `Interceptor`,它們的作用方法域內只能拿到 `ServletResponse` 的引數,這個時候返回值已經被寫入 `ServletResponse`,我們很難再去修改。 而 `ResponseBodyAdvice`作用時機位於寫入之前,所以這個時候可以很容易拿到原值進行修改。 ![](https://img2020.cnblogs.com/other/1419561/202012/1419561-20201202085641394-365034754.jpg) ## 總結 SpringMVC 初始化的過程中,將會掃描所有帶有 `@ControllerAdvice`註解的類,將其生成為 `ControllerAdviceBean`。如果這類剛好為 `ResponseBodyAdvice`介面的子類,Spring 將會為其單獨儲存起來,後續將會封裝到的 `RequestResponseBodyAdviceChain`,使用責任鏈的模式對請求、響應進行處理。 最後我們解釋了一下 `Filter`,`Interceptor`,`ResponseBodyAdvice`區別,從作用範圍上來講: ``` Filter>Interceptor>ResponseBodyAdvice ``` 但是前兩者沒辦法修改返回值(時機太晚),只有後者才可以真正在返回值返回之前做到修改。 好了,今天文章就到這裡了,下次我們分享一下如何寫出優雅的 Dubbo 介面,下次見。 > 歡迎關注我的公眾號:程式通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的部落格:[studyidea.cn](https://studyidea.cn)