Netty 5 遷移指南
為了讓 Netty 5 和 Netty 4 能夠共存於同一個 classpath 上,我們已將 Netty 5 類別的套件名稱變更為 io.netty5.*
。
Netty 5 導入了新的緩衝區 API,比 ByteBuf
更簡單且更安全的使用。
Netty 4.1 ByteBuf
會在編寫時自動擴充容量直至達到最大容量。
新的 Buffer
API 不再執行此動作,也不再區分容量和最大容量。程式碼應改為分配適當大小的緩衝區(大小參數現在為強制性),和/或視需要呼叫 ensureWritable()
。ensureWritable()
方法現在也可以接受允許壓縮(與舊有的 discardReadBytes()
相同)和擴充的參數,一次執行單一記憶體複製。
包含了轉換兩個 API 的轉接器,並允許它們共存,直至所有 handles 和相關程式碼已遷移為止。
ByteBufBuffer.wrap()
方法採用 ByteBuf
實例並回傳 Buffer
,也就是新 API 的緩衝區實例。相反地,ByteBufAdaptor.intoByteBuf
方法採用 Buffer
並回傳 ByteBuf
。這兩個方法會以最有效率的方式進行轉換,且多次轉換會互相抵消以避免巢狀轉接器。
在 BufferConversionHandler
中,它也可以插入仰賴不同 API 來處理的管線當中。請注意,BufferConversionHandler
無法轉換任何 ByteBufHolder
或 BufferHolder
物件。包含 Buffer
或 ByteBuf
實例的物件將需要使用自訂 MessageToMessageCodec
來轉換。
新 Buffer
API 中的主要差異在於不再允許別名。別名是在兩個或兩個以上的緩衝區物件參照相同的底層記憶體時產生。這表示方法(例如 slice()
和 duplicate()
)不再可用。另一方面,生命週期處理已簡化,且參照計數可以完全從 API 中移除。
只要您使用 slice()
、duplicate()
及其 retain*
變體,以及 retain()
方法系列,您現在改用 split()
、readSplit()
和 copy()
。
split
方法系列與 slice()
類似,但它們只能在緩衝區的尾端進行切片,且回傳的緩衝區切片會從原始緩衝區中移除,因此可以避免別名。
retain()
和 release()
方法已經消失。緩衝區改用 close()
方法,緩衝區的生命週期的結尾會呼叫它。緩衝區會實作 AutoCloseable
作為一種方便功能,只要緩衝區的範圍和生命週期完全在本地端。
在多數使用 retain()
的地方,實際上是為了取消超級類別或子類別中無條件 release()
的效用。在這種情況下,可以使用 split()
,因為這些情況通常涉及傳遞緩衝區的可讀區段,而 split()
會產生平行必須關閉的兩個緩衝區。
有一個新的 send()
方法,可用於在類型系統中編碼,表示緩衝區的「所有權」從一個地方移動到另一個地方。例如,CompositeBuffer
工廠方法用它來確保複合緩衝區取得對元件緩衝區的獨佔權限。這可防止透過緩衝區組合產生別名。
緩衝區現在總是為大端序,且 *LE
取用器方法已消失。若要執行小端序讀取或寫入作業,請使用 Integer
或 Long
中的 reverseBytes
方法,搭配大端序讀取或寫入。
為簡化 API 和類型層級,我們決定完全移除 ChannelFuture
/ ChannelPromise
(及其所有子類型 / 實作)。作為替代,可以直接使用 Future<Void>
和 Promise<Void>
。
ProgressiveFuture / ProgressivePromise 的支援已在 netty 5 中移除。這樣做的原因是,儘管它可能有時候會派上用場,但它也需要處理管線中所有的處理常式,如果這些處理常式連結承諾的話。這並非事實,而且實際上很麻煩。
基於上述問題,我們決定只移除這項功能,因為這項功能的使用率並不高。與其提供只能「偶爾」運作的功能,不如不提供支援。這也表示維護的程式碼會減少。
在 netty 4.1.x 中,可以使用 voidPromise()
方法取得特殊的 ChannelPromise
實作,此方法可搭配各種 IO 作業(例如 write
)來減少建立的物件數目。儘管這項功能的動機很好,但事實上,這個特殊的 ChannelPromise
實作的確帶來了許多問題
- 已移除
Future.addListeners()
、Future.removeListeners()
和Future.removeListener()
。我們已移除移除先前已新增監聽者的功能。這項功能並沒有被實際使用,因此讓我們得以移除部分複雜度並簡化部分 API 表面。 - 已新增
Future.isFailed()
方法,此方法會檢查未來是否已完成且失敗。這類似現有的Future.isSuccess()
,後者會檢查未來是否已完成且成功。 - 已新增
Future.map()
和Future.flatMap()
方法,此方法可輕鬆根據現有未來組合並建立新的未來。這些方法會透過傳遞適當地處理失敗及取消。 - 已新增轉換為
CompletionStage
的新方法,如此一來能夠更容易與其他 API 互動。 - 已移除
Future
介面中所有的封鎖方法,因為人們可能會誤用這些方法,進而封鎖EventLoop
。如果您仍需要從EventLoop
外部封鎖,則需要透過Future.asStage()
轉換Future
。回傳的FutureCompletionStage
會提供封鎖方法。
在 netty 5.x 中,我們將 executor()
方法新增到 ChannelOutboundInvoker
,由於此方法會回傳 EventExecutor
,我們決定從 Channel
移除 eventLoop()
方法,並僅覆寫 executor()
,使它為 Channel
回傳 EventLoop
。
由於我們已移除 ChannelFuture
/ ChannelPromise
,因此也將方法的傳回型別變更為 Future<Void>
Netty5 已在其核心支援半關閉。因此引入了 ChannelHandler.shutdown
和 ChannelHandler.channelShutdown
。此外,還新增了 Channel.isShutdown(...)
和 ChannelOutboundInvoker.shutdown(...)
。這取代了舊有的 DuplexChannel
抽象,該抽象已被完全移除。如需更多詳細資訊,請參閱 プルリクエスト #12468。
我們已變更 ChannelHandlerContext
,使其不再延伸 AttributeMap
。如果你使用屬性,你應直接使用仍延伸 AttributeMap
的 Channel
。
Channel.Unsafe
介面已被完全移除,因此終端使用者無法干擾內部系統。
ChannelOutboundBuffer
是我們的 AbstractChannel
實作的實作細節,因此已從 Channel
本身完全移除。
我們已移除 Channel.beforeBeforeWritable()
方法,因為它根本沒有使用過,並將 Channel.bytesBeforeUnwritable()
變更名稱為 Channel.writableBytes
。
在 netty 4.x 中,我們新增了使用明確的 EventExecutorGroup
將 ChannelHandler
新增至 ChannelPipeline
的功能。儘管這看似是一個好主意,但結果發現,在進入生命週期時會產生一些問題
-
handlerRemoved(...)
、handlerAdded(...)
可能在「錯誤的時間」被呼叫。這可能會造成許多問題。最糟糕的情況可能是呼叫handlerRemoved(...)
,而處理常式會解放一些原生記憶體,因為它預期以後都不會再使用該處理常式。然後,在呼叫此方法後,可能會呼叫channelRead(...)
,而channelRead(...)
會嘗試存取先前解放的記憶體,並因此導致 JVM 崩潰。 - 在執行緒存取/修改管線的情況下,正確實作「可見性」也很有問題。
考量到這一點,我們發現使用者實際上最想要的是,讓另一個執行緒處理接收到的訊息,以處理商業邏輯。這最好在由使用者提供的自訂實作中完成,因為使用者可以更妥善處理可以或不可以刪除的時機。
由於我們已移除 ChannelFuture
/ ChannelPromise
,因此也將方法的傳回型別變更為 Future<Void>
Netty 5 大幅簡化了 ChannelHandler
的類型階層。
ChannelInboundHandler
和 ChannelOutboundHandler
已被整合至 [ChannelHandler
]。[ChannelHandler
] 現在同時具備 inbound 和 outbound 處理程序方法。所有帶有 ChannelPromise
的 outbound 方法都已變更為回傳 Future<Void>
。此變更有助於減少錯誤,並簡化 API。
ChannelInboundHandlerAdapter
、ChannelOutboundHandlerAdapter
和 ChannelDuplexHandlerAdapter
已移除,並由 [ChannelHandlerAdapter
] 取代。
由於現在無法判斷處理程序是 inbound 還是 outbound 處理程序,因此 CombinedChannelDuplexHandler
已由 [ChannelHandlerAppender
] 取代。
有關此變更的詳細資訊,請參閱 協力要求 #1999。
如果您使用 [SimpleChannelInboundHandler
],您需要將 channelRead0()
重新命名為 messageReceived()
。
現在可以使用 fireChannelInboundEvent(...)
(取代至 fireUserEventTriggered(...)
) 和 sendOutboundEvent(...)
在管道中以上下游方向發送使用者/自訂事件。這兩種方式皆可透過定義在 ChannelHandler
中的方法攔截,就像以往一樣。
現在可以透過 ChannelPipeline
中的 ChannelHandler
輕鬆地影響 Channel
的可寫性。藉由這樣的變更,只要 ChannelHandler
本身會緩衝 outbound 資料,就能夠影響反壓。
雖然在 netty 4.x 中,我們使用不同的 EventLoopGroup
/ EventLoop
實作來應付各式不同的傳輸 (例如 NioEventLoopGroup
和 EpollEventLoopGroup
),我們已在 Netty 5 中變更為只使用一個叫做 MultiThreadEventLoopGroup
的 EventLoopGroup
實作。這個 MultiThreadEventLoopGroup
會使用特定於傳輸本身的 IoHandlerFactory
(例如 NioHandler.newFactory()
和 EpollHandler.newFactory()
)。這樣的變更帶來許多優點。舉例來說,很容易擴充 MultiThreadEventLoopGroup
,並為其加入裝飾或新增自訂指標等等。這樣一來,這個實作就可以在不同的傳輸類型中重複使用。這與 JDK 中提供、具有自訂化可能性的 ThreadPoolExecutor
非常類似。
在嘗試使用前,現在可以檢查 Channel
子類型是否與 EventLoopGroup
/ EventLoop
相容。這有助於選取正確的 Channel
子類型。
已新增方法至 EventLoop
介面,以允許註冊和取消註冊 Channel
。使用者不應自行使用這些方法,但可由 Channel
實作本身使用。
為了精簡程式碼庫並減輕維護負擔,下列編解碼器和處理常式已移至 Netty Contrib
- netty-codec-xml
- netty-codec-redis
- netty-codec-memcache
- netty-codec-stomp
- netty-codec-haproxy
- netty-codec-mqtt
- netty-codec-socks
- netty-handler-proxy
io.netty.handler.codec.json
io.netty.handler.codec.marshalling
io.netty.handler.codec.protobuf
io.netty.handler.codec.serialization
io.netty.handler.codec.xml
io.netty.handler.pcap