wrapper怎么用_用责任链模式设计拦截器
我在 Redant(https://github.com/all4you/redant) 中通過繼承 ChannelHandler 實現了攔截器的功能,并且 pipeline 就是一種責任鏈模式的應用。但是我后面對原本的攔截器進行了重新設計,為什么這樣做呢,因為原本的方式是在 ChannelHandler 的基礎上操作的,而我們知道 Netty 的數據處理都是基于 ByteBuf 的,這就涉及到引用計數釋放的問題,前面的 ChannelHandler 在處理時可以不關心引用計數的問題,而交給最后一個 ChannelHandler 去釋放。
但是攔截器的一大特性就是當某個條件不滿足時需要中斷后面的操作直接返回,所以這就造成了在 pipeline 中某個節點需要釋放引用計數,另外一個方面就是原先的設計使用了很多自定義的 ChannelHandler,有的只做了一些簡單的工作,所以完全可以對他們進行合并,使代碼變得更加精簡緊湊。
合并多個 ChannelHandler 是比較簡單的,重新設計攔截器相對就復雜一些了。
重新設計攔截器
首先我把原本的前置攔截器和后置攔截器統一成一個攔截器,然后抽象出兩個方法,分別表示:前置處理,后置處理,如下圖所示:
默認前置處理的方法返回 true,用戶可以根據他們的業務進行覆蓋。
這里是定義了一個抽象類,也可以用接口,java 8 開始接口中可以有默認方法實現。
攔截器定義好之后,現在就可以在 ChannelHandler 中加入攔截器的方法調用了,如下圖所示:
當前置方法返回 false 時,直接返回,中斷后面的業務邏輯處理,最終會到 finally 中將結果寫入 response 中返回給前端。
現在只要實現 InterceptorHandler 中的兩個方法就可以了,其實這也很簡單,只要獲取到所有的 Interceptor 的實現類,然后依次調用這些實現類的前置方法和后置方法就好了,如下圖所示:
獲取攔截器
現在的重點就是怎樣獲取到所有的攔截器,首先可以想到的是通過掃描的方法,找到所有 Interceptor 的實現類,然后將這些實現類加入到一個 List 中即可。
那怎么保證攔截器的執行順序呢,很簡單,只要在加入 List 之前對他們進行排序就可以了。再定義一個 @Order 注解來表示排序的順序,然后用一個 Wrapper 包裝類將 Interceptor 和 Order 包裝起來,排序到包裝類的 List 中,最后再從包裝類的 List 中依次取出所有的 Interceptor 就完成了 Interceptor 的排序了。
知道了大致的原理之后,實現起來就很簡單了,如下圖所示:
但是我們不能每次都通過調用 scanInterceptors() 方法來獲取所有的攔截器,如果這樣每次都掃描一次的話性能會有影響,所以我們只需要第一次調用一下該方法,然后把結果保存在一個私有的變量中,獲取的時候直接讀取該變量的值即可,如下圖所示:
自定義攔截器實現類
下面讓我們來自定義兩個攔截器實現類,來驗證下具體的效果。
第一個攔截器,在前置方法中對請求參數進行判斷,如果請求參數中有 block=true 的參數,則進行攔截,如下圖所示:
第二個攔截器,在后置方法中打印出每次請求的耗時,如下圖所示:
通過 @Order 注解來指定執行的順序,先執行 BlockInterceptor 再執行 PerformanceInterceptor。
查看效果
現在我們請求 /user/info 這個接口,查看下效果。
首先我們只提交正常的參數,如下圖所示:
打印的結果如下圖所示:
從打印的結果中可以看到依次執行了:
BlockInterceptor 的 preHandle 方法
PerformanceInterceptor 的 preHandle方法
BlockInterceptor 的 postHandle 方法
PerformanceInterceptor 的 postHandle方法
這說明攔截器是按照 @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 和具體的實現類也要做響應的調整,如下圖所示:
重新執行剛剛的測試類,發現結果和之前的一樣,至此一個簡單的鏈式責任鏈完成了。
關注「逅弈逐碼」
查看更多原創好文
更多推薦內容
↓↓↓
《Sentinel 原理:控制臺是如何獲取到實時數據的》
《Sentinel 實戰:搭建集群限流環境》
《Netty 實戰:如何編寫一個麻小俱全的 Web 容器》
《讓人歡喜讓人憂的正則表達式》
總結
以上是生活随笔為你收集整理的wrapper怎么用_用责任链模式设计拦截器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux的实际操作:关机shutdow
- 下一篇: python读取命令行输入-python