Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Document DataBuffer/PooledDataBuffer and codecs
Issue: SPR-16156
- Loading branch information
Showing
5 changed files
with
165 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
[[databuffers]] | ||
= Data Buffers and Codecs | ||
|
||
== Introduction | ||
|
||
The `DataBuffer` interface defines an abstraction over byte buffers. | ||
The main reason for introducing it, and not use the standard `java.nio.ByteBuffer` instead, is Netty. | ||
Netty does not use `ByteBuffer`, but instead offers `ByteBuf` as an alternative. | ||
Spring's `DataBuffer` is a simple abstraction over `ByteBuf` that can also be used on non-Netty | ||
platforms (i.e. Servlet 3.1+). | ||
|
||
== `DataBufferFactory` | ||
|
||
The `DataBufferFactory` offers functionality to allocate new data buffers, as well as to wrap | ||
existing data. | ||
The `allocate` methods allocate a new data buffer, with a default or given capacity. | ||
Though `DataBuffer` implementation grow and shrink on demand, it is more efficient to give the | ||
capacity upfront, if known. | ||
The `wrap` methods decorate an existing `ByteBuffer` or byte array. | ||
Wrapping does not involve allocation: it simply decorates the given data with a `DataBuffer` | ||
implementation. | ||
|
||
There are two implementation of `DataBufferFactory`: the `NettyDataBufferFactory` which is meant | ||
to be used on Netty platforms, such as Reactor Netty. | ||
The other implementation, the `DefaultDataBufferFactory`, is used on other platforms, such as | ||
Servlet 3.1+ servers. | ||
|
||
== The `DataBuffer` interface | ||
|
||
The `DataBuffer` interface is similar to `ByteBuffer`, but offers a number of advantages. | ||
Similar to Netty's `ByteBuf`, the `DataBuffer` abstraction offers independent read and write | ||
positions. | ||
This is different from the JDK's `ByteBuffer`, which only exposes one position for both reading and | ||
writing, and a separate `flip()` operation to switch between the two I/O operations. | ||
In general, the following invariant holds for the read position, write position, and the capacity: | ||
|
||
-- | ||
`0` <= _read position_ <= _write position_ <= _capacity_ | ||
-- | ||
|
||
When reading bytes from the `DataBuffer`, the read position is automatically updated in accordance with | ||
the amount of data read from the buffer. | ||
Similarly, when writing bytes to the `DataBuffer`, the write position is updated with the amount of | ||
data written to the buffer. | ||
Also, when writing data, the capacity of a `DataBuffer` is automatically expanded, just like `StringBuilder`, | ||
`ArrayList`, and similar types. | ||
|
||
Besides the reading and writing functionality mentioned above, the `DataBuffer` also has methods to | ||
view a (slice of a) buffer as `ByteBuffer`, `InputStream`, or `OutputStream`. | ||
Additionally, it offers methods to determine the index of a given byte. | ||
|
||
There are two implementation of `DataBuffer`: the `NettyDataBuffer` which is meant to be used on | ||
Netty platforms, such as Reactor Netty. | ||
The other implementation, the `DefaultDataBuffer`, is used on other platforms, such as Servlet 3.1+ | ||
servers. | ||
|
||
=== `PooledDataBuffer` | ||
|
||
The `PooledDataBuffer` is an extension to `DataBuffer` that adds methods for reference counting. | ||
The `retain` method increases the reference count by one. | ||
The `release` method decreases the count by one, and releases the buffer's memory when the count | ||
reaches 0. | ||
Both of these methods are related to _reference counting_, a mechanism that is explained below. | ||
|
||
Note that `DataBufferUtils` offers useful utility methods for releasing and retaining pooled data | ||
buffers. | ||
These methods take a plain `DataBuffer` as parameter, but only call `retain` or `release` if the | ||
passed data buffer is an instance of `PooledDataBuffer`. | ||
|
||
[[databuffer-reference-counting]] | ||
==== Reference Counting | ||
|
||
Reference counting is not a common technique in Java; it is much more common in other programming | ||
languages such as Object C and C++. | ||
In and of itself, reference counting is not complex: it basically involves tracking the number of | ||
references that apply to an object. | ||
The reference count of a `PooledDataBuffer` starts at 1, is incremented by calling `retain`, | ||
and decremented by calling `release`. | ||
As long as the buffer's reference count is larger than 0 the buffer will not be released. | ||
When the number decreases to 0, the instance will be released. | ||
In practice, this means that the reserved memory captured by the buffer will be returned back to | ||
the memory pool, ready to be used for future allocations. | ||
|
||
In general, _the last component to access a `DataBuffer` is responsible for releasing it_. | ||
Withing Spring, there are two sorts of components that release buffers: decoders and transports. | ||
Decoders are responsible for transforming a stream of buffers into other types (see <<codecs>> below), | ||
and transports are responsible for sending buffers across a network boundary, typically as an HTTP message. | ||
This means that if you allocate data buffers for the purpose of putting them into an outbound HTTP | ||
message (i.e. client-side request or server-side response), they do not have to be released. | ||
The other consequence of this rule is that if you allocate data buffers that do not end up in the | ||
body, for instance because of a thrown exception, you will have to release them yourself. | ||
The following snippet shows a typical `DataBuffer` usage scenario when dealing with methods that | ||
throw exceptions: | ||
|
||
[source,java,indent=0] | ||
[subs="verbatim,quotes"] | ||
---- | ||
DataBufferFactory factory = ... | ||
DataBuffer buffer = factory.allocateBuffer(); <1> | ||
boolean release = true; <2> | ||
try { | ||
writeDataToBuffer(buffer); <3> | ||
putBufferInHttpBody(buffer); | ||
release = false; <4> | ||
} finally { | ||
if (release) { | ||
DataBufferUtils.release(buffer); <5> | ||
} | ||
} | ||
private void writeDataToBuffer(DataBuffer buffer) throws IOException { <3> | ||
... | ||
} | ||
---- | ||
<1> A new buffer is allocated. | ||
<2> A boolean flag indicates whether the allocated buffer should be released. | ||
<3> This example method loads data into the buffer. Note that the method can throw an `IOException`, | ||
and therefore a `finally` block to release the buffer is required. | ||
<4> If no exception occurred, we switch the `release` flag to `false` as the buffer will now be | ||
released as part of sending the HTTP body across the wire. | ||
<5> If an exception did occur, the flag is still set to `true`, and the buffer will be released | ||
here. | ||
|
||
=== DataBufferUtils | ||
|
||
`DataBufferUtils` contains various utility methods that operate on data buffers. | ||
It contains methods for reading a `Flux` of `DataBuffer` objects from an `InputStream` or NIO | ||
`Channel`, and methods for writing a data buffer `Flux` to an `OutputStream` or `Channel`. | ||
`DataBufferUtils` also exposes `retain` and `release` methods that operate on plain `DataBuffer` | ||
instances (so that casting to a `PooledDataBuffer` is not required). | ||
|
||
[codecs] | ||
== Codecs | ||
|
||
The `org.springframework.core.codec` package contains the two main abstractions for converting a | ||
stream of bytes into a stream of objects, or vice-versa. | ||
The `Encoder` is a strategy interface that encodes a stream of objects into an output stream of | ||
data buffers. | ||
The `Decoder` does the reverse: it turns a stream of data buffers into a stream of objects. | ||
Note that a decoder instance needs to consider <<databuffer-reference-counting, reference counting>>. | ||
|
||
Spring comes with a wide array of default codecs, capable of converting from/to `String`, | ||
`ByteBuffer`, byte arrays, and also codecs that support marshalling libraries such as JAXB and | ||
Jackson. | ||
Withing the context of Spring WebFlux, codecs are used to convert the request body into a | ||
`@RequestMapping` parameter, or to convert the return type into the response body that is sent back | ||
to the client. | ||
The default codecs are configured in the `WebFluxConfigurationSupport` class, and can easily be | ||
changed by overriding the `configureHttpMessageCodecs` when inheriting from that class. | ||
For more information about using codecs in WebFlux, see <<web-reactive#webflux-codecs, this section>>. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters