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 {
@Override @Override
public DefaultBuilder additionalMessageConverter(HttpMessageConverter<?> customConverter) { public DefaultBuilder additionalMessageConverter(HttpMessageConverter<?> customConverter) {
Assert.notNull(customConverter, "'customConverter' must not be null"); Assert.notNull(customConverter, "'customConverter' must not be null");
this.commonMessageConverters.additionalMessageConverters.add(customConverter); this.commonMessageConverters.customMessageConverters.add(customConverter);
return this; return this;
} }
@ -216,7 +216,13 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
private @Nullable HttpMessageConverter<?> yamlMessageConverter; 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 { static {
ClassLoader classLoader = DefaultClientMessageConverterConfigurer.class.getClassLoader(); ClassLoader classLoader = DefaultClientMessageConverterConfigurer.class.getClassLoader();
@ -340,13 +346,34 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
this.inheritedMessageConverters.xmlMessageConverter != null) { this.inheritedMessageConverters.xmlMessageConverter != null) {
converters.add(this.inheritedMessageConverters.xmlMessageConverter); 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; return converters;
} }
List<HttpMessageConverter<?>> getCustomConverters() { List<HttpMessageConverter<?>> getCustomConverters() {
List<HttpMessageConverter<?>> result = new ArrayList<>(this.additionalMessageConverters); List<HttpMessageConverter<?>> result = new ArrayList<>(this.customMessageConverters);
if (this.inheritedMessageConverters != null) { if (this.inheritedMessageConverters != null) {
result.addAll(this.inheritedMessageConverters.additionalMessageConverters); result.addAll(this.inheritedMessageConverters.customMessageConverters);
} }
return result; return result;
} }
@ -405,12 +432,12 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
} }
if (isKotlinSerializationProtobufPresent) { if (isKotlinSerializationProtobufPresent) {
this.additionalMessageConverters.add(new KotlinSerializationProtobufHttpMessageConverter()); this.protobufMessageConverter = new KotlinSerializationProtobufHttpMessageConverter();
} }
if (isRomePresent) { if (isRomePresent) {
this.additionalMessageConverters.add(new AtomFeedHttpMessageConverter()); this.atomMessageConverter = new AtomFeedHttpMessageConverter();
this.additionalMessageConverters.add(new RssChannelHttpMessageConverter()); this.rssMessageConverter = new RssChannelHttpMessageConverter();
} }
} }
@ -466,7 +493,7 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
@Override @Override
public ClientMessageConverterConfigurer additionalMessageConverter(HttpMessageConverter<?> customConverter) { public ClientMessageConverterConfigurer additionalMessageConverter(HttpMessageConverter<?> customConverter) {
Assert.notNull(customConverter, "'customConverter' must not be null"); Assert.notNull(customConverter, "'customConverter' must not be null");
this.clientMessageConverters.additionalMessageConverters.add(customConverter); this.clientMessageConverters.customMessageConverters.add(customConverter);
return this; return this;
} }
@ -485,16 +512,16 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
List<HttpMessageConverter<?>> allConverters = new ArrayList<>(); List<HttpMessageConverter<?>> allConverters = new ArrayList<>();
List<HttpMessageConverter<?>> partConverters = new ArrayList<>(); List<HttpMessageConverter<?>> partConverters = new ArrayList<>();
partConverters.addAll(this.clientMessageConverters.getCoreConverters());
partConverters.addAll(this.clientMessageConverters.getCustomConverters()); partConverters.addAll(this.clientMessageConverters.getCustomConverters());
partConverters.addAll(this.clientMessageConverters.getCoreConverters());
allConverters.addAll(this.clientMessageConverters.getCustomConverters());
allConverters.addAll(this.clientMessageConverters.getBaseConverters()); allConverters.addAll(this.clientMessageConverters.getBaseConverters());
allConverters.addAll(this.resourceMessageConverters); allConverters.addAll(this.resourceMessageConverters);
if (!partConverters.isEmpty()) { if (!partConverters.isEmpty()) {
allConverters.add(new AllEncompassingFormHttpMessageConverter(partConverters)); allConverters.add(new AllEncompassingFormHttpMessageConverter(partConverters));
} }
allConverters.addAll(this.clientMessageConverters.getCoreConverters()); allConverters.addAll(this.clientMessageConverters.getCoreConverters());
allConverters.addAll(this.clientMessageConverters.getCustomConverters());
if (this.configurer != null) { if (this.configurer != null) {
allConverters.forEach(this.configurer); allConverters.forEach(this.configurer);
@ -553,7 +580,7 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
@Override @Override
public ServerMessageConverterConfigurer additionalMessageConverter(HttpMessageConverter<?> customConverter) { public ServerMessageConverterConfigurer additionalMessageConverter(HttpMessageConverter<?> customConverter) {
Assert.notNull(customConverter, "'customConverter' must not be null"); Assert.notNull(customConverter, "'customConverter' must not be null");
this.serverMessageConverters.additionalMessageConverters.add(customConverter); this.serverMessageConverters.customMessageConverters.add(customConverter);
return this; return this;
} }
@ -572,16 +599,16 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
List<HttpMessageConverter<?>> allConverters = new ArrayList<>(); List<HttpMessageConverter<?>> allConverters = new ArrayList<>();
List<HttpMessageConverter<?>> partConverters = new ArrayList<>(); List<HttpMessageConverter<?>> partConverters = new ArrayList<>();
partConverters.addAll(this.serverMessageConverters.getCoreConverters());
partConverters.addAll(this.serverMessageConverters.getCustomConverters()); partConverters.addAll(this.serverMessageConverters.getCustomConverters());
partConverters.addAll(this.serverMessageConverters.getCoreConverters());
allConverters.addAll(this.serverMessageConverters.getCustomConverters());
allConverters.addAll(this.serverMessageConverters.getBaseConverters()); allConverters.addAll(this.serverMessageConverters.getBaseConverters());
allConverters.addAll(this.resourceMessageConverters); allConverters.addAll(this.resourceMessageConverters);
if (!partConverters.isEmpty()) { if (!partConverters.isEmpty()) {
allConverters.add(new AllEncompassingFormHttpMessageConverter(partConverters)); allConverters.add(new AllEncompassingFormHttpMessageConverter(partConverters));
} }
allConverters.addAll(this.serverMessageConverters.getCoreConverters()); allConverters.addAll(this.serverMessageConverters.getCoreConverters());
allConverters.addAll(this.serverMessageConverters.getCustomConverters());
if (this.configurer != null) { if (this.configurer != null) {
allConverters.forEach(this.configurer); 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;
* <p>The following HTTP message converters will be detected and registered if available, in order. * <p>The following HTTP message converters will be detected and registered if available, in order.
* For {@link #forClient() client side converters}: * For {@link #forClient() client side converters}:
* <ol> * <ol>
* <li>All custom message converters configured with the builder
* <li>{@link ByteArrayHttpMessageConverter} * <li>{@link ByteArrayHttpMessageConverter}
* <li>{@link StringHttpMessageConverter} with the {@link java.nio.charset.StandardCharsets#ISO_8859_1} charset * <li>{@link StringHttpMessageConverter} with the {@link java.nio.charset.StandardCharsets#ISO_8859_1} charset
* <li>{@link ResourceHttpMessageConverter}, with resource streaming support disabled * <li>{@link ResourceHttpMessageConverter}, with resource streaming support disabled
@ -42,11 +43,11 @@ import java.util.function.Consumer;
* <li>An XML converter * <li>An XML converter
* <li>An ProtoBuf converter * <li>An ProtoBuf converter
* <li>ATOM and RSS converters * <li>ATOM and RSS converters
* <li>All custom message converters configured with the builder
* </ol> * </ol>
* *
* For {@link #forClient() client side converters}: * For {@link #forClient() client side converters}:
* <ol> * <ol>
* <li>All custom message converters configured with the builder
* <li>{@link ByteArrayHttpMessageConverter} * <li>{@link ByteArrayHttpMessageConverter}
* <li>{@link StringHttpMessageConverter} with the {@link java.nio.charset.StandardCharsets#ISO_8859_1} charset * <li>{@link StringHttpMessageConverter} with the {@link java.nio.charset.StandardCharsets#ISO_8859_1} charset
* <li>{@link ResourceHttpMessageConverter} * <li>{@link ResourceHttpMessageConverter}
@ -58,7 +59,6 @@ import java.util.function.Consumer;
* <li>An XML converter * <li>An XML converter
* <li>An ProtoBuf converter * <li>An ProtoBuf converter
* <li>ATOM and RSS converters * <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 * <li>a Multipart converter, using all detected and custom converters for part conversion
* </ol> * </ol>
* *

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

@ -150,7 +150,21 @@ class DefaultHttpMessageConvertersTests {
void registerCustomMessageConverter() { void registerCustomMessageConverter() {
var converters = HttpMessageConverters.create() var converters = HttpMessageConverters.create()
.additionalMessageConverter(new CustomHttpMessageConverter()).build(); .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 @Test
@ -224,7 +238,22 @@ class DefaultHttpMessageConvertersTests {
void registerCustomMessageConverter() { void registerCustomMessageConverter() {
var converters = HttpMessageConverters.create() var converters = HttpMessageConverters.create()
.additionalMessageConverter(new CustomHttpMessageConverter()).build(); .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 @Test

Loading…
Cancel
Save