8.数据缓冲区和编解码器
Java NIO提供了ByteBuffer
,但是许多库在顶部构建了自己的字节缓冲区API,特别是对于网络操作,其中重用缓冲区或使用直接缓冲区对性能有利。 例如,Netty
具有ByteBuf
层次结构,Undertow
使用XNIO
,Jetty
使用具有要释放的回调的池化字节缓冲区,依此类推。 spring-core
模块提供了一组抽象,可以与各种字节缓冲区API配合使用,如下所示:
DataBufferFactory
抽象数据缓冲区的创建。DataBuffer
表示一个字节缓冲区,可以将其合并。DataBufferUtils
提供了用于数据缓冲区的工具方法。- 编解码器将流数据缓冲区流解码或编码为更高级别的对象。
8.1。 DataBufferFactory
DataBufferFactory
用于通过以下两种方式之一创建数据缓冲区:
- 分配一个新的数据缓冲区,可以选择预先指定容量(如果已知),即使容量的
DataBuffer
实现可以按需增长和收缩,该容量也会更有效。 - 包装一个现有的
byte[]
或java.nio.ByteBuffer
,用一个DataBuffer实现装饰给定的数据并且不涉及分配。
请注意,WebFlux应用程序不会直接创建DataBufferFactory
,而是通过客户端的ServerHttpResponse
或ClientHttpRequest
访问它。 工厂的类型取决于基础客户端或服务器,例如 NettyDataBufferFactory
用于Reactor Netty,DefaultDataBufferFactory
用于其他。
8.2。 DataBuffer
DataBuffer
接口提供与java.nio.ByteBuffer
类似的操作,但还带来了一些其他好处,其中一些是受Netty ByteBuf启发的。 以下是部分好处清单:
- 具有独立位置的读取和写入,即不需要调用
flip()
在读取和写入之间交替。 - 与
java.lang.StringBuilder
一样,容量可以按需扩展。 - 通过
PooledDataBuffer
进行缓冲池和引用计数。 - 将缓冲区查看为
java.nio.ByteBuffer
,InputStream
或OutputStream
。 - 确定给定字节的索引或最后一个索引。
8.3。 PooledDataBuffer
如Javadoc中 ByteBuffer所述,字节缓冲区可以是直接的也可以是非直接的。直接缓冲区可以驻留在Java堆之外,从而无需复制本机 I/O 操作。这使得直接缓冲区对于通过套接字接收和发送数据特别有用,但是它们的创建和释放也更昂贵,这导致了缓冲池的想法。
PooledDataBuffer
是DataBuffer的扩展,可帮助进行引用计数,这对于字节缓冲区池至关重要。 它是如何工作的? 分配PooledDataBuffer时,引用计数为1。调用keep()
会增加计数,而调用release()
会减少计数。 只要计数大于0,就保证不会释放缓冲区。 当计数减少到0时,可以释放池中的缓冲区,这实际上意味着将为缓冲区保留的内存返回到内存池。
请注意,与其直接在PooledDataBuffer
上进行操作,在大多数情况下,不如使用DataBufferUtils
中的便捷方法,仅当它是PooledDataBuffer的实例时,才将释放或保留应用于DataBuffer。
8.4。 DataBufferUtils
DataBufferUtils
提供了许多工具方法来对数据缓冲区进行操作:
- 如果底层字节缓冲区API支持,则可以通过复合缓冲区将数据缓冲区流连接到单个缓冲区中,该缓冲区可能具有零副本。
- 将
InputStream
或NIO Channel
转为Flux
,反之亦然,Publisher
转为OutputStream
或NIO Channel
。 - 如果缓冲区是
PooledDataBuffer
的实例,则释放或保留DataBuffer
的方法。 - 从字节流中跳过或获取,直到特定的字节数为止。
8.5。编解码器
org.springframework.core.codec
软件包提供以下策略接口:
- 编码器,用于将
Publisher
编码为数据缓冲区流。 - 解码器,用于将
Publisher
解码为更高级别的对象流。
spring-core
模块提供byte[]
,ByteBuffer
,DataBuffer
,Resource
和String编码器和解码器实现。 spring-web
模块添加了Jackson JSON,Jackson Smile,JAXB2,Protocol Buffers和其他编码器和解码器。 请参阅WebFlux部分中的编解码器。
8.6。使用DataBuffer
使用数据缓冲区时,必须特别小心以确保释放缓冲区,因为它们可能会被合并。我们将使用编解码器来说明其工作原理,但是这些概念将更普遍地应用。让我们看看编解码器必须在内部执行哪些操作来管理数据缓冲区。
在创建更高级别的对象之前,Decoder
是最后一个读取输入数据缓冲区的对象,因此,它必须按以下方式释放它们:
如果Decoder只需读取每个输入缓冲区并准备立即释放它,则可以通过DataBufferUtils.release(dataBuffer)
进行操作。
如果Decoder使用的是Flux或Mono运算符(例如flatMap,reduce和其他在内部预取和缓存数据项的运算符),或者正在使用诸如filter,skip的运算符以及其他遗漏项的运算符,则必须将doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)
添加到组合链中,以确保在丢弃此类缓冲区之前将其释放,这也可能是错误或取消信号的结果。
如果解码器以任何其他方式保留一个或多个数据缓冲区,则它必须确保在完全读取时释放它们,或者在读取和释放缓存的数据缓冲区之前发生错误或取消信号的情况下。
请注意,DataBufferUtils#join
提供了一种安全有效的方法来将数据缓冲区流聚合到单个数据缓冲区中。 同样,skipUntilByteCount
和takeUntilByteCount
是供解码器使用的其他安全方法。
编码器分配其他人必须读取(和释放)的数据缓冲区。 因此,编码器无事可做。 但是,如果在使用数据填充缓冲区时发生序列化错误,则编码器必须小心释放数据缓冲区。 例如:
DataBuffer buffer = factory.allocateBuffer();
boolean release = true;
try {
// serialize and populate buffer..
release = false;
}
finally {
if (release) {
DataBufferUtils.release(buffer);
}
}
return buffer;
编码器的使用者负责释放其接收的数据缓冲区。 在WebFlux应用程序中,编码器的输出用于写入HTTP服务器响应或客户端HTTP请求,在这种情况下,释放数据缓冲区是代码写入服务器响应或客户端的请求的责任。
请注意,在Netty上运行时,有用于调试缓冲区泄漏的调试选项。