Optimize Jackson resource management in codecs
Prior to this commit, references to `JsonGenerator` and `ByteArrayBuilder` were not closed/released within codecs calls. This prevents Jackson from reusing more efficiently shared memory resources. This commit properly closes/releases Jackson resources in Spring MVC, Spring WebFlux and Spring Messaging codecs. A benchmark on WebFlux codecs (in both single value/streaming mode) shows significant throughput and allocation improvements for small payloads. Closes gh-25910
This commit is contained in:
parent
db3d537e72
commit
7bee3d1574
|
|
@ -263,14 +263,15 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
|
|||
if (byte[].class == getSerializedPayloadClass()) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
|
||||
JsonEncoding encoding = getJsonEncoding(getMimeType(headers));
|
||||
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(out, encoding);
|
||||
if (view != null) {
|
||||
this.objectMapper.writerWithView(view).writeValue(generator, payload);
|
||||
try (JsonGenerator generator = this.objectMapper.getFactory().createGenerator(out, encoding)) {
|
||||
if (view != null) {
|
||||
this.objectMapper.writerWithView(view).writeValue(generator, payload);
|
||||
}
|
||||
else {
|
||||
this.objectMapper.writeValue(generator, payload);
|
||||
}
|
||||
payload = out.toByteArray();
|
||||
}
|
||||
else {
|
||||
this.objectMapper.writeValue(generator, payload);
|
||||
}
|
||||
payload = out.toByteArray();
|
||||
}
|
||||
else {
|
||||
// Assuming a text-based target payload
|
||||
|
|
|
|||
|
|
@ -149,7 +149,16 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple
|
|||
|
||||
return Flux.from(inputStream)
|
||||
.map(value -> encodeStreamingValue(value, bufferFactory, hints, sequenceWriter, byteBuilder,
|
||||
separator));
|
||||
separator))
|
||||
.doAfterTerminate(() -> {
|
||||
try {
|
||||
byteBuilder.release();
|
||||
generator.close();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
logger.error("Could not close Encoder resources", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return Flux.error(ex);
|
||||
|
|
@ -172,30 +181,34 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple
|
|||
|
||||
ObjectWriter writer = createObjectWriter(valueType, mimeType, hints);
|
||||
ByteArrayBuilder byteBuilder = new ByteArrayBuilder(writer.getFactory()._getBufferRecycler());
|
||||
JsonEncoding encoding = getJsonEncoding(mimeType);
|
||||
|
||||
logValue(hints, value);
|
||||
|
||||
try {
|
||||
JsonGenerator generator = getObjectMapper().getFactory().createGenerator(byteBuilder, encoding);
|
||||
writer.writeValue(generator, value);
|
||||
generator.flush();
|
||||
}
|
||||
catch (InvalidDefinitionException ex) {
|
||||
throw new CodecException("Type definition error: " + ex.getType(), ex);
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
throw new EncodingException("JSON encoding error: " + ex.getOriginalMessage(), ex);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Unexpected I/O error while writing to byte array builder", ex);
|
||||
}
|
||||
JsonEncoding encoding = getJsonEncoding(mimeType);
|
||||
|
||||
byte[] bytes = byteBuilder.toByteArray();
|
||||
DataBuffer buffer = bufferFactory.allocateBuffer(bytes.length);
|
||||
buffer.write(bytes);
|
||||
logValue(hints, value);
|
||||
|
||||
return buffer;
|
||||
try (JsonGenerator generator = getObjectMapper().getFactory().createGenerator(byteBuilder, encoding)) {
|
||||
writer.writeValue(generator, value);
|
||||
generator.flush();
|
||||
}
|
||||
catch (InvalidDefinitionException ex) {
|
||||
throw new CodecException("Type definition error: " + ex.getType(), ex);
|
||||
}
|
||||
catch (JsonProcessingException ex) {
|
||||
throw new EncodingException("JSON encoding error: " + ex.getOriginalMessage(), ex);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException("Unexpected I/O error while writing to byte array builder", ex);
|
||||
}
|
||||
|
||||
byte[] bytes = byteBuilder.toByteArray();
|
||||
DataBuffer buffer = bufferFactory.allocateBuffer(bytes.length);
|
||||
buffer.write(bytes);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
finally {
|
||||
byteBuilder.release();
|
||||
}
|
||||
}
|
||||
|
||||
private DataBuffer encodeStreamingValue(Object value, DataBufferFactory bufferFactory, @Nullable Map<String, Object> hints,
|
||||
|
|
|
|||
|
|
@ -307,8 +307,8 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
|
|||
|
||||
MediaType contentType = outputMessage.getHeaders().getContentType();
|
||||
JsonEncoding encoding = getJsonEncoding(contentType);
|
||||
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);
|
||||
try {
|
||||
|
||||
try (JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding)) {
|
||||
writePrefix(generator, object);
|
||||
|
||||
Object value = object;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Logger name="org.springframework.web" level="debug" />
|
||||
<Logger name="org.springframework.web" level="warn" />
|
||||
<Logger name="org.springframework.beans" level="warn" />
|
||||
<Logger name="org.springframework.binding" level="warn" />
|
||||
<Logger name="org.springframework.http" level="warn" />
|
||||
|
|
|
|||
|
|
@ -206,29 +206,30 @@ public abstract class AbstractJackson2View extends AbstractView {
|
|||
* @throws IOException if writing failed
|
||||
*/
|
||||
protected void writeContent(OutputStream stream, Object object) throws IOException {
|
||||
JsonGenerator generator = this.objectMapper.getFactory().createGenerator(stream, this.encoding);
|
||||
writePrefix(generator, object);
|
||||
try (JsonGenerator generator = this.objectMapper.getFactory().createGenerator(stream, this.encoding)) {
|
||||
writePrefix(generator, object);
|
||||
|
||||
Object value = object;
|
||||
Class<?> serializationView = null;
|
||||
FilterProvider filters = null;
|
||||
Object value = object;
|
||||
Class<?> serializationView = null;
|
||||
FilterProvider filters = null;
|
||||
|
||||
if (value instanceof MappingJacksonValue) {
|
||||
MappingJacksonValue container = (MappingJacksonValue) value;
|
||||
value = container.getValue();
|
||||
serializationView = container.getSerializationView();
|
||||
filters = container.getFilters();
|
||||
if (value instanceof MappingJacksonValue) {
|
||||
MappingJacksonValue container = (MappingJacksonValue) value;
|
||||
value = container.getValue();
|
||||
serializationView = container.getSerializationView();
|
||||
filters = container.getFilters();
|
||||
}
|
||||
|
||||
ObjectWriter objectWriter = (serializationView != null ?
|
||||
this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());
|
||||
if (filters != null) {
|
||||
objectWriter = objectWriter.with(filters);
|
||||
}
|
||||
objectWriter.writeValue(generator, value);
|
||||
|
||||
writeSuffix(generator, object);
|
||||
generator.flush();
|
||||
}
|
||||
|
||||
ObjectWriter objectWriter = (serializationView != null ?
|
||||
this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer());
|
||||
if (filters != null) {
|
||||
objectWriter = objectWriter.with(filters);
|
||||
}
|
||||
objectWriter.writeValue(generator, value);
|
||||
|
||||
writeSuffix(generator, object);
|
||||
generator.flush();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue