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

樓下小黑哥發表於2020-12-02

Hello,各位小夥伴們,早上好~

上週文章年輕人不講武德,竟然重構出這麼優雅後臺 API 介面我們使用 @ControllerAdviceResponseBodyAdvice 重構後端的 API 介面,降低了複雜度,減少了重複程式碼,後續介面開發非常簡潔優雅。

知其然而知其所以然,今天這篇文章來聊聊這個註解背後的原理,讓我們徹底掌握這個註解,避免後續踩坑。

另外,有個小夥伴看完上篇文章,覺得這個註解的跟 Spring Interceptor 功能很類似,再加上之前還學習了 Servlet 體系 Filter 功能,不知道這幾個有什麼區別,感覺很混亂。

所以今天這篇文章下面兩個部分出發,詳細解釋一下。

  1. @ControllerAdviceResponseBodyAdvice 註解原理
  2. FilterInterceptorResponseBodyAdvice 區別

歡迎關注我的公眾號:程式通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的部落格:studyidea.cn

從原始碼解析背後的原理

上篇文章中我們看到 ResponseBodyAdvice的子類使用 @ControllerAdvice註解,大家有沒有好奇,如果我將@ControllerAdvice換成 @Controller 註解,還能達到上篇文章的效果嗎?

感興趣的小夥伴可以自己嘗試下,這裡小黑哥自己告訴大家結果了,實際測試結果是不行的。

那為什麼一定要與@ControllerAdvice 搭配才會生效?

首先我們先檢視一下 @ControllerAdvice 的原始碼:

image-20201128152447563

可以看到這個註解上還存在一個我們非常熟悉的 @Component 註解。這裡我們可以將 @ControllerAdvice 理解成@Component 子類,所以其修飾的類也會成為 Spring 中 Bean

ps:大家可以看下 @Controller/@Service/@Repository,其實也是這個原理。

Spring 容器初始化過程,如果掃描到 @ControllerAdvice 註解,將會將其生成一個 ControllerAdviceBean Bean。

這個過程程式碼主要位於 RequestMappingHandlerAdapter#initControllerAdviceCache:

這段程式碼主要分為兩步:

第一步使用 ControllerAdviceBean#findAnnotatedBeans獲取所有被 @ControllerAdvice修飾的類。

第二步將所有實現了ResponseBodyAdvice 介面的 Bean 放入到 requestResponseBodyAdviceBeans 集合中,後續將會使用該集合。

這就解釋了為什麼實現 ResponseBodyAdvice介面的子類一定要與@ControllerAdvice一起使用的原因了。

接下來我們來看下 ResponseBodyAdvice 的執行流程。

這裡教給大家一個程式碼除錯的小技巧,當我們不知道一個類在原始碼中如何被呼叫的時候,我們可以使用 IDEA 程式碼除錯功能,然後檢視程式碼呼叫棧。

如上面的所示,我們可以很清楚觀察 ResponseBodyAdvice 呼叫關係。這裡的類呼叫關係相對還是比較複雜,下面給大家簡化一下。

前面的邏輯就不說了,就是 Spring MVC 通用流程。重點邏輯位於 RequestResponseBodyAdviceChain,我們具體看下原始碼:

嗯吶嗯吶,請忽略上圖的 ③

其實邏輯非常簡單,遍歷所有的 ResponseBodyAdvice 的子類,首先呼叫其 supports判斷是否支援,如果支援的呼叫的 beforeBodyWrite修改返回資訊。

FilterInterceptorResponseBodyAdvice 區別

Filter屬於 Servlet 元件,所有請求將會先進入 Filter ,判斷通過之後才會在進入到真正的具體的請求中。

上圖代表是用 Spring MVC 的一個 Web 專案,所有請求將會先進入到 Filter,通過之後才會進入到 SpringMVC 中最重要的元件 DispatchServlet

Interceptor 是 SpringMVC 的元件,它的作用實際上與 Filter類似, 只不過的它的作用是位於自定義的 Controller 前後。

不管是 Filter 還是 Interceptor,它們的作用方法域內只能拿到 ServletResponse 的引數,這個時候返回值已經被寫入 ServletResponse,我們很難再去修改。

ResponseBodyAdvice作用時機位於寫入之前,所以這個時候可以很容易拿到原值進行修改。

總結

SpringMVC 初始化的過程中,將會掃描所有帶有 @ControllerAdvice註解的類,將其生成為 ControllerAdviceBean。如果這類剛好為 ResponseBodyAdvice介面的子類,Spring 將會為其單獨儲存起來,後續將會封裝到的 RequestResponseBodyAdviceChain,使用責任鏈的模式對請求、響應進行處理。

最後我們解釋了一下 FilterInterceptorResponseBodyAdvice區別,從作用範圍上來講:

Filter>Interceptor>ResponseBodyAdvice

但是前兩者沒辦法修改返回值(時機太晚),只有後者才可以真正在返回值返回之前做到修改。

好了,今天文章就到這裡了,下次我們分享一下如何寫出優雅的 Dubbo 介面,下次見。

歡迎關注我的公眾號:程式通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的部落格:studyidea.cn

相關文章