Netty 5 移行ガイド
Netty 5とNetty 4を同じクラスパス上に共存させるために、Netty 5クラスのパッケージ名をio.netty5.*
に変更しました。
Netty 5では、ByteBuf
よりもシンプルで安全な新しいバッファAPIが導入されました。
Netty 4.1のByteBuf
は、書き込み時に必要に応じて、特定の最大容量に達するまで自動的に容量を拡張します。
新しいBuffer
APIでは、この動作は行われなくなり、容量と最大容量の区別もなくなりました。代わりに、適切なサイズのバッファを割り当てる(サイズの引数は必須になりました)か、必要に応じてensureWritable()
を呼び出す必要があります。ensureWritable()
メソッドは、単一のメモリコピーでコンパクション(旧discardReadBytes()
と同じ)と拡張を同時に実行できる引数も受け入れるようになりました。
2つのAPI間の変換を行い、すべてのハンドルと関連コードの移行が完了するまで共存できるようにするアダプターセットが含まれています。
ByteBufBuffer.wrap()
メソッドはByteBuf
インスタンスを受け取り、新しいAPIのバッファインスタンスであるBuffer
を返します。逆に、ByteBufAdaptor.intoByteBuf
メソッドはBuffer
を受け取り、ByteBuf
を返します。これらのメソッドはどちらも可能な限り効率的な方法で変換を行い、複数回の変換はアダプターのネストを避けるために互いに打ち消し合います。
異なるAPIに依存するハンドルの間にパイプラインに挿入できるBufferConversionHandler
も含まれています。BufferConversionHandler
は、ByteBufHolder
またはBufferHolder
オブジェクトを変換できません。Buffer
またはByteBuf
インスタンスを含むオブジェクトは、例えばカスタムMessageToMessageCodec
を使用して変換する必要があります。
新しいBuffer
APIの重要な違いは、エイリアシングが許可されなくなったことです。エイリアシングとは、2つ以上のバッファオブジェクトが同じ基となるメモリを参照することです。つまり、slice()
やduplicate()
などのメソッドは使用できなくなりました。一方、ライフサイクルの処理が簡素化され、参照カウントをAPIから完全に削除できます。
slice()
、duplicate()
、およびそれらのretain*
バリアント、およびretain()
メソッドファミリを使用していた場所では、代わりにsplit()
、readSplit()
、およびcopy()
を使用します。
split
メソッドファミリはslice()
に似ていますが、バッファの端でのみスライスでき、返されたバッファスライスは元のバッファから削除されるため、エイリアシングを防ぎます。
retain()
メソッドとrelease()
メソッドはなくなりました。代わりにバッファにはclose()
メソッドがあり、これはバッファのライフタイムの最後に呼び出されます。バッファは、バッファのスコープとライフタイムが完全にローカルである場合の便宜のためにAutoCloseable
を実装しています。
retain()
が使用されていたほとんどの場所は、事実上、スーパークラスまたはサブクラスの無条件のrelease()
の効果をキャンセルするためでした。これらのケースでは、通常、バッファの読み取り可能なセクションの受け渡しに関与するため、split()
を使用できます。split()
は、それぞれ閉じなければならない2つのバッファになります。
バッファの「所有権」が場所間で移動することを型システムでエンコードするために使用できる新しいsend()
メソッドがあります。たとえば、これはCompositeBuffer
ファクトリメソッドによって、複合バッファがコンポーネントバッファへの排他的アクセスを取得することを保証するために使用されます。これにより、バッファ合成によるエイリアシングを防ぎます。
バッファは常にビッグエンディアンになり、*LE
アクセサーメソッドはなくなりました。リトルエンディアンの読み取りまたは書き込みを実行するには、ビッグエンディアンの読み取りまたは書き込みと組み合わせて、Integer
またはLong
のreverseBytes
メソッドを使用します。
APIと型階層を簡素化するために、ChannelFuture
/ ChannelPromise
(およびそのすべてのサブタイプ/実装)を完全に削除することにしました。代替として、Future<Void>
とPromise<Void>
を直接使用します。
Progressive*Future / Progressive*Promiseのサポートは、Netty 5で完全に削除されました。これは、場合によっては役立つ可能性がありますが、パイプライン内のすべてのハンドラが、Promiseをチェーンする場合に特別なアクションを実行する必要があるためです。実際にはそうではなく、実際に行うのは非常に面倒です。
上記の理由から、この機能はあまり使用されていなかったため、この機能を完全に削除するのが最善であると判断しました。「時々」しか機能しないものよりも、全くサポートしない方が良いでしょう。これにより、維持するコードも少なくなります。
Netty 4.1.xでは、voidPromise()
メソッドを使用して、さまざまなIO操作(write
など)に使用できる特別なChannelPromise
実装を取得し、作成されるオブジェクトの数を減らすことができました。この機能の動機は良かったのですが、この特別なChannelPromise
は実際には多くの問題を引き起こすことが判明しました。
Future.addListeners()
、Future.removeListeners()
、およびFuture.removeListener()
は削除されました。以前に追加されたリスナーを削除する機能を削除しました。この機能は実際には使用されていなかったため、複雑さを軽減し、APIサーフェースを削減することができました。- Futureが完了していて失敗しているかどうかを確認する
Future.isFailed()
メソッドが追加されました。これは、Futureが完了していて成功しているかどうかを確認する既存のFuture.isSuccess()
と同様です。 - 既存のFutureに基づいて新しいFutureを簡単に構成および作成できる
Future.map()
メソッドとFuture.flatMap()
メソッドが追加されました。これらのメソッドは、伝播を通じてエラーとキャンセルを適切に処理します。 CompletionStage
に変換するための新しいメソッドが追加され、他のAPIとの相互運用が容易になりました。Future
インターフェースからすべてのブロッキングメソッドが削除されました。これは、人々が誤ってこれらを使用し、EventLoop
をブロックすることが容易だったためです。EventLoop
の外側からブロックする必要がある場合は、Future.asStage()
を使用してFuture
を変換する必要があります。返されたFutureCompletionStage
はブロッキングメソッドを提供します。
Netty 5.xでは、ChannelOutboundInvoker
にexecutor()
メソッドを追加しました。このメソッドはEventExecutor
を返すため、Channel
からeventLoop()
メソッドを削除し、Channel
に対してEventLoop
を返すようにexecutor()
をオーバーライドすることにしました。
ChannelFuture
/ ChannelPromise
を削除したため、メソッドの戻り値の型もFuture<Void>
に変更しました。
Netty 5は、そのコアにハーフクロージャのサポートを組み込んでいます。そのため、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(...)
が呼び出され、以前解放されたメモリにアクセスしようとしてJVMがクラッシュすることです。 - パイプラインの同時アクセス/変更に関して「可視性」を正しく実装することも非常に問題です。
これを考慮して、ユーザーが主に望んでいるのは、着信メッセージを別のスレッドで処理してビジネスロジックを処理することであることに気付きました。これは、ユーザーがいつ何を破棄できるかをよりよく把握しているため、ユーザーが提供するカスタム実装で実行する方が適切です。
ChannelFuture
/ ChannelPromise
を削除したため、メソッドの戻り値の型もFuture<Void>
に変更しました。
Netty 5は、ChannelHandler
の型階層を大幅に簡素化しました。
ChannelInboundHandler
とChannelOutboundHandler
は[ChannelHandler
]にマージされました。[ChannelHandler
]には、インバウンドハンドラメソッドとアウトバウンドハンドラメソッドの両方があります。ChannelPromise
を受け取るすべてのアウトバウンドメソッドは、Future<Void>
を返すように変更されました。この変更により、使用がエラーになりにくくなり、APIが簡素化されます。
ChannelInboundHandlerAdapter
、ChannelOutboundHandlerAdapter
、およびChannelDuplexHandlerAdapter
は削除され、[ChannelHandlerAdapter
] に置き換えられました。
ハンドラーが入力ハンドラーか出力ハンドラーかを判別できなくなったため、CombinedChannelDuplexHandler
は[ChannelHandlerAppender
]に置き換えられました。
この変更に関する詳細については、プルリクエスト #1999 を参照してください。
[SimpleChannelInboundHandler
]を使用している場合は、channelRead0()
をmessageReceived()
に名前を変更する必要があります。
パイプラインを通して双方向にユーザー/カスタムイベントを発生させることが可能になりました。入力イベントにはfireChannelInboundEvent(...)
(fireUserEventTriggered(...)
の代替)、出力イベントにはsendOutboundEvent(...)
を使用します。これらはどちらも、通常のChannelHandler
で定義されたメソッドによってインターセプトできます。
ChannelPipeline
内のChannelHandler
によって、Channel
の書き込み可能性に影響を与えることが容易になりました。これにより、ChannelHandler
自体が出力データをバッファリングする場合のバックプレッシャーに影響を与えることができます。
Netty 4.xでは、さまざまなトランスポート(つまり、NioEventLoopGroup
、EpollEventLoopGroup
など)に対して異なるEventLoopGroup
/EventLoop
の実装がありましたが、Netty 5では、MultiThreadEventLoopGroup
と呼ばれる単一EventLoopGroup
実装に変更されました。このMultiThreadEventLoopGroup
は、トランスポート固有のIoHandlerFactory
(つまり、NioHandler.newFactory()
、EpollHandler.newFactory()
など)を受け取ります。この変更により、多くの利点があります。たとえば、MultiThreadEventLoopGroup
を容易に拡張して、機能を装飾したり、カスタムメトリクスを追加したりすることができます。この実装は、さまざまなトランスポートで再利用できます。これは、カスタマイズの可能性という点で、JDKがThreadPoolExecutor
で提供するものと非常に似ています。
使用する前に、Channel
サブタイプがEventLoopGroup
/EventLoop
と互換性があるかどうかを確認できるようになりました。これにより、適切なChannel
サブタイプを選択できます。
Channel
の登録と登録解除を可能にするために、EventLoop
インターフェースに新しいメソッドが追加されました。これらはユーザーが直接使用するのではなく、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