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