Skip to content

Commit 53cafe7

Browse files
committed
Support to customize rather than replace default codecs
See gh-26212
1 parent 58e9b18 commit 53cafe7

File tree

4 files changed

+99
-18
lines changed

4 files changed

+99
-18
lines changed

spring-web/src/main/java/org/springframework/http/codec/CodecConfigurer.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -192,6 +192,17 @@ interface DefaultCodecs {
192192
*/
193193
void kotlinSerializationJsonEncoder(Encoder<?> encoder);
194194

195+
/**
196+
* Register a consumer to apply to default config instances. This can be
197+
* used to configure rather than replace a specific codec or multiple
198+
* codecs. The consumer is applied to every default {@link Encoder},
199+
* {@link Decoder}, {@link HttpMessageReader} and {@link HttpMessageWriter}
200+
* instance.
201+
* @param codecConsumer the consumer to apply
202+
* @since 5.3.4
203+
*/
204+
void configureDefaultCodec(Consumer<Object> codecConsumer);
205+
195206
/**
196207
* Configure a limit on the number of bytes that can be buffered whenever
197208
* the input stream needs to be aggregated. This can be a result of

spring-web/src/main/java/org/springframework/http/codec/json/Jackson2CodecSupport.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public abstract class Jackson2CodecSupport {
8484

8585
protected final Log logger = HttpLogging.forLogName(getClass());
8686

87-
private final ObjectMapper defaultObjectMapper;
87+
private ObjectMapper defaultObjectMapper;
8888

8989
@Nullable
9090
private Map<Class<?>, Map<MimeType, ObjectMapper>> objectMapperRegistrations;
@@ -103,6 +103,19 @@ protected Jackson2CodecSupport(ObjectMapper objectMapper, MimeType... mimeTypes)
103103
}
104104

105105

106+
/**
107+
* Configure the default ObjectMapper instance to use.
108+
* @param objectMapper the ObjectMapper instance
109+
* @since 5.3.4
110+
*/
111+
public void setObjectMapper(ObjectMapper objectMapper) {
112+
Assert.notNull(objectMapper, "ObjectMapper must not be null");
113+
this.defaultObjectMapper = objectMapper;
114+
}
115+
116+
/**
117+
* Return the {@link #setObjectMapper configured} default ObjectMapper.
118+
*/
106119
public ObjectMapper getObjectMapper() {
107120
return this.defaultObjectMapper;
108121
}

spring-web/src/main/java/org/springframework/http/codec/support/BaseDefaultCodecs.java

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Collections;
2121
import java.util.List;
2222
import java.util.Map;
23+
import java.util.function.Consumer;
2324

2425
import org.springframework.core.SpringProperties;
2526
import org.springframework.core.codec.AbstractDataBufferDecoder;
@@ -46,6 +47,7 @@
4647
import org.springframework.http.codec.ResourceHttpMessageReader;
4748
import org.springframework.http.codec.ResourceHttpMessageWriter;
4849
import org.springframework.http.codec.ServerSentEventHttpMessageReader;
50+
import org.springframework.http.codec.ServerSentEventHttpMessageWriter;
4951
import org.springframework.http.codec.json.AbstractJackson2Decoder;
5052
import org.springframework.http.codec.json.Jackson2JsonDecoder;
5153
import org.springframework.http.codec.json.Jackson2JsonEncoder;
@@ -139,6 +141,9 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
139141
@Nullable
140142
private Encoder<?> kotlinSerializationJsonEncoder;
141143

144+
@Nullable
145+
private Consumer<Object> codecConsumer;
146+
142147
@Nullable
143148
private Integer maxInMemorySize;
144149

@@ -196,6 +201,7 @@ protected BaseDefaultCodecs(BaseDefaultCodecs other) {
196201
this.jaxb2Encoder = other.jaxb2Encoder;
197202
this.kotlinSerializationJsonDecoder = other.kotlinSerializationJsonDecoder;
198203
this.kotlinSerializationJsonEncoder = other.kotlinSerializationJsonEncoder;
204+
this.codecConsumer = other.codecConsumer;
199205
this.maxInMemorySize = other.maxInMemorySize;
200206
this.enableLoggingRequestDetails = other.enableLoggingRequestDetails;
201207
this.registerDefaults = other.registerDefaults;
@@ -265,6 +271,14 @@ public void kotlinSerializationJsonEncoder(Encoder<?> encoder) {
265271
initObjectWriters();
266272
}
267273

274+
@Override
275+
public void configureDefaultCodec(Consumer<Object> codecConsumer) {
276+
this.codecConsumer = (this.codecConsumer != null ?
277+
this.codecConsumer.andThen(codecConsumer) : codecConsumer);
278+
initReaders();
279+
initWriters();
280+
}
281+
268282
@Override
269283
public void maxInMemorySize(int byteCount) {
270284
if (!ObjectUtils.nullSafeEquals(this.maxInMemorySize, byteCount)) {
@@ -359,6 +373,9 @@ private void initCodec(@Nullable Object codec) {
359373
if (codec instanceof DecoderHttpMessageReader) {
360374
codec = ((DecoderHttpMessageReader) codec).getDecoder();
361375
}
376+
else if (codec instanceof EncoderHttpMessageWriter) {
377+
codec = ((EncoderHttpMessageWriter<?>) codec).getEncoder();
378+
}
362379

363380
if (codec == null) {
364381
return;
@@ -394,7 +411,6 @@ private void initCodec(@Nullable Object codec) {
394411
}
395412
if (codec instanceof ServerSentEventHttpMessageReader) {
396413
((ServerSentEventHttpMessageReader) codec).setMaxInMemorySize(size);
397-
initCodec(((ServerSentEventHttpMessageReader) codec).getDecoder());
398414
}
399415
if (codec instanceof DefaultPartHttpMessageReader) {
400416
((DefaultPartHttpMessageReader) codec).setMaxInMemorySize(size);
@@ -430,12 +446,23 @@ private void initCodec(@Nullable Object codec) {
430446
}
431447
}
432448

449+
if (this.codecConsumer != null) {
450+
this.codecConsumer.accept(codec);
451+
}
452+
453+
// Recurse for nested codecs
433454
if (codec instanceof MultipartHttpMessageReader) {
434455
initCodec(((MultipartHttpMessageReader) codec).getPartReader());
435456
}
436457
else if (codec instanceof MultipartHttpMessageWriter) {
437458
initCodec(((MultipartHttpMessageWriter) codec).getFormWriter());
438459
}
460+
else if (codec instanceof ServerSentEventHttpMessageReader) {
461+
initCodec(((ServerSentEventHttpMessageReader) codec).getDecoder());
462+
}
463+
else if (codec instanceof ServerSentEventHttpMessageWriter) {
464+
initCodec(((ServerSentEventHttpMessageWriter) codec).getEncoder());
465+
}
439466
}
440467

441468
/**
@@ -521,22 +548,21 @@ protected void initTypedWriters() {
521548
/**
522549
* Return "base" typed writers only, i.e. common to client and server.
523550
*/
524-
@SuppressWarnings("unchecked")
525551
final List<HttpMessageWriter<?>> getBaseTypedWriters() {
526552
if (!this.registerDefaults) {
527553
return Collections.emptyList();
528554
}
529555
List<HttpMessageWriter<?>> writers = new ArrayList<>();
530-
writers.add(new EncoderHttpMessageWriter<>(new ByteArrayEncoder()));
531-
writers.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
532-
writers.add(new EncoderHttpMessageWriter<>(new DataBufferEncoder()));
556+
addCodec(writers, new EncoderHttpMessageWriter<>(new ByteArrayEncoder()));
557+
addCodec(writers, new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
558+
addCodec(writers, new EncoderHttpMessageWriter<>(new DataBufferEncoder()));
533559
if (nettyByteBufPresent) {
534-
writers.add(new EncoderHttpMessageWriter<>(new NettyByteBufEncoder()));
560+
addCodec(writers, new EncoderHttpMessageWriter<>(new NettyByteBufEncoder()));
535561
}
536-
writers.add(new ResourceHttpMessageWriter());
537-
writers.add(new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly()));
562+
addCodec(writers, new ResourceHttpMessageWriter());
563+
addCodec(writers, new EncoderHttpMessageWriter<>(CharSequenceEncoder.textPlainOnly()));
538564
if (protobufPresent) {
539-
writers.add(new ProtobufHttpMessageWriter(this.protobufEncoder != null ?
565+
addCodec(writers, new ProtobufHttpMessageWriter(this.protobufEncoder != null ?
540566
(ProtobufEncoder) this.protobufEncoder : new ProtobufEncoder()));
541567
}
542568
return writers;
@@ -574,17 +600,17 @@ protected void initObjectWriters() {
574600
final List<HttpMessageWriter<?>> getBaseObjectWriters() {
575601
List<HttpMessageWriter<?>> writers = new ArrayList<>();
576602
if (kotlinSerializationJsonPresent) {
577-
writers.add(new EncoderHttpMessageWriter<>(getKotlinSerializationJsonEncoder()));
603+
addCodec(writers, new EncoderHttpMessageWriter<>(getKotlinSerializationJsonEncoder()));
578604
}
579605
if (jackson2Present) {
580-
writers.add(new EncoderHttpMessageWriter<>(getJackson2JsonEncoder()));
606+
addCodec(writers, new EncoderHttpMessageWriter<>(getJackson2JsonEncoder()));
581607
}
582608
if (jackson2SmilePresent) {
583-
writers.add(new EncoderHttpMessageWriter<>(this.jackson2SmileEncoder != null ?
609+
addCodec(writers, new EncoderHttpMessageWriter<>(this.jackson2SmileEncoder != null ?
584610
(Jackson2SmileEncoder) this.jackson2SmileEncoder : new Jackson2SmileEncoder()));
585611
}
586612
if (jaxb2Present && !shouldIgnoreXml) {
587-
writers.add(new EncoderHttpMessageWriter<>(this.jaxb2Encoder != null ?
613+
addCodec(writers, new EncoderHttpMessageWriter<>(this.jaxb2Encoder != null ?
588614
(Jaxb2XmlEncoder) this.jaxb2Encoder : new Jaxb2XmlEncoder()));
589615
}
590616
return writers;

spring-web/src/test/java/org/springframework/http/codec/support/ClientCodecConfigurerTests.java

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import java.util.List;
2424
import java.util.concurrent.atomic.AtomicInteger;
2525

26+
import com.fasterxml.jackson.databind.ObjectMapper;
2627
import org.junit.jupiter.api.Test;
2728
import reactor.core.publisher.Flux;
2829

@@ -52,6 +53,7 @@
5253
import org.springframework.http.codec.ResourceHttpMessageReader;
5354
import org.springframework.http.codec.ResourceHttpMessageWriter;
5455
import org.springframework.http.codec.ServerSentEventHttpMessageReader;
56+
import org.springframework.http.codec.json.Jackson2CodecSupport;
5557
import org.springframework.http.codec.json.Jackson2JsonDecoder;
5658
import org.springframework.http.codec.json.Jackson2JsonEncoder;
5759
import org.springframework.http.codec.json.Jackson2SmileDecoder;
@@ -121,12 +123,29 @@ public void defaultWriters() {
121123
}
122124

123125
@Test
124-
public void jackson2EncoderOverride() {
126+
public void jackson2CodecCustomizations() {
125127
Jackson2JsonDecoder decoder = new Jackson2JsonDecoder();
128+
Jackson2JsonEncoder encoder = new Jackson2JsonEncoder();
126129
this.configurer.defaultCodecs().jackson2JsonDecoder(decoder);
130+
this.configurer.defaultCodecs().jackson2JsonEncoder(encoder);
131+
132+
ObjectMapper objectMapper = new ObjectMapper();
133+
this.configurer.defaultCodecs().configureDefaultCodec(codec -> {
134+
if (codec instanceof Jackson2CodecSupport) {
135+
((Jackson2CodecSupport) codec).setObjectMapper(objectMapper);
136+
}
137+
});
127138

128139
List<HttpMessageReader<?>> readers = this.configurer.getReaders();
140+
Jackson2JsonDecoder actualDecoder = findCodec(readers, Jackson2JsonDecoder.class);
141+
assertThat(actualDecoder).isSameAs(decoder);
142+
assertThat(actualDecoder.getObjectMapper()).isSameAs(objectMapper);
129143
assertThat(findCodec(readers, ServerSentEventHttpMessageReader.class).getDecoder()).isSameAs(decoder);
144+
145+
List<HttpMessageWriter<?>> writers = this.configurer.getWriters();
146+
Jackson2JsonEncoder actualEncoder = findCodec(writers, Jackson2JsonEncoder.class);
147+
assertThat(actualEncoder).isSameAs(encoder);
148+
assertThat(actualEncoder.getObjectMapper()).isSameAs(objectMapper);
130149
}
131150

132151
@Test
@@ -237,7 +256,19 @@ private Encoder<?> getNextEncoder(List<HttpMessageWriter<?>> writers) {
237256

238257
@SuppressWarnings("unchecked")
239258
private <T> T findCodec(List<?> codecs, Class<T> type) {
240-
return (T) codecs.stream().filter(type::isInstance).findFirst().get();
259+
return (T) codecs.stream()
260+
.map(c -> {
261+
if (c instanceof EncoderHttpMessageWriter) {
262+
return ((EncoderHttpMessageWriter<?>) c).getEncoder();
263+
}
264+
else if (c instanceof DecoderHttpMessageReader) {
265+
return ((DecoderHttpMessageReader<?>) c).getDecoder();
266+
}
267+
else {
268+
return c;
269+
}
270+
})
271+
.filter(type::isInstance).findFirst().get();
241272
}
242273

243274
@SuppressWarnings("unchecked")

0 commit comments

Comments
 (0)