Browse Source

Fix message converter customizers order

Prior to this commit, gh-48310 separated client and server message
converter configurations by switching from message converter instances
as beans in the application context, to server/client customizers that
are applied to the `HttpMessageConverters` instances while being built.

This change did not order the new ClientHttpMessageConvertersCustomizer
or ServerHttpMessageConvertersCustomizer, letting those being at the
"lowest precedence" default. As customizers, this means they are applied
last and custom instances cannot take over.

This commit ensures that such customizers provided by Spring Boot are
now ordered at "0" to let applications ones take over.

Fixes gh-48635
pull/48817/head
Brian Clozel 4 weeks ago committed by Stéphane Nicoll
parent
commit
9d1db6830f
  1. 2
      module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.java
  2. 3
      module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration.java
  3. 3
      module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration.java
  4. 2
      module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JsonbHttpMessageConvertersConfiguration.java
  5. 2
      module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/KotlinSerializationHttpMessageConvertersConfiguration.java
  6. 68
      module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfigurationTests.java

2
module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.java

@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.condition.NoneNestedConditions; @@ -27,6 +27,7 @@ import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.converter.HttpMessageConverters.ClientBuilder;
import org.springframework.http.converter.HttpMessageConverters.ServerBuilder;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
@ -48,6 +49,7 @@ class GsonHttpMessageConvertersConfiguration { @@ -48,6 +49,7 @@ class GsonHttpMessageConvertersConfiguration {
static class GsonHttpMessageConverterConfiguration {
@Bean
@Order(0)
@ConditionalOnMissingBean(GsonHttpMessageConverter.class)
GsonHttpConvertersCustomizer gsonHttpMessageConvertersCustomizer(Gson gson) {
return new GsonHttpConvertersCustomizer(gson);

3
module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/Jackson2HttpMessageConvertersConfiguration.java

@ -28,6 +28,7 @@ import org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageC @@ -28,6 +28,7 @@ import org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageC
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.converter.HttpMessageConverters.ClientBuilder;
import org.springframework.http.converter.HttpMessageConverters.ServerBuilder;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@ -51,6 +52,7 @@ class Jackson2HttpMessageConvertersConfiguration { @@ -51,6 +52,7 @@ class Jackson2HttpMessageConvertersConfiguration {
static class MappingJackson2HttpMessageConverterConfiguration {
@Bean
@Order(0)
@ConditionalOnMissingBean(org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class)
Jackson2JsonMessageConvertersCustomizer jackson2HttpMessageConvertersCustomizer(ObjectMapper objectMapper) {
return new Jackson2JsonMessageConvertersCustomizer(objectMapper);
@ -64,6 +66,7 @@ class Jackson2HttpMessageConvertersConfiguration { @@ -64,6 +66,7 @@ class Jackson2HttpMessageConvertersConfiguration {
protected static class MappingJackson2XmlHttpMessageConverterConfiguration {
@Bean
@Order(0)
@ConditionalOnMissingBean(org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter.class)
Jackson2XmlMessageConvertersCustomizer mappingJackson2XmlHttpMessageConverter(
Jackson2ObjectMapperBuilder builder) {

3
module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JacksonHttpMessageConvertersConfiguration.java

@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.converter.HttpMessageConverters.ClientBuilder;
import org.springframework.http.converter.HttpMessageConverters.ServerBuilder;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
@ -47,6 +48,7 @@ class JacksonHttpMessageConvertersConfiguration { @@ -47,6 +48,7 @@ class JacksonHttpMessageConvertersConfiguration {
static class JacksonJsonHttpMessageConverterConfiguration {
@Bean
@Order(0)
@ConditionalOnMissingBean(value = JacksonJsonHttpMessageConverter.class,
ignoredType = { "org.springframework.hateoas.server.mvc.TypeConstrainedJacksonJsonHttpMessageConverter",
"org.springframework.data.rest.webmvc.alps.AlpsJacksonJsonHttpMessageConverter" })
@ -62,6 +64,7 @@ class JacksonHttpMessageConvertersConfiguration { @@ -62,6 +64,7 @@ class JacksonHttpMessageConvertersConfiguration {
protected static class JacksonXmlHttpMessageConverterConfiguration {
@Bean
@Order(0)
@ConditionalOnMissingBean(JacksonXmlHttpMessageConverter.class)
JacksonXmlHttpMessageConvertersCustomizer jacksonXmlHttpMessageConvertersCustomizer(XmlMapper xmlMapper) {
return new JacksonXmlHttpMessageConvertersCustomizer(xmlMapper);

2
module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JsonbHttpMessageConvertersConfiguration.java

@ -28,6 +28,7 @@ import org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageC @@ -28,6 +28,7 @@ import org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageC
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.converter.HttpMessageConverters.ClientBuilder;
import org.springframework.http.converter.HttpMessageConverters.ServerBuilder;
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
@ -47,6 +48,7 @@ class JsonbHttpMessageConvertersConfiguration { @@ -47,6 +48,7 @@ class JsonbHttpMessageConvertersConfiguration {
static class JsonbHttpMessageConverterConfiguration {
@Bean
@Order(0)
@ConditionalOnMissingBean(JsonbHttpMessageConverter.class)
JsonbHttpMessageConvertersCustomizer jsonbHttpMessageConvertersCustomizer(Jsonb jsonb) {
return new JsonbHttpMessageConvertersCustomizer(jsonb);

2
module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/KotlinSerializationHttpMessageConvertersConfiguration.java

@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; @@ -24,6 +24,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.converter.HttpMessageConverters.ClientBuilder;
import org.springframework.http.converter.HttpMessageConverters.ServerBuilder;
@ -42,6 +43,7 @@ import org.springframework.util.ClassUtils; @@ -42,6 +43,7 @@ import org.springframework.util.ClassUtils;
class KotlinSerializationHttpMessageConvertersConfiguration {
@Bean
@Order(0)
@ConditionalOnMissingBean(KotlinSerializationJsonHttpMessageConverter.class)
KotlinSerializationJsonConvertersCustomizer kotlinSerializationJsonConvertersCustomizer(Json json,
ResourceLoader resourceLoader) {

68
module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfigurationTests.java

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.boot.http.converter.autoconfigure;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@ -52,11 +53,16 @@ import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguratio @@ -52,11 +53,16 @@ import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguratio
import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.mediatype.hal.forms.HalFormsHttpMessageConverter;
import org.springframework.hateoas.server.mvc.TypeConstrainedJacksonJsonHttpMessageConverter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverters;
import org.springframework.http.converter.HttpMessageConverters.ClientBuilder;
import org.springframework.http.converter.HttpMessageConverters.ServerBuilder;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
@ -103,6 +109,14 @@ class HttpMessageConvertersAutoConfigurationTests { @@ -103,6 +109,14 @@ class HttpMessageConvertersAutoConfigurationTests {
});
}
@Test
void jacksonServerCustomizer() {
this.contextRunner.withUserConfiguration(CustomJsonConverterConfig.class).run((context) -> {
assertConverterIsNotRegistered(context, JacksonJsonHttpMessageConverter.class);
assertConverterIsRegistered(context, CustomConverter.class);
});
}
@Test
void jacksonConverterWithBuilder() {
this.contextRunner.withUserConfiguration(JacksonJsonMapperBuilderConfig.class).run((context) -> {
@ -455,16 +469,16 @@ class HttpMessageConvertersAutoConfigurationTests { @@ -455,16 +469,16 @@ class HttpMessageConvertersAutoConfigurationTests {
private HttpMessageConverters getClientConverters(ApplicationContext context) {
ClientBuilder clientBuilder = HttpMessageConverters.forClient().registerDefaults();
context.getBeansOfType(ClientHttpMessageConvertersCustomizer.class)
.values()
context.getBeanProvider(ClientHttpMessageConvertersCustomizer.class)
.orderedStream()
.forEach((customizer) -> customizer.customize(clientBuilder));
return clientBuilder.build();
}
private HttpMessageConverters getServerConverters(ApplicationContext context) {
ServerBuilder serverBuilder = HttpMessageConverters.forServer().registerDefaults();
context.getBeansOfType(ServerHttpMessageConvertersCustomizer.class)
.values()
context.getBeanProvider(ServerHttpMessageConvertersCustomizer.class)
.orderedStream()
.forEach((customizer) -> customizer.customize(serverBuilder));
return serverBuilder.build();
}
@ -503,6 +517,26 @@ class HttpMessageConvertersAutoConfigurationTests { @@ -503,6 +517,26 @@ class HttpMessageConvertersAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class CustomJsonConverterConfig {
@Bean
JsonMapper jsonMapper() {
return new JsonMapper();
}
@Bean
ServerHttpMessageConvertersCustomizer jsonServerCustomizer() {
return (configurer) -> configurer.withJsonConverter(new CustomConverter(MediaType.APPLICATION_JSON));
}
@Bean
ClientHttpMessageConvertersCustomizer jsonClientCustomizer() {
return (configurer) -> configurer.withJsonConverter(new CustomConverter(MediaType.APPLICATION_JSON));
}
}
@Configuration(proxyBeanMethods = false)
static class JacksonJsonMapperBuilderConfig {
@ -640,4 +674,30 @@ class HttpMessageConvertersAutoConfigurationTests { @@ -640,4 +674,30 @@ class HttpMessageConvertersAutoConfigurationTests {
}
@SuppressWarnings("NullAway")
static class CustomConverter extends AbstractHttpMessageConverter<Object> {
CustomConverter(MediaType supportedMediaType) {
super(supportedMediaType);
}
@Override
protected boolean supports(Class<?> clazz) {
return true;
}
@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
protected void writeInternal(Object o, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
}
}
}

Loading…
Cancel
Save