对于最新稳定版本,请使用 Spring Framework 7.0.6spring-doc.cadn.net.cn

数据缓冲区与编解码器

Java NIO 提供了 ByteBuffer,但许多库在其之上构建了自己的字节缓冲区 API, 尤其是在网络操作中,重用缓冲区和/或使用直接缓冲区对性能有益。 例如,Netty 拥有 ByteBuf 类层次结构,Undertow 使用 XNIO,Jetty 使用带回调释放机制的池化字节缓冲区,等等。 spring-core 模块提供了一组抽象,用于与各种字节缓冲区 API 协同工作,如下所示:spring-doc.cadn.net.cn

DataBufferFactory

DataBufferFactory 用于通过以下两种方式之一创建数据缓冲区:spring-doc.cadn.net.cn

  1. 分配一个新的数据缓冲区,如果已知容量,可选择预先指定容量,这样效率更高,即使 DataBuffer 的实现可以根据需要动态增长或缩小。spring-doc.cadn.net.cn

  2. 包装一个现有的 byte[]java.nio.ByteBuffer,通过 DataBuffer 实现对给定数据进行装饰,且不涉及内存分配。spring-doc.cadn.net.cn

请注意,WebFlux 应用程序不会直接创建 DataBufferFactory,而是在客户端通过 ServerHttpResponseClientHttpRequest 来访问它。 工厂的具体类型取决于底层的客户端或服务器,例如:Reactor Netty 使用 NettyDataBufferFactory,而其他情况则使用 DefaultDataBufferFactoryspring-doc.cadn.net.cn

DataBuffer

DataBuffer 接口提供了与 java.nio.ByteBuffer 类似的操作,同时还带来了一些额外的优势,其中部分优势受到 Netty ByteBuf 的启发。 以下是这些优势的部分列表:spring-doc.cadn.net.cn

PooledDataBuffer

正如 ByteBuffer 的 Javadoc 中所解释的, 字节缓冲区可以是直接缓冲区(direct)或非直接缓冲区(non-direct)。直接缓冲区可能位于 Java 堆之外, 从而在执行本地 I/O 操作时无需进行数据复制。这使得直接缓冲区 在通过套接字接收和发送数据时特别有用,但它们的创建和释放成本也更高, 这就引出了缓冲区池化的概念。spring-doc.cadn.net.cn

PooledDataBufferDataBuffer 的一个扩展,用于协助引用计数,这对于字节缓冲区池化至关重要。其工作原理如下:当分配一个 PooledDataBuffer 时,其引用计数为 1。retain() 方法调用会增加该计数,而 release() 方法调用则会减少该计数。只要引用计数大于 0,就能保证缓冲区不会被释放。当引用计数减少到 0 时,该池化的缓冲区就可以被释放,在实际操作中,这意味着为该缓冲区预留的内存将被归还到内存池中。spring-doc.cadn.net.cn

请注意,大多数情况下,与其直接操作 PooledDataBuffer,不如使用 DataBufferUtils 中提供的便捷方法,这些方法仅在 DataBufferPooledDataBuffer 实例时才会对其执行释放(release)或保留(retain)操作。spring-doc.cadn.net.cn

DataBufferUtils

DataBufferUtils 提供了多种用于操作数据缓冲区的实用方法:spring-doc.cadn.net.cn

  • 将数据缓冲区流合并为单个缓冲区,可能实现零拷贝(例如通过复合缓冲区),前提是底层字节缓冲区 API 支持该特性。spring-doc.cadn.net.cn

  • InputStream 或 NIO Channel 转换为 Flux<DataBuffer>,反之亦可将 Publisher<DataBuffer> 转换为 OutputStream 或 NIO Channelspring-doc.cadn.net.cn

  • 如果缓冲区是 DataBuffer 的实例,则用于释放或保留 PooledDataBuffer 的方法。spring-doc.cadn.net.cn

  • 跳过或从字节流中读取,直到达到指定的字节数。spring-doc.cadn.net.cn

编解码器

org.springframework.core.codec 包提供了以下策略接口:spring-doc.cadn.net.cn

spring-core 模块提供了 byte[]ByteBufferDataBufferResourceString 的编码器与解码器实现。spring-web 模块增加了 Jackson JSON、Jackson Smile、JAXB2、Protocol Buffers 以及其他编码器和解码器。请参阅 WebFlux 部分中的 Codecsspring-doc.cadn.net.cn

使用DataBuffer

在处理数据缓冲区时,需要特别注意确保释放缓冲区,因为它们可能是池化的。我们将以编解码器(codecs)为例来说明其工作原理,但这些概念具有更普遍的适用性。让我们看看编解码器在内部必须如何管理数据缓冲区。spring-doc.cadn.net.cn

Decoder 是在创建高层对象之前读取输入数据缓冲区的最后一个组件,因此它必须按如下方式释放这些缓冲区:spring-doc.cadn.net.cn

  1. 如果一个 Decoder 只是读取每个输入缓冲区并能立即释放它,那么可以通过 DataBufferUtils.release(dataBuffer) 来实现。spring-doc.cadn.net.cn

  2. 如果 Decoder 使用了 FluxMono 的操作符(例如 flatMapreduce 等会内部预取并缓存数据项的操作符),或者使用了诸如 filterskip 等会跳过某些数据项的操作符,那么必须在组合链中添加 doOnDiscard(DataBuffer.class, DataBufferUtils::release),以确保这些缓冲区在被丢弃之前得到释放,即使是在发生错误或收到取消信号的情况下也是如此。spring-doc.cadn.net.cn

  3. 如果 Decoder 以任何其他方式持有一个或多个数据缓冲区,则必须确保在完全读取后释放它们,或者在缓存的数据缓冲区被读取并释放之前发生错误或取消信号的情况下释放它们。spring-doc.cadn.net.cn

请注意,DataBufferUtils#join 提供了一种安全且高效的方式,可将数据缓冲区流聚合为单个数据缓冲区。同样地,skipUntilByteCounttakeUntilByteCount 也是解码器可使用的其他安全方法。spring-doc.cadn.net.cn

Encoder 负责分配数据缓冲区,而这些缓冲区必须由其他组件读取(并释放)。因此,Encoder 本身并没有太多工作要做。然而,如果在向缓冲区填充数据时发生序列化错误,Encoder 必须注意释放该数据缓冲区。例如:spring-doc.cadn.net.cn

DataBuffer buffer = factory.allocateBuffer();
boolean release = true;
try {
	// serialize and populate buffer..
	release = false;
}
finally {
	if (release) {
		DataBufferUtils.release(buffer);
	}
}
return buffer;
val buffer = factory.allocateBuffer()
var release = true
try {
	// serialize and populate buffer..
	release = false
} finally {
	if (release) {
		DataBufferUtils.release(buffer)
	}
}
return buffer

Encoder 的使用者负责释放其接收到的数据缓冲区。 在 WebFlux 应用程序中,Encoder 的输出用于写入 HTTP 服务器响应或客户端 HTTP 请求; 在这种情况下,释放数据缓冲区的责任由向服务器响应或客户端请求写入数据的代码承担。spring-doc.cadn.net.cn

请注意,当在 Netty 上运行时,有用于排查缓冲区泄漏问题的调试选项。spring-doc.cadn.net.cn