1. 程式人生 > >ReplayingDecoder 解碼器:別以為我有多厲害,也只不過是使用了一下裝飾器模式而已~

ReplayingDecoder 解碼器:別以為我有多厲害,也只不過是使用了一下裝飾器模式而已~

#[原文地址](https://blog.csdn.net/Howinfun/article/details/108160762) # 一、設計模式為啥老是用不好? 想要寫出更屌的程式碼,提高程式碼的健壯性和可擴充套件性,那麼設計模式可謂是必學的技能。 關於學習設計模式,大家可能都覺得設計模式的概念太過於抽象,理解起來有點費勁;又或者看的時候是理解了,但是寫起程式碼時,卻毫無頭緒,壓根不知道可以套用哪個設計模式。 對,可以看到我使用了 “套” 這個字眼,正是因為我們無法深入理解設計模式的設計理念和使用場景,所以我們往往是想讓我們的程式碼套用設計模式,而不理會業務場景是否合適。 關於設計模式的學習,我不會推薦任何書,因為我自己也沒看過,哈哈哈。我看過的是龍哥的設計模式系列文章,裡面的文章不但會介紹設計模式的概念,也會用非常有趣的場景去講解設計模式的設計理念,下面先分享一波連結:[龍哥設計模式全集](https://blog.csdn.net/zuoxiaolong8810/category_1434962.html)。 對於我自己而言,關於設計模式的使用,除非是非常深刻的理解了,又或者某種設計模式的使用場景非常的清晰明確(例如建立型設計模式中的單例模式、結構型設計模式中的組合模式、行為型設計模式中的策略模式等等),不然我也不知道該如何使用,和什麼時候使用。 # 二、在閱讀開源框架原始碼中學習設計模式! **想學習設計模式的使用方式,何不研究一下各大優秀的開源框架的原始碼。** 想更深層次的理解設計模式,往往閱讀優秀的框架和中介軟體的原始碼是非常好的方式。優秀的開源框架和中介軟體,裡面都使用了大量的設計模式,使得框架的實用性、可擴充套件性和效能非常的高。 很巧,今天在工作的空餘時間中,我繼續閱讀一本關於併發的書,並看到關於 Netty 的內建解碼器,其中最常用的有 ReplayingDecoder,它是 ByteToMessageDecoder 的子類,作用是: 在讀取ByteBuf緩衝區的資料之前,需要檢查緩衝區是否有足夠的位元組;若ByteBuf中有足夠的位元組,則會正常讀取;反之,如果沒有足夠的位元組,則會停止解碼。 它是如何做到自主控制解碼的時機的呢?其實底層是使用了 ReplayingDecoderByteBuf 這個繼承於 ByteBuf 的實現類。而它使用了裝飾器設計模式。 ## 1、在 Netty 中如何自定義實現整數解碼器? ### 1.1、ByteToMessageDecoder: 我們需要自定義類需要繼承 ByteToMessageDecoder 抽象類,然後重寫 decode 方法即可。 看程式碼: ```java /** * @author Howinfun * @desc * @date 2020/8/21 */ public class MyIntegerDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { while (byteBuf.readableBytes() >= 4){ int num = byteBuf.readInt(); System.out.println("解碼出一個整數:"+num); list.add(num); } } } ``` 我們可以看到非常的簡單,就是不斷地判斷緩衝區裡的的可讀位元組數是否大於等於4(Java 中整數的大小);如果是的話就讀取4個位元組大小的內容,然後放到結果集裡面。 ### 1.2、ReplayingDecoder: 我們需要自定義類需要繼承 ReplayingDecoder 類,然後重寫 decode 方法即可。 看程式碼: ```java /** * @author Howinfun * @desc * @date 2020/8/21 */ public class MyIntegerDecoder2 extends ReplayingDecoder { @Override protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { int num = byteBuf.readInt(); System.out.println("解碼出一個整數:"+num); list.add(num); } } ``` 這個實現更加簡單,那就是去掉判斷,直接呼叫 ByteBuf 的 readInt() 方法去獲取整數即可。 ### 1.3、測試用例: **1.3.1、自定義業務處理器:** 先建立一個業務處理器 IntegerProcessHandler,用於處理上面的自定義解碼器解碼之後的 Java Integer 整數。其功能是:讀取上一站的入站資料,把它轉換成整數,並且輸出到Console控制檯上。 碼如下: ```java /** * @author Howinfun * @desc * @date 2020/8/21 */ public class IntegerProcessorHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { Integer integer = (Integer) msg; System.out.println("打印出一個整數:"+integer); } } ``` 這個業務處理器非常的簡單,直接繼承 ChannelInBoundHandlerAdapter,然戶重寫 channelRead() 方法即可。 **1.3.2、利用 EmbeddedChannel 進行測試:** 為了測試入站處理器,需要確保通道能接收到 ByteBuf 入站資料。這裡呼叫 writeInbound 方法,模擬入站資料的寫入,向嵌入式通道 EmbeddedChannel 寫入100次 ByteBuf 入站緩衝;每一次寫入僅僅包含一個整數。 EmbeddedChannel 的 writeInbound 方法模擬入站資料,會被流水線上的兩個入站處理器所接收和處理。接著,這些入站的二進位制位元組被解碼成一個一個的整數,然後逐個地輸出到控制檯上。 看程式碼: ```java public class Test{ public static void main(String[] args){ ChannelInitializer i = new ChannelInitializer() { @Override protected void initChannel(EmbeddedChannel channel) throws Exception { // 繼承 ByteToMessageDecoder 抽象類的自定義解碼器 // channel.pipeline().addLast(new MyIntegerDecoder()).addLast(new IntegerProcessorHandler()); // 繼承 ReplayingDecoder 類的自定義解碼器 channel.pipeline().addLast(new MyIntegerDecoder2()).addLast(new IntegerProcessorHandler()); } }; EmbeddedChannel channel = new EmbeddedChannel(i); for (int j = 0;j < 20;j++){ ByteBuf byteBuf = Unpooled.buffer(); byteBuf.writeInt(j); channel.writeInbound(byteBuf); } ThreadUtil.sleep(Integer.MAX_VALUE); } } ``` 通過測試,兩個自定義 Decoder 都是沒問題的。而他們的最大不同點在於:繼承抽象類 ByteToMessageDecoder 的解碼器需要判斷可讀位元組數是否大於等於4,大於等於才可以讀取一個整數出來;而繼承 ReplayingDecoder 的解碼器直接呼叫 readInt() 方法即可。 ## 2、解讀 ReplayingDecoder 的原理 其實其中的原理非常的簡單,我們可以直接從 ReplayingDecoder 的原始碼入手: ### 2.1、ReplayingDecoder的建構函式: 首先是建構函式,此處我們用了無參建構函式: ```java protected ReplayingDecoder() { this((Object)null); } protected ReplayingDecoder(S initialState) { this.replayable = new ReplayingDecoderByteBuf(); this.checkpoint = -1; this.state = initialState; } ``` 我們可以看到,主要是初始化了 ReplayingDecoderByteBuf(其實就是加了點料的 ByteBuf)、checkpoint(讀指標下標) 和 state。我們這篇文章不需要理會 state 屬性,這個屬性是稍微高階一點的用法。 我們最需要關注的是 ReplayingDecoderByteBuf 這個類。 ### 2.2、繼續探討 ReplayingDecoderByteBuf: 那麼接下來看看 ReplayingDecoderByteBuf 的原始碼。 **2.2.1、ReplayingDecoderByteBuf 的屬性:** ```java final class ReplayingDecoderByteBuf extends ByteBuf { private static final Signal REPLAY; private ByteBuf buffer; private boolean terminated; private SwappedByteBuf swapped; static final ReplayingDecoderByteBuf EMPTY_BUFFER; ReplayingDecoderByteBuf() { } //... } ``` 我們可以看到,它繼承了 ByteBuf 抽象類,並且裡面包含一個 ByteBuf 型別的 buffer 屬性,剩餘的其他屬性暫時不需要看懂。 **2.2.2、瞧一瞧 readInt() 方法:** 那麼接下來,我們就是直接看 ReplayingDecoderByteBuf 的 readInt() 方法了,因為我們知道,在上面的自定義解碼器 MyIntegerDecoder2 的 decode() 方法中,只需要直接呼叫 ByteBuf(也就是 ReplayingDecoderByteBuf) 的 readInt() 方法即可解碼一個整數。 ```java public int readInt() { this.checkReadableBytes(4); return this.buffer.readInt(); } ``` readInt() 方法非常簡單,首先是呼叫 checkReadableBytes() 方法,並且傳入 4。根據方法名,我們就可以猜到,先判斷緩衝區中是否有4個可讀位元組;如果是的話,就呼叫 buffer 的 readInt() 方法,讀取一個整數。 **2.2.3、繼續看看 checkReadableBytes() 方法:** 程式碼如下: ```java private void checkReadableBytes(int readableBytes) { if (this.buffer.readableBytes() < readableBytes) { throw REPLAY; } } ``` 方法非常簡單,其實和我們上面的 MyIntegerDecoder 一樣,就是判斷緩衝區中是否有 4個位元組的可讀資料,如果不是的話,則丟擲異常。 **2.2.4、Signal 異常:** 而我們最需要關注的就是這個異常,這個異常是 ReplayingDecoder 的靜態成員變數。它是繼承了 error 的異常類,是 netty 提供配合 ReplayingDecoder 一起使用的。 至於如何使用,我們可以看到 ReplayingDecoder 的 callDecode() 方法: ```java protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List out) { // 呼叫 ReplayingDecoderByteBuf 的 setCumulation() 方法,使用 ReplayingDecoderByteBuf 裝飾 ByteBuf this.replayable.setCumulation(in); try { while(in.isReadable()) { int oldReaderIndex = this.checkpoint = in.readerIndex(); int outSize = out.size(); if (outSize > 0) { // 將結果集流到下一個 InBoundChannel fireChannelRead(ctx, out, outSize); out.clear(); if (ctx.isRemoved()) { break; } outSize = 0; } S oldState = this.state; int oldInputLength = in.readableBytes(); try { // 呼叫自定義解碼器的 decode() 方法進行解碼 this.decodeRemovalReentryProtection(ctx, this.replayable, out); if (ctx.isRemoved()) { break; } if (outSize == out.size()) { if (oldInputLength == in.readableBytes() && oldState == this.state) { throw new DecoderException(StringUtil.simpleClassName(this.getClass()) + ".decode() must consume the inbound data or change its state if it did not decode anything."); } continue; } } catch (Signal var10) { // 如果不是 Sinal 異常,則往外拋 var10.expect(REPLAY); if (!ctx.isRemoved()) { // 設定讀指標為原來的位置 int checkpoint = this.checkpoint; if (checkpoint >= 0) { in.readerIndex(checkpoint); } } break; } // ...... } } catch (DecoderException var11) { throw var11; } catch (Exception var12) { throw new DecoderException(var12); } } ``` 到這裡,我們可以捋一下思路: 1. 當緩衝區資料流到繼承 ReplayingDecoder 的解碼器時,會先判斷結果集是否有資料,如果有則流入到下一個 InBoundChannel; 2. 接著會呼叫自定義解碼器的 decode() 方法,而這裡就是是直接呼叫 ByteBuf 的 readInt() 方法,即 ReplayingDecoderByteBuf 的 readInt() 方法;裡面會先判斷可讀位元組大小是否大於 4,如果大於則讀取,否則丟擲 Signal 這個 Error 型別的異常。 3. 如果 ReplayingDecoder 捕捉 Signal 這個異常,會先判斷 checkpoint(即讀指標下標不) 是否為零,如果不是則重新設定讀指標下標,然後跳出讀迴圈。 ReplayingDecoder 能做到自主控制解碼的時機,是因為使用 ReplayingDecoderByteBuf 對 ByteBuf 進行修飾,在呼叫 ByteBuf 的方法前,會先呼叫自己的判斷邏輯,這也就是我們常說的裝飾器模式。 # 三、裝飾器模式的特點 首先,被裝飾的類和裝飾類都是繼承同一個類(抽象類)或實現同一個介面。 接著,被裝飾類會作為裝飾類的成員變數。 最後,在執行被裝飾類的方法前後,可能會呼叫裝飾類的方法。 **場景總結:** 裝飾器模式常用於這麼一個場景:在不修改類的狀態(屬性或行為)下,對類的功能進行擴充套件! 當然啦,這是我自己個人的總結,大家可去閱讀專業的書籍來證實這是否正確。如果有更好的總結,可以留言給我,讓我也學習