Skip to content

Commit fa096dc

Browse files
committed
Minor refactoring in StringDecoder + polish
1. Avoid re-creating the List with delimited byte arrays on every request if using the default delimiters which don't vary by charset. 2. Replace flatMap with flatMapIterable for splitOnDelimiter. 3. Avoid going through DataBufferUtils#join, and unnecessarily creating Flux from the List, since the join method needs a list anyway.
1 parent fc957e9 commit fa096dc

File tree

4 files changed

+63
-57
lines changed

4 files changed

+63
-57
lines changed

spring-core/src/main/java/org/springframework/core/codec/DataBufferDecoder.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,15 @@
2929

3030
/**
3131
* Simple pass-through decoder for {@link DataBuffer DataBuffers}.
32-
* <p><strong>Note</strong> that the "decoded" buffers returned by instances of this class should
33-
* be released after usage by calling
34-
* {@link org.springframework.core.io.buffer.DataBufferUtils#release(DataBuffer)}.
32+
*
33+
* <p><strong>Note:</strong> The data buffers should be released via
34+
* {@link org.springframework.core.io.buffer.DataBufferUtils#release(DataBuffer)}
35+
* after they have been consumed. In addition, if using {@code Flux} or
36+
* {@code Mono} operators such as flatMap, reduce, and others that prefetch,
37+
* cache, and skip or filter out data items internally, please add
38+
* {@code doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release)} to the
39+
* composition chain to ensure cached data buffers are released prior to an
40+
* error or cancellation signal.
3541
*
3642
* @author Arjen Poutsma
3743
* @author Rossen Stoyanchev
@@ -48,7 +54,8 @@ public DataBufferDecoder() {
4854
@Override
4955
public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) {
5056
Class<?> clazz = elementType.getRawClass();
51-
return (super.canDecode(elementType, mimeType) && clazz != null && DataBuffer.class.isAssignableFrom(clazz));
57+
return (super.canDecode(elementType, mimeType) &&
58+
clazz != null && DataBuffer.class.isAssignableFrom(clazz));
5259
}
5360

5461
@Override

spring-core/src/main/java/org/springframework/core/codec/StringDecoder.java

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727

2828
import org.reactivestreams.Publisher;
2929
import reactor.core.publisher.Flux;
30-
import reactor.core.publisher.Mono;
3130

3231
import org.springframework.core.ResolvableType;
3332
import org.springframework.core.io.buffer.DataBuffer;
@@ -36,7 +35,6 @@
3635
import org.springframework.core.io.buffer.PooledDataBuffer;
3736
import org.springframework.core.log.LogFormatUtils;
3837
import org.springframework.lang.Nullable;
39-
import org.springframework.util.Assert;
4038
import org.springframework.util.MimeType;
4139
import org.springframework.util.MimeTypeUtils;
4240

@@ -59,25 +57,26 @@ public final class StringDecoder extends AbstractDataBufferDecoder<String> {
5957

6058
private static final DataBuffer END_FRAME = new DefaultDataBufferFactory().wrap(new byte[0]);
6159

62-
/**
63-
* The default charset to use, i.e. "UTF-8".
64-
*/
60+
/** The default charset to use, i.e. "UTF-8". */
6561
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
6662

67-
/**
68-
* The default delimiter strings to use, i.e. {@code \n} and {@code \r\n}.
69-
*/
63+
/** The default delimiter strings to use, i.e. {@code \r\n} and {@code \n}. */
7064
public static final List<String> DEFAULT_DELIMITERS = Arrays.asList("\r\n", "\n");
7165

66+
private static final List<byte[]> DEFAULT_DELIMITER_BYTES = DEFAULT_DELIMITERS.stream()
67+
.map(s -> s.getBytes(StandardCharsets.UTF_8))
68+
.collect(Collectors.toList());
7269

70+
71+
@Nullable
7372
private final List<String> delimiters;
7473

7574
private final boolean stripDelimiter;
7675

77-
private StringDecoder(List<String> delimiters, boolean stripDelimiter, MimeType... mimeTypes) {
76+
77+
private StringDecoder(@Nullable List<String> delimiters, boolean stripDelimiter, MimeType... mimeTypes) {
7878
super(mimeTypes);
79-
Assert.notEmpty(delimiters, "'delimiters' must not be empty");
80-
this.delimiters = new ArrayList<>(delimiters);
79+
this.delimiters = delimiters != null ? new ArrayList<>(delimiters) : null;
8180
this.stripDelimiter = stripDelimiter;
8281
}
8382

@@ -92,36 +91,32 @@ public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType
9291
public Flux<String> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
9392
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
9493

95-
List<byte[]> delimiterBytes = getDelimiterBytes(mimeType);
94+
List<byte[]> delimiterBytes = this.delimiters != null ?
95+
this.delimiters.stream().map(s -> s.getBytes(getCharset(mimeType))).collect(Collectors.toList()) :
96+
DEFAULT_DELIMITER_BYTES;
9697

9798
Flux<DataBuffer> inputFlux = Flux.from(inputStream)
98-
.flatMap(dataBuffer -> splitOnDelimiter(dataBuffer, delimiterBytes))
99+
.flatMapIterable(dataBuffer -> splitOnDelimiter(dataBuffer, delimiterBytes))
99100
.bufferUntil(StringDecoder::isEndFrame)
100-
.flatMap(StringDecoder::joinUntilEndFrame)
101+
.map(StringDecoder::joinUntilEndFrame)
101102
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
102-
return super.decode(inputFlux, elementType, mimeType, hints);
103-
}
104103

105-
private List<byte[]> getDelimiterBytes(@Nullable MimeType mimeType) {
106-
Charset charset = getCharset(mimeType);
107-
return this.delimiters.stream()
108-
.map(s -> s.getBytes(charset))
109-
.collect(Collectors.toList());
104+
return super.decode(inputFlux, elementType, mimeType, hints);
110105
}
111106

112107
/**
113108
* Splits the given data buffer on delimiter boundaries. The returned Flux contains a
114109
* {@link #END_FRAME} buffer after each delimiter.
115110
*/
116-
private Flux<DataBuffer> splitOnDelimiter(DataBuffer dataBuffer, List<byte[]> delimiterBytes) {
111+
private List<DataBuffer> splitOnDelimiter(DataBuffer dataBuffer, List<byte[]> delimiterBytes) {
117112
List<DataBuffer> frames = new ArrayList<>();
118113
do {
119114
int length = Integer.MAX_VALUE;
120115
byte[] matchingDelimiter = null;
121116
for (byte[] delimiter : delimiterBytes) {
122-
int idx = indexOf(dataBuffer, delimiter);
123-
if (idx >= 0 && idx < length) {
124-
length = idx;
117+
int index = indexOf(dataBuffer, delimiter);
118+
if (index >= 0 && index < length) {
119+
length = index;
125120
matchingDelimiter = delimiter;
126121
}
127122
}
@@ -148,12 +143,12 @@ private Flux<DataBuffer> splitOnDelimiter(DataBuffer dataBuffer, List<byte[]> de
148143
while (dataBuffer.readableByteCount() > 0);
149144

150145
DataBufferUtils.release(dataBuffer);
151-
return Flux.fromIterable(frames);
146+
return frames;
152147
}
153148

154149
/**
155-
* Finds the given delimiter in the given data buffer. Return the index of the delimiter, or
156-
* -1 if not found.
150+
* Find the given delimiter in the given data buffer.
151+
* @return the index of the delimiter, or -1 if not found.
157152
*/
158153
private static int indexOf(DataBuffer dataBuffer, byte[] delimiter) {
159154
for (int i = dataBuffer.readPosition(); i < dataBuffer.writePosition(); i++) {
@@ -172,7 +167,6 @@ private static int indexOf(DataBuffer dataBuffer, byte[] delimiter) {
172167
}
173168
delimiterPos++;
174169
}
175-
176170
if (delimiterPos == delimiter.length) {
177171
return i - dataBuffer.readPosition();
178172
}
@@ -188,17 +182,17 @@ private static boolean isEndFrame(DataBuffer dataBuffer) {
188182
}
189183

190184
/**
191-
* Joins the given list of buffers into a single buffer.
185+
* Joins the given list of buffers into a single buffer, also removing
186+
* the (inserted) {@link #END_FRAME}.
192187
*/
193-
private static Mono<DataBuffer> joinUntilEndFrame(List<DataBuffer> dataBuffers) {
188+
private static DataBuffer joinUntilEndFrame(List<DataBuffer> dataBuffers) {
194189
if (!dataBuffers.isEmpty()) {
195190
int lastIdx = dataBuffers.size() - 1;
196191
if (isEndFrame(dataBuffers.get(lastIdx))) {
197192
dataBuffers.remove(lastIdx);
198193
}
199194
}
200-
Flux<DataBuffer> flux = Flux.fromIterable(dataBuffers);
201-
return DataBufferUtils.join(flux);
195+
return dataBuffers.get(0).factory().join(dataBuffers);
202196
}
203197

204198
@Override
@@ -241,15 +235,18 @@ public static StringDecoder textPlainOnly(boolean ignored) {
241235
* Create a {@code StringDecoder} for {@code "text/plain"}.
242236
*/
243237
public static StringDecoder textPlainOnly() {
244-
return textPlainOnly(DEFAULT_DELIMITERS, true);
238+
return textPlainOnly(null, true);
245239
}
246240

247241
/**
248242
* Create a {@code StringDecoder} for {@code "text/plain"}.
243+
* @param delimiters delimiter strings to use to split the input stream, if
244+
* {@code null} by default {@link #DEFAULT_DELIMITERS} is used.
245+
* @param stripDelimiter whether to remove delimiters from the resulting
246+
* input strings.
249247
*/
250-
public static StringDecoder textPlainOnly(List<String> delimiters, boolean stripDelimiter) {
251-
return new StringDecoder(delimiters, stripDelimiter,
252-
new MimeType("text", "plain", DEFAULT_CHARSET));
248+
public static StringDecoder textPlainOnly(@Nullable List<String> delimiters, boolean stripDelimiter) {
249+
return new StringDecoder(delimiters, stripDelimiter, new MimeType("text", "plain", DEFAULT_CHARSET));
253250
}
254251

255252
/**
@@ -267,16 +264,19 @@ public static StringDecoder allMimeTypes(boolean ignored) {
267264
* Create a {@code StringDecoder} that supports all MIME types.
268265
*/
269266
public static StringDecoder allMimeTypes() {
270-
return allMimeTypes(DEFAULT_DELIMITERS, true);
267+
return allMimeTypes(null, true);
271268
}
272269

273270
/**
274271
* Create a {@code StringDecoder} that supports all MIME types.
272+
* @param delimiters delimiter strings to use to split the input stream, if
273+
* {@code null} by default {@link #DEFAULT_DELIMITERS} is used.
274+
* @param stripDelimiter whether to remove delimiters from the resulting
275+
* input strings.
275276
*/
276-
public static StringDecoder allMimeTypes(List<String> delimiters, boolean stripDelimiter) {
277+
public static StringDecoder allMimeTypes(@Nullable List<String> delimiters, boolean stripDelimiter) {
277278
return new StringDecoder(delimiters, stripDelimiter,
278279
new MimeType("text", "plain", DEFAULT_CHARSET), MimeTypeUtils.ALL);
279280
}
280281

281-
282282
}

spring-core/src/main/java/org/springframework/core/io/buffer/DataBufferUtils.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -422,13 +422,16 @@ public static Consumer<DataBuffer> releaseConsumer() {
422422
}
423423

424424
/**
425-
* Return a new {@code DataBuffer} composed of the {@code dataBuffers} elements joined together.
426-
* Depending on the {@link DataBuffer} implementation, the returned buffer may be a single
427-
* buffer containing all data of the provided buffers, or it may be a true composite that
428-
* contains references to the buffers.
429-
* <p>If {@code dataBuffers} contains an error signal, then all buffers that preceded the error
430-
* will be {@linkplain #release(DataBuffer) released}, and the error is stored in the
431-
* returned {@code Mono}.
425+
* Return a new {@code DataBuffer} composed from joining together the given
426+
* {@code dataBuffers} elements. Depending on the {@link DataBuffer} type,
427+
* the returned buffer may be a single buffer containing all data of the
428+
* provided buffers, or it may be a zero-copy, composite with references to
429+
* the given buffers.
430+
* <p>If {@code dataBuffers} produces an error or if there is a cancel
431+
* signal, then all accumulated buffers will be
432+
* {@linkplain #release(DataBuffer) released}.
433+
* <p>Note that the given data buffers do <strong>not</strong> have to be
434+
* released. They will be released as part of the returned composite.
432435
* @param dataBuffers the data buffers that are to be composed
433436
* @return a buffer that is composed from the {@code dataBuffers} argument
434437
* @since 5.0.3
@@ -439,10 +442,7 @@ public static Mono<DataBuffer> join(Publisher<DataBuffer> dataBuffers) {
439442
return Flux.from(dataBuffers)
440443
.collectList()
441444
.filter(list -> !list.isEmpty())
442-
.map(list -> {
443-
DataBufferFactory bufferFactory = list.get(0).factory();
444-
return bufferFactory.join(list);
445-
})
445+
.map(list -> list.get(0).factory().join(list))
446446
.doOnDiscard(PooledDataBuffer.class, DataBufferUtils::release);
447447

448448
}

spring-core/src/main/java/org/springframework/core/io/buffer/NettyDataBufferFactory.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,7 @@ public DataBuffer join(List<? extends DataBuffer> dataBuffers) {
9292
CompositeByteBuf composite = this.byteBufAllocator.compositeBuffer(dataBuffers.size());
9393
for (DataBuffer dataBuffer : dataBuffers) {
9494
Assert.isInstanceOf(NettyDataBuffer.class, dataBuffer);
95-
NettyDataBuffer nettyDataBuffer = (NettyDataBuffer) dataBuffer;
96-
composite.addComponent(true, nettyDataBuffer.getNativeBuffer());
95+
composite.addComponent(true, ((NettyDataBuffer) dataBuffer).getNativeBuffer());
9796
}
9897
return new NettyDataBuffer(composite, this);
9998
}

0 commit comments

Comments
 (0)