5.0における新機能と注目すべき変更点
このドキュメントでは、Nettyのメジャーリリース(4.1以降)における注目すべき変更点と新機能の一覧を説明し、アプリケーションを新しいバージョンに移植するためのアイデアを提供します。
3.xと4.0の間の変更とは異なり、5.0では設計のシンプルさにおいて大きな進歩を遂げましたが、変更点はそれほど多くありません。4.xから5.0への移行をできるだけスムーズに行えるように努めていますが、移行中に問題が発生した場合はお知らせください。
Netty 5では、ByteBufよりもシンプルで安全に使用できる新しいBuffer APIが導入されました。詳細は、プルリクエスト #11347 を参照してください。要約すると、新しいAPIには以下の主要な違いがあります。
- エイリアシングは許可されなくなりました。つまり、複数のバッファが同じメモリを参照することはできなくなりました。
- これは、`slice`、`duplicate`、およびそれらの保持バリアントがなくなったことを意味します。
- 代替として、`split`、`readSplit`、`send` という新しいAPIが導入されました。これらのメソッドの契約により、エイリアシングが防止されます。
- 参照カウントは事実上なくなりました。
- `retain` および `release` メソッドはなくなりました。代わりにバッファには `close` メソッドがあり、バッファのライフタイムの終わりに呼び出されます。
- APIと統合は、バッファが常に一意で明確な所有権を持つように設計する必要があります。参照カウントを実行時に借用または参照を追跡するために使用できなくなったため、APIではバッファの「借用」を最小限に抑え、推奨しません。
- `retain` が使用されていたほとんどの場所は、スーパークラスの無条件の `release` の影響をキャンセルしようとしていただけでした。これらの場合、バッファの読み取り可能な部分のみを渡したいだけなので、`split` を使用できます。
- `send` メソッドと `Send` インターフェースを使用して、型システムでの所有権の転送をエンコードできます。
- バッファは常にビッグエンディアンであり、`*LE` メソッドはなくなりました。
- リトルエンディアンの読み取りまたは書き込みを実行するには、バッファのビッグエンディアンの読み取りおよび書き込みメソッドと組み合わせて、`Integer`、`Long` などの `reverseBytes` ファミリのメソッドを使用します。
- `BufferUtil` には、「ミディアム」(3バイト)整数を反転するためのメソッドがあります。
- バッファ実装のテストカバレッジが高くなり、動作の一貫性が向上しました。
`ChannelInboundHandler` と `ChannelOutboundHandler` は、[ `ChannelHandler` ] にマージされました。[ `ChannelHandler` ] には、インバウンドハンドラーメソッドとアウトバウンドハンドラーメソッドの両方があります。
`ChannelInboundHandlerAdapter`、`ChannelOutboundHandlerAdapter`、および `ChannelDuplexHandlerAdapter` は削除され、`ChannelHandlerAdapter` に置き換えられました。
ハンドラーがインバウンドハンドラーなのかアウトバウンドハンドラーなのかを判別できなくなったため、`CombinedChannelDuplexHandler` は `ChannelHandlerAppender` に置き換えられました。
この変更の詳細については、プルリクエスト #1999 を参照してください。
`SimpleChannelInboundHandler` を使用している場合は、`channelRead0()` を `messageReceived()` に名前変更する必要があります。
すべての `ChannelHandler` アウトバウンドメソッド(`flush` と `read` を除く)は、`Future<Void>` を返すようになりました。これにより、適切な伝播と連鎖が保証されます。これに加えて、`exceptionCaught(...)` を `channelExceptionCaught(...)` に名前変更して、これがインバウンド例外を処理することを明確にしました。
パイプラインを介して両方向にユーザー/カスタムイベントを発生させることができるようになりました。インバウンドイベントの場合は `fireChannelInboundEvent(...)`(`fireUserEventTriggered(...)` の置き換え)、アウトバウンドイベントの場合は `sendOutboundEvent(...)` を使用します。これらの両方は、通常どおり `ChannelHandler` で定義されたメソッドによってインターセプトできます。
`ChannelPipeline` に含まれる `ChannelHandler` によって `Channel` の書き込み可能性に影響を与えることが容易になりました。これにより、`ChannelHandler` 自体がアウトバウンドデータをバッファリングする場合に、バックプレッシャーに影響を与えることができます。
Netty5は、コアにハーフクロージャーのサポートを組み込みました。このため、`ChannelHandler.shutdown` と `ChannelHandler.channelShutdown` が導入されました。これに加えて、`Channel.isShutdown(...)` と `ChannelOutboundInvoker.shutdown(...)` も追加されました。これらは、完全に削除された古い `DuplexChannel` 抽象化を置き換えます。詳細は、プルリクエスト #12468 を参照してください。
ChannelHandlerContextがAttributeMapを拡張しないように変更しました。属性を使用している場合は、引き続き `AttributeMap` を拡張する `Channel` を直接使用する必要があります。
netty 4.xでは、明示的な `EventExecutorGroup` を使用して `ChannelPipeline` に `ChannelHandler` を追加する機能を追加しました。これは良いアイデアのように見えましたが、ライフサイクルに関してはかなりの問題があることがわかりました。
- `handlerRemoved(...)`、`handlerAdded(...)` が「間違ったタイミング」で呼び出される可能性があります。これは多くの問題につながる可能性があります。最悪の場合、`handlerRemoved(...)` が呼び出され、ハンドラーがネイティブメモリを解放することを意味する可能性があります。これは、ハンドラーが今後使用されないと予想されるためです。ここで起こりうることは、それが呼び出された後、`channelRead(...)` が呼び出され、以前に解放されたメモリにアクセスしようとしてJVMがクラッシュする可能性があることです。
- パイプラインの同時アクセス/変更に関して「可視性」を正しく実装することも非常に問題があります。
これを念頭に置いて、ユーザーが本当に望んでいるのは、ビジネスロジックを処理するために着信メッセージを別のスレッドで処理することであることに気づきました。これは、ユーザーがいつ何が破棄できるかをより適切に処理できるため、ユーザーが提供するカスタム実装で行う方が適切です。
netty 5.xでは、`executor()` メソッドを `ChannelOutboundInvoker` に追加し、このメソッドが `EventExecutor` を返すため、`Channel` から `eventLoop()` メソッドを削除し、`executor()` をオーバーライドして `Channel` に `EventLoop` を返すことにしました。
使用する前に、`Channel` サブタイプが `EventLoopGroup` / `EventLoop` と互換性があるかどうかを確認できるようになりました。これは、正しい `Channel` サブタイプを選択するのに役立ちます。
`Channel` の登録と登録解除を可能にするために、`EventLoop` インターフェースに新しいメソッドが追加されました。これらはユーザーが直接使用するのではなく、`Channel` 実装自体によって使用する必要があります。
Channel.Unsafeインターフェースは完全に削除されたため、エンドユーザーが内部を混乱させることはできません。
`ChannelOutboundBuffer` は `AbstractChannel` 実装の実装の詳細であるため、`Channel` 自体からは完全に削除されました。
`Channel.beforeBeforeWritable()` が削除され、`Channel.bytesBeforeUnwritable()` が `writableBytes()` に名前変更されました。
まったく使用されていなかったため、`Channel.beforeBeforeWritable()` メソッドを削除し、`Channel.bytesBeforeUnwritable()` を `Channel.writableBytes` に名前変更しました。
`ProgressiveFuture` / `ProgressivePromise` と `ChannelProgressiveFuture` / `ChannelProgressivePromise` の削除
Progressive * Future / Progressive * Promiseのサポートは、netty 5で完全に削除されました。理由は、これが時々役に立つ場合もある一方で、パイプライン内のすべてのハンドラーがプロミスを連鎖させる場合に特別なアクションを実行する必要があるためです。これは実際にはそうではなく、実際には非常に面倒です。
記載されている問題のため、この機能の使用方法があまりなかったため、この機能を完全に削除するのが最善であると判断しました。「時々」だけ機能するものよりも、まったくサポートしない方がましです。また、これにより、保守するコードが少なくなります。
netty 4.1.xでは、`voidPromise()` メソッドを使用して、作成されるオブジェクトの数を減らすためにさまざまなIO操作(`write` など)に使用できる特別な `ChannelPromise` 実装を取得できました。この機能の動機は良かったのですが、この `ChannelPromise` の特別なケースは実際には多くの問題を引き起こすことがわかりました。
- プロミスに `ChannelFutureListener` を追加した各 `ChannelHandler` は、リスナーを追加しても安全であることを確認するために、最初に `unvoid()` を呼び出す必要がありました。そうしないと、`addListener` が呼び出されると `RuntimeException` が発生します。
- wait()/ sync()操作はまったくサポートされていませんでした。
- 一部の操作では `VoidChannelPromise` を使用でき、一部の操作では使用できませんでした。
Future.addListeners()
、Future.removeListeners()
、およびFuture.removeListener()
が削除されました。追加されたリスナーを削除する機能を削除しました。この機能は実際には使用されていなかったため、複雑さを軽減し、API サーフェスを削減することができました。sync
メソッドとawait
メソッドの割り込み不可能なバリアントが削除されました。- future が完了して失敗したことを確認する
Future.isFailed()
メソッドが追加されました。これは、future が完了して正常に完了したことを確認する既存のFuture.isSuccess()
と似ています。 -
Promise.setUncancellable
は、promise が「未完了」から「キャンセル不可」に遷移した場合にのみtrue
を返すようになりました。具体的には、このメソッドは、promise がすでに完了している場合にfalse
を返すようになりました。4.1 では、このような場合にtrue
を返していました。 - 既存の future に基づいて新しい future を簡単に構成および作成できる、新しい
Future.map()
メソッドとFuture.flatMap()
メソッドが追加されました。これらのメソッドは、伝播を通じて失敗とキャンセルを適切に処理します。 Future
インターフェースからすべてのブロッキングメソッドが削除されました。これは、ユーザーがこれらのメソッドを誤用してEventLoop
をブロックしやすいためです。EventLoop
の外部からブロックする必要がある場合は、Future.asStage()
を介してFuture
を変換する必要があります。返されたFutureCompletionStage
はブロッキングメソッドを提供します。
すべての圧縮実装が、新しい 圧縮 API を使用するように変更されました。これにより、追加の EmbeddedChannel
を作成することなく、異なるコーデックで再利用しやすくなります。
- netty-core からマルチパートのサポートを削除しました。これは、将来、コントリビューターリポジトリとして Netty 5 に移行される予定です。(https://github.com/netty/netty/pull/11830)
- 古い websocket ドラフトを削除しました (https://github.com/netty/netty/pull/11831)
- HTTP/2 ヘッダー検証がデフォルトで有効になりました。これにより、不正な形式のリクエストはデフォルトで拒否されます。
コードベースをスリム化し、メンテナンスの負担を軽減するために、以下のコーデックとハンドラーが 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
Netty は、ネイティブイメージで最小限のサイズオーバーヘッドで、実行時に自動的に初期化されるようになりました。サポートされる Graal の最小バージョンは 22.1、Java 17 です。