Browse Source

Configure custom converters ahead in HttpMessageConverters

This commit aligns `HttpMessageConverters` with the WebFlux codecs
configuration by adding custom converters first, before the
auto-detected and well-known ones.

This is a general approach in Spring Framework, where custom
implementations are more likely to handle specific uses cases and should
be involved before considering the more general implementations.

Fixes gh-35177
pull/35186/head
Brian Clozel 5 months ago
parent
commit
0a705f467d
  1. 53
      spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java
  2. 4
      spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java
  3. 33
      spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java

53
spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java

@ -149,7 +149,7 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { @@ -149,7 +149,7 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
@Override
public DefaultBuilder additionalMessageConverter(HttpMessageConverter<?> customConverter) {
Assert.notNull(customConverter, "'customConverter' must not be null");
this.commonMessageConverters.additionalMessageConverters.add(customConverter);
this.commonMessageConverters.customMessageConverters.add(customConverter);
return this;
}
@ -216,7 +216,13 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { @@ -216,7 +216,13 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
private @Nullable HttpMessageConverter<?> yamlMessageConverter;
private final List<HttpMessageConverter<?>> additionalMessageConverters = new ArrayList<>();
private @Nullable HttpMessageConverter<?> protobufMessageConverter;
private @Nullable HttpMessageConverter<?> atomMessageConverter;
private @Nullable HttpMessageConverter<?> rssMessageConverter;
private final List<HttpMessageConverter<?>> customMessageConverters = new ArrayList<>();
static {
ClassLoader classLoader = DefaultClientMessageConverterConfigurer.class.getClassLoader();
@ -340,13 +346,34 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { @@ -340,13 +346,34 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
this.inheritedMessageConverters.xmlMessageConverter != null) {
converters.add(this.inheritedMessageConverters.xmlMessageConverter);
}
if (this.protobufMessageConverter != null) {
converters.add(this.protobufMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.protobufMessageConverter != null) {
converters.add(this.inheritedMessageConverters.protobufMessageConverter);
}
if (this.atomMessageConverter != null) {
converters.add(this.atomMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.atomMessageConverter != null) {
converters.add(this.inheritedMessageConverters.atomMessageConverter);
}
if (this.rssMessageConverter != null) {
converters.add(this.rssMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.rssMessageConverter != null) {
converters.add(this.inheritedMessageConverters.rssMessageConverter);
}
return converters;
}
List<HttpMessageConverter<?>> getCustomConverters() {
List<HttpMessageConverter<?>> result = new ArrayList<>(this.additionalMessageConverters);
List<HttpMessageConverter<?>> result = new ArrayList<>(this.customMessageConverters);
if (this.inheritedMessageConverters != null) {
result.addAll(this.inheritedMessageConverters.additionalMessageConverters);
result.addAll(this.inheritedMessageConverters.customMessageConverters);
}
return result;
}
@ -405,12 +432,12 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { @@ -405,12 +432,12 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
}
if (isKotlinSerializationProtobufPresent) {
this.additionalMessageConverters.add(new KotlinSerializationProtobufHttpMessageConverter());
this.protobufMessageConverter = new KotlinSerializationProtobufHttpMessageConverter();
}
if (isRomePresent) {
this.additionalMessageConverters.add(new AtomFeedHttpMessageConverter());
this.additionalMessageConverters.add(new RssChannelHttpMessageConverter());
this.atomMessageConverter = new AtomFeedHttpMessageConverter();
this.rssMessageConverter = new RssChannelHttpMessageConverter();
}
}
@ -466,7 +493,7 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { @@ -466,7 +493,7 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
@Override
public ClientMessageConverterConfigurer additionalMessageConverter(HttpMessageConverter<?> customConverter) {
Assert.notNull(customConverter, "'customConverter' must not be null");
this.clientMessageConverters.additionalMessageConverters.add(customConverter);
this.clientMessageConverters.customMessageConverters.add(customConverter);
return this;
}
@ -485,16 +512,16 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { @@ -485,16 +512,16 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
List<HttpMessageConverter<?>> allConverters = new ArrayList<>();
List<HttpMessageConverter<?>> partConverters = new ArrayList<>();
partConverters.addAll(this.clientMessageConverters.getCoreConverters());
partConverters.addAll(this.clientMessageConverters.getCustomConverters());
partConverters.addAll(this.clientMessageConverters.getCoreConverters());
allConverters.addAll(this.clientMessageConverters.getCustomConverters());
allConverters.addAll(this.clientMessageConverters.getBaseConverters());
allConverters.addAll(this.resourceMessageConverters);
if (!partConverters.isEmpty()) {
allConverters.add(new AllEncompassingFormHttpMessageConverter(partConverters));
}
allConverters.addAll(this.clientMessageConverters.getCoreConverters());
allConverters.addAll(this.clientMessageConverters.getCustomConverters());
if (this.configurer != null) {
allConverters.forEach(this.configurer);
@ -553,7 +580,7 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { @@ -553,7 +580,7 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
@Override
public ServerMessageConverterConfigurer additionalMessageConverter(HttpMessageConverter<?> customConverter) {
Assert.notNull(customConverter, "'customConverter' must not be null");
this.serverMessageConverters.additionalMessageConverters.add(customConverter);
this.serverMessageConverters.customMessageConverters.add(customConverter);
return this;
}
@ -572,16 +599,16 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { @@ -572,16 +599,16 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
List<HttpMessageConverter<?>> allConverters = new ArrayList<>();
List<HttpMessageConverter<?>> partConverters = new ArrayList<>();
partConverters.addAll(this.serverMessageConverters.getCoreConverters());
partConverters.addAll(this.serverMessageConverters.getCustomConverters());
partConverters.addAll(this.serverMessageConverters.getCoreConverters());
allConverters.addAll(this.serverMessageConverters.getCustomConverters());
allConverters.addAll(this.serverMessageConverters.getBaseConverters());
allConverters.addAll(this.resourceMessageConverters);
if (!partConverters.isEmpty()) {
allConverters.add(new AllEncompassingFormHttpMessageConverter(partConverters));
}
allConverters.addAll(this.serverMessageConverters.getCoreConverters());
allConverters.addAll(this.serverMessageConverters.getCustomConverters());
if (this.configurer != null) {
allConverters.forEach(this.configurer);
}

4
spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java

@ -31,6 +31,7 @@ import java.util.function.Consumer; @@ -31,6 +31,7 @@ import java.util.function.Consumer;
* <p>The following HTTP message converters will be detected and registered if available, in order.
* For {@link #forClient() client side converters}:
* <ol>
* <li>All custom message converters configured with the builder
* <li>{@link ByteArrayHttpMessageConverter}
* <li>{@link StringHttpMessageConverter} with the {@link java.nio.charset.StandardCharsets#ISO_8859_1} charset
* <li>{@link ResourceHttpMessageConverter}, with resource streaming support disabled
@ -42,11 +43,11 @@ import java.util.function.Consumer; @@ -42,11 +43,11 @@ import java.util.function.Consumer;
* <li>An XML converter
* <li>An ProtoBuf converter
* <li>ATOM and RSS converters
* <li>All custom message converters configured with the builder
* </ol>
*
* For {@link #forClient() client side converters}:
* <ol>
* <li>All custom message converters configured with the builder
* <li>{@link ByteArrayHttpMessageConverter}
* <li>{@link StringHttpMessageConverter} with the {@link java.nio.charset.StandardCharsets#ISO_8859_1} charset
* <li>{@link ResourceHttpMessageConverter}
@ -58,7 +59,6 @@ import java.util.function.Consumer; @@ -58,7 +59,6 @@ import java.util.function.Consumer;
* <li>An XML converter
* <li>An ProtoBuf converter
* <li>ATOM and RSS converters
* <li>All custom message converters configured with the builder
* <li>a Multipart converter, using all detected and custom converters for part conversion
* </ol>
*

33
spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java

@ -150,7 +150,21 @@ class DefaultHttpMessageConvertersTests { @@ -150,7 +150,21 @@ class DefaultHttpMessageConvertersTests {
void registerCustomMessageConverter() {
var converters = HttpMessageConverters.create()
.additionalMessageConverter(new CustomHttpMessageConverter()).build();
assertThat(converters.forClient()).hasExactlyElementsOfTypes(AllEncompassingFormHttpMessageConverter.class, CustomHttpMessageConverter.class);
assertThat(converters.forClient()).hasExactlyElementsOfTypes(CustomHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class);
}
@Test
void registerCustomMessageConverterAheadOfDefaults() {
var converters = HttpMessageConverters.withDefaults()
.additionalMessageConverter(new CustomHttpMessageConverter()).build();
assertThat(converters.forClient()).hasExactlyElementsOfTypes(
CustomHttpMessageConverter.class, ByteArrayHttpMessageConverter.class,
StringHttpMessageConverter.class, ResourceHttpMessageConverter.class,
AllEncompassingFormHttpMessageConverter.class,
JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class,
JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class,
JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class,
AtomFeedHttpMessageConverter.class, RssChannelHttpMessageConverter.class);
}
@Test
@ -224,7 +238,22 @@ class DefaultHttpMessageConvertersTests { @@ -224,7 +238,22 @@ class DefaultHttpMessageConvertersTests {
void registerCustomMessageConverter() {
var converters = HttpMessageConverters.create()
.additionalMessageConverter(new CustomHttpMessageConverter()).build();
assertThat(converters.forServer()).hasExactlyElementsOfTypes(AllEncompassingFormHttpMessageConverter.class, CustomHttpMessageConverter.class);
assertThat(converters.forServer()).hasExactlyElementsOfTypes(CustomHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class);
}
@Test
void registerCustomMessageConverterAheadOfDefaults() {
var converters = HttpMessageConverters.withDefaults()
.additionalMessageConverter(new CustomHttpMessageConverter()).build();
assertThat(converters.forServer()).hasExactlyElementsOfTypes(
CustomHttpMessageConverter.class,
ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class,
ResourceHttpMessageConverter.class, ResourceRegionHttpMessageConverter.class,
AllEncompassingFormHttpMessageConverter.class,
JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class,
JacksonCborHttpMessageConverter.class, JacksonYamlHttpMessageConverter.class,
JacksonXmlHttpMessageConverter.class, KotlinSerializationProtobufHttpMessageConverter.class,
AtomFeedHttpMessageConverter.class, RssChannelHttpMessageConverter.class);
}
@Test

Loading…
Cancel
Save