此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
数据缓冲区和编解码器
Java NIO 提供ByteBuffer
但许多库在上面构建了自己的字节缓冲区 API,
特别是对于重用缓冲区和/或使用直接缓冲区的网络作
有利于性能。例如,Netty 具有ByteBuf
层次结构,Undertow 使用
XNIO、Jetty 使用池字节缓冲区和要释放的回调,依此类推。
这spring-core
模块提供了一组抽象来处理各种字节缓冲区
API 如下所示:
-
DataBufferFactory
抽象数据缓冲区的创建。 -
DataBuffer
表示可以池化的字节缓冲区。 -
DataBufferUtils
为数据缓冲区提供实用程序方法。 -
编解码器将数据缓冲区流解码或编码为更高级别的对象。
DataBufferFactory
DataBufferFactory
用于通过以下两种方式之一创建数据缓冲区:
-
分配一个新的数据缓冲区,如果已知,可以选择预先指定容量,即 即使实现
DataBuffer
可以按需增长和缩小。 -
包装现有的
byte[]
或java.nio.ByteBuffer
,它用 一个DataBuffer
实施,这不涉及分配。
请注意,WebFlux 应用程序不会创建DataBufferFactory
直接而是
通过ServerHttpResponse
或ClientHttpRequest
在客户端。
工厂的类型取决于底层客户端或服务器,例如NettyDataBufferFactory
对于 Reactor Netty,DefaultDataBufferFactory
对于其他人来说。
DataBuffer
这DataBuffer
接口提供与java.nio.ByteBuffer
而且还
带来了一些额外的好处,其中一些是受到 Netty 的启发ByteBuf
.
以下是部分福利列表:
-
使用独立位置进行读写,即不需要调用
flip()
自 在读取和写入之间交替。 -
按需扩展容量,如
java.lang.StringBuilder
. -
池化缓冲区和参考计数
PooledDataBuffer
. -
将缓冲区查看为
java.nio.ByteBuffer
,InputStream
或OutputStream
. -
确定给定字节的索引或最后一个索引。
PooledDataBuffer
正如 ByteBuffer 的 Javadoc 中所解释的, 字节缓冲区可以是直接的,也可以是非直接的。直接缓冲区可能驻留在 Java 堆之外 这消除了本机 I/O作的复制需求。这使得直接缓冲区 对于通过套接字接收和发送数据特别有用,但它们也更多 创建和发布成本高昂,这导致了池化缓冲区的想法。
PooledDataBuffer
是DataBuffer
这有助于引用计数
对于字节缓冲池至关重要。它是如何工作的?当PooledDataBuffer
是
分配的参考计数为 1。调用retain()
增加计数,而
调用release()
递减它。只要计数高于 0,缓冲区为
保证不放。当计数减少到 0 时,池化缓冲区可以
released,这实际上可能意味着缓冲区的保留内存将返回到
内存池。
请注意,而不是在PooledDataBuffer
直接,在大多数情况下更好
使用DataBufferUtils
将发布或保留应用于DataBuffer
仅当它是PooledDataBuffer
.
DataBufferUtils
DataBufferUtils
提供了许多实用方法来对数据缓冲区进行作:
-
将数据缓冲区流连接到可能为零副本的单个缓冲区中,例如,通过 复合缓冲区,如果基础字节缓冲区 API 支持。
-
转
InputStream
或蔚来Channel
到Flux<DataBuffer>
,反之亦然Publisher<DataBuffer>
到OutputStream
或蔚来Channel
. -
释放或保留
DataBuffer
如果缓冲区是PooledDataBuffer
. -
跳过或从字节流中获取,直到特定的字节计数。
Codec
这org.springframework.core.codec
package 提供以下策略接口:
-
Encoder
编码Publisher<T>
转换为数据缓冲区流。 -
Decoder
解码Publisher<DataBuffer>
转换为更高级别对象的流。
这spring-core
模块提供byte[]
,ByteBuffer
,DataBuffer
,Resource
和String
编码器和解码器实现。这spring-web
模块添加 Jackson JSON,
Jackson Smile、JAXB2、协议缓冲区和其他编码器和解码器。请参阅 WebFlux 部分中的编解码器。
用DataBuffer
使用数据缓冲区时,必须特别注意确保释放缓冲区 因为它们可以合并。我们将使用编解码器来说明 这是如何工作的,但这些概念更普遍地适用。让我们看看编解码器必须做什么 内部管理数据缓冲区。
一个Decoder
在创建更高级别之前,最后读取输入数据缓冲区
对象,因此它必须按如下方式释放它们:
-
如果
Decoder
只需读取每个输入缓冲区并准备好 立即释放它,它可以通过DataBufferUtils.release(dataBuffer)
. -
如果
Decoder
正在使用Flux
或Mono
运算符,例如flatMap
,reduce
和 其他在内部预取和缓存数据项,或者使用filter
,skip
,以及其他遗漏项目的项目,然后doOnDiscard(DataBuffer.class, DataBufferUtils::release)
必须添加到 组合链,以确保在丢弃之前释放此类缓冲区,可能 也是错误或取消信号的结果。 -
如果
Decoder
以任何其他方式保留一个或多个数据缓冲区,它必须 确保在完全读取时释放它们,或者在出现错误或取消信号时释放 在读取和释放缓存的数据缓冲区之前发生。
请注意DataBufferUtils#join
提供了一种安全有效的数据聚合方式
缓冲区流到单个数据缓冲区中。同样skipUntilByteCount
和takeUntilByteCount
是解码器使用的其他安全方法。
一Encoder
分配其他人必须读取(和释放)的数据缓冲区。所以一个Encoder
没什么可做的。然而,一个Encoder
如果出现以下情况,必须注意释放数据缓冲区
使用数据填充缓冲区时发生序列化错误。例如:
-
Java
-
Kotlin
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 请求,在这种情况下,释放数据缓冲区是
负责将代码写入服务器响应或客户端请求。
请注意,在 Netty 上运行时,有用于解决缓冲区泄漏问题的调试选项。