我在 Redant(https://github.com/all4you/redant) 中經過繼承 ChannelHandler 實現了攔截器的功能,而且 pipeline 就是一種責任鏈模式的應用。可是後來我對本來的攔截器進行了從新設計,爲何這樣作呢,由於本來的方式是在 ChannelHandler 的基礎上實現的,而咱們知道 Netty 的數據處理都是基於 ByteBuf 的,這就涉及到引用計數釋放的問題,前面的 ChannelHandler 在處理時能夠不關心引用計數的問題,而交給最後一個 ChannelHandler 去釋放。前端
可是攔截器的一大特性就是當某個條件不知足時須要中斷後面的操做直接返回,因此這就形成了在 pipeline 中某個節點須要釋放引用計數,另一個方面就是原先的設計使用了不少自定義的 ChannelHandler,有的只作了一些簡單的工做,因此徹底能夠對他們進行合併,使代碼變得更加精簡緊湊。java
合併多個 ChannelHandler 是比較簡單的,從新設計攔截器相對就複雜一些了。git
首先我把本來的前置攔截器和後置攔截器統一成一個攔截器,而後抽象出兩個方法,分別表示:前置處理,後置處理,以下圖所示:github
默認前置處理的方法返回 true,用戶能夠根據他們的業務進行覆蓋。app
這裏是定義了一個抽象類,也能夠用接口,java 8 開始接口中能夠有默認方法實現。ide
攔截器定義好以後,如今就能夠在 ChannelHandler 中加入攔截器的方法調用了,以下圖所示:post
當前置方法返回 false 時,直接返回,中斷後面的業務邏輯處理,最終會到 finally 中將結果寫入 response 中返回給前端。性能
如今只要實現 InterceptorHandler 中的兩個方法就能夠了,其實這也很簡單,只要獲取到全部的 Interceptor 的實現類,而後依次調用這些實現類的前置方法和後置方法就行了,以下圖所示:測試
如今的重點就是怎樣獲取到全部的攔截器,首先能夠想到的是經過掃描特定路徑,找到全部 Interceptor 的實現類,而後將這些實現類加入到一個 List 中便可。ui
那怎麼保證攔截器的執行順序呢,很簡單,只要在加入 List 以前對他們進行排序就能夠了。定義一個 @Order 註解來表示排序的順序,而後用一個 Wrapper 包裝類將 Interceptor 和 Order 包裝起來,排序到包裝類的 List 中,最後再從包裝類的 List 中依次取出全部的 Interceptor 就完成了 Interceptor 的排序了。
知道了大體的原理以後,實現起來就很簡單了,以下圖所示:
可是咱們不能每次都經過調用 scanInterceptors() 方法來獲取全部的攔截器,若是這樣每次都掃描一次的話性能會有影響,因此咱們只須要第一次調用一下該方法,而後把結果保存在一個私有的變量中,獲取的時候直接讀取該變量的值便可,以下圖所示:
下面讓咱們來自定義兩個攔截器實現類,來驗證下具體的效果。
第一個攔截器,在前置方法中對請求參數進行判斷,若是請求參數中有 block=true 的參數,則進行攔截,以下圖所示:
第二個攔截器,在後置方法中打印出每次請求的耗時,以下圖所示:
經過 @Order 註解來指定執行的順序,先執行 BlockInterceptor 再執行 PerformanceInterceptor。
如今咱們請求 /user/info 這個接口,查看下效果。
首先咱們只提交正常的參數,以下圖所示:
打印的結果以下圖所示:
從打印的結果中能夠看到依次執行了:
這說明攔截器是按照 @Order 註解進行了排序,而後依次執行的。
而後咱們再提交一個 block=true 的參數,再次請求該接口,以下圖所示:
能夠看到該請求已經被攔截器的前置方法給攔截了,再看下打印的日誌,以下圖所示:
只打印了 BlockInterceptor 的 preHandler 方法中的部分日誌,後面的方法都沒有執行,由於被攔截了直接返回了。
到這裏已經對攔截器完成了改造,而且也驗證了效果,看上去效果還能夠。可是有沒有什麼問題呢?
還真有一個問題:全部的 Interceptor 實現類只要被掃描到了,就會被加入到 List 中去,若是不想應用某一個攔截器這時就作不到了,由於沒法對 list 中的值進行動態的更改。
若是咱們能夠構造一個動態的獲取 Interceptor 的 list 構造器,優先從構造器中獲取 list,若是用戶沒有定義構造器時再經過掃描的方式去拿到全部的 Interceptor 這樣就完美了。
動態獲取 Interceptor 的 list 的方法,能夠由用戶自定義實現,根據某些規則來肯定要不要將某個 Interceptor 加入到 list 中去,這樣就把 Interceptor 的實現和使用進行了解耦了。用戶能夠實現任意多的 Interceptor,可是隻根據規則去使用其中的某些 Interceptor。
理清楚了原理以後,就很好實現了,首先定義一個接口,用來構造 Interceptor 的 List,以下圖所示:
有了 InterceptorBuilder 以後,在獲取 Interceptor 的時候,就能夠先根據 InterceptorBuilder 來獲取了,以下圖所示:
如下是一個示例的 InterceptorBuilder,具體的能夠用戶自行擴展設計,以下圖所示:
這樣用戶只要實現一個 InterceptorBuilder 接口,便可按照本身的意圖去組裝全部的攔截器。
在 Redant 中實現的攔截器所使用的責任鏈,實際上是經過一個 List 來保存了全部的 Interceptor,那咱們一般所說的責任鏈除了使用 List 來實現外,還能夠經過真正的鏈表結構來實現,Netty 和 Sentinel 中都有這樣的實現,下面我來實現一個簡單的鏈式結構的責任鏈。
責任鏈的應用已經有不少了,這裏再也不贅述,假設咱們須要對前端提交的請求作如下操做:鑑權,登陸,日誌記錄,經過責任鏈來作這些處理是很是合適的。
首先定義一個處理接口,以下圖所示:
經過 List 方式的實現很簡單,只須要把每一個 Processor 的實現類添加到一個 List 中便可,處理的時候遍歷該 List 依次處理,這裏再也不作具體的描述,感興趣的能夠自行實現。
若是是經過鏈表的形式來實現的話,首先咱們須要有一個類表示鏈表中的某個節點,而且該節點須要有一個同類型的私有變量表示該節點的下個節點,這樣就能夠實現一個鏈表了,以下圖所示:
接着咱們須要定義一個容器,在容器中有頭,尾兩個節點,頭結點做爲一個空節點,真正的節點將添加到頭結點的 next 節點上去,尾節點做爲一個指針,用來指向當前添加的節點,下一次添加新節點時,將從尾節點處添加。有了具體的處理邏輯以後,實現起來就很簡單了,這個容器的實現以下圖所示:
下面咱們能夠實現具體的 Processor 來處理業務邏輯了,只要繼承 AbstractLinkedProcessor 便可,以下圖所示:
其餘兩個實現類: LoginProcessor ,LogProcessor 相似,這裏就不貼出來了。
而後就能夠根據規則來組裝所須要的 Processor 了,假設咱們的規則是須要對請求依次進行:鑑權,登陸,日誌記錄,那組裝的代碼以下圖所示:
執行該代碼,結果以下圖所示:
看的仔細的同窗可能發現了,在 AuthProcessor 的業務邏輯實現中,除了執行了具體的邏輯代碼以外,還調用了一行 super.process(content) 代碼,這行代碼的做用是調用鏈表中的下一個節點的 process 方法。可是若是有一天咱們在寫本身的 Processor 實現類時,忘記調用這行代碼的話,會是怎樣的結果呢?
結果就是當前節點後面的節點不會被調用,整個鏈表就像斷掉同樣,那怎樣來避免這種問題的發生呢?其實咱們在 AbstractProcessor 中已經實現了 process 方法,該方法就是調用下個節點的 process 方法的。那咱們在這個方法觸發調用下個節點以前,再抽象出一個用以具體的業務邏輯處理的方法 doProcess ,先執行 doProcess 方法,執行完以後再觸發下個節點的 process ,這樣就不會出現鏈表斷掉的狀況了,具體的實現以下圖所示:
相應的 LinkedProcessorChain 和具體的實現類也要作響應的調整,以下圖所示:
從新執行剛剛的測試類,發現結果和以前的同樣,至此一個簡單的鏈式責任鏈完成了。