5.0 中的新增功能和注意事项
此文档将引导您了解 Netty 主要版本(自 4.1 以来)中值得注意的更改和新功能列表,以便您了解如何将应用程序移植到新版本。
与 3.x 和 4.0 之间的更改不同,5.0 并没有改变很多,尽管它在设计简洁性方面取得了相当大的突破。我们尝试尽可能平滑地过渡到 4.x 到 5.0,但如果您在迁移过程中遇到任何问题,请告诉我们。
Netty 5 引入了新的 Buffer API,它比 ByteBuf 更简单、更安全。有关详细信息,请参阅 请求拉取 #11347。总之,新 API 具有以下主要区别
- 不再允许别名。换句话说,您不能再让多个缓冲区引用同一内存。
- 这意味着
slice
、duplicate
以及它们的保留变体已消失。 - 引入了新 API 作为替代:
split
、readSplit
和send
。这些方法的契约禁止别名。
- 这意味着
- 引用计数已有效消失。
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
将 ChannelHandler
添加到 ChannelPipeline
的功能。虽然这看起来像一个好主意,但事实证明,在生命周期方面存在一些问题
-
handlerRemoved(...)
、handlerAdded(...)
可能会在“错误时间”调用。这会导致很多问题。最坏的情况可能是调用handlerRemoved(...)
,并且处理程序释放了一些本机内存,因为它期望该处理程序永远不会再使用。这里可能发生的情况是,在调用它之后,可能会调用channelRead(...)
,然后尝试访问先前释放的内存,从而导致 JVM 崩溃。 - 正确实现管道并发访问/修改方面的“可见性”也相当有难度。
考虑到这一点,我们意识到用户最想要的是让传入消息由另一个线程处理,以处理业务逻辑。最好由用户提供自定义实现来完成此操作,因为用户可以更好地控制何时可以销毁事物或不能销毁事物。
在 netty 5.x 中,我们向 ChannelOutboundInvoker
添加了 executor()
方法,并且由于此方法返回 EventExecutor
,因此我们决定从 Channel
中删除 eventLoop()
方法,并仅覆盖 executor()
以返回 Channel
的 EventLoop
。
现在可以在尝试使用 Channel
子类型之前检查它是否与 EventLoopGroup
/ EventLoop
兼容。这有助于选择正确的 Channel
子类型。
向 EventLoop
接口添加了新方法,以允许注册和取消注册 Channel
。这些方法不应由用户直接使用,而应由 Channel
实现本身使用。
Channel.Unsafe 接口已完全删除,因此最终用户无法弄乱内部结构。
ChannelOutboundBuffer
是我们 AbstractChannel
实现的实现细节,因此已从 Channel
本身完全删除。
我们删除了 Channel.beforeBeforeWritable()
方法,因为它根本没有被使用,并将 Channel.bytesBeforeUnwritable()
重命名为 Channel.writableBytes
。
在 netty 5 中完全删除了对 ProgressiveFuture / ProgressivePromise 的支持。原因是,虽然它有时可能有用,但它还要求管道中的所有处理程序在此处执行特殊操作,如果它们对 promise 进行链接。实际上并非如此,并且在现实中这样做非常麻烦。
由于存在上述问题,我们决定最好直接移除此功能,因为此功能的用处不大。完全不支持某项功能总比只在“有时”起作用要好。这也意味着要维护的代码更少。
在 netty 4.1.x 中,可以使用 voidPromise()
方法获取一个特殊的 ChannelPromise
实现,该实现可用于各种 IO 操作(如 write
),以减少创建的对象数量。虽然此功能的初衷是好的,但事实证明,此特殊情况的 ChannelPromise
确实带来了很多问题
- 向该承诺添加
ChannelFutureListener
的每个ChannelHandler
都必须首先调用unvoid()
,以确保可以安全地添加侦听器。如果未执行此操作,则在调用addListener
时将导致RuntimeException
。 - 根本不支持 wait() / sync() 操作。
- 某些操作允许使用
VoidChannelPromise
,而某些操作则不允许。
- 已移除
Future.addListeners()
、Future.removeListeners()
和Future.removeListener()
。我们移除了移除先前添加的侦听器的功能。此功能并未真正使用,因此它允许我们移除一些复杂性并移除一些 API 表面。 - 已移除
sync
和await
方法的不可中断变体。 - 已添加
Future.isFailed()
方法,该方法检查 future 是否已完成且失败。这类似于现有的Future.isSuccess()
,它检查 future 是否已完成且成功。 -
Promise.setUncancellable
现在仅在 promise 从“未完成”过渡到“不可取消”时才返回true
。具体来说,如果 promise 已完成,则此方法现在返回false
,而在 4.1 中,它在这种情况下将返回true
。 - 已添加新的
Future.map()
和Future.flatMap()
方法,这使得基于现有 future 轻松组合和创建新的 future 变得容易。这些方法通过传播正确处理故障和取消。 - 所有阻塞方法都已从
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 现在在运行时自动初始化,在 native image 中具有最小的开销。现在支持的最低 Graal 版本是 22.1,Java 17。