diff --git a/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration.java b/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration.java index bace77bb3f6..db2083a355a 100644 --- a/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration.java +++ b/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfiguration.java @@ -30,7 +30,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; -import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; @@ -67,7 +66,7 @@ public final class HttpMessageConvertersAutoConfiguration { static final String PREFERRED_MAPPER_PROPERTY = "spring.http.converters.preferred-json-mapper"; @Bean - @Order(Ordered.LOWEST_PRECEDENCE) + @Order(0) @SuppressWarnings("deprecation") ClientHttpMessageConvertersCustomizer clientConvertersCustomizer( ObjectProvider legacyConverters, @@ -77,7 +76,7 @@ public final class HttpMessageConvertersAutoConfiguration { } @Bean - @Order(Ordered.LOWEST_PRECEDENCE) + @Order(0) @SuppressWarnings("deprecation") ServerHttpMessageConvertersCustomizer serverConvertersCustomizer( ObjectProvider legacyConverters, diff --git a/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfigurationTests.java b/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfigurationTests.java index e636dc87b8d..b22d73864d7 100644 --- a/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfigurationTests.java +++ b/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfigurationTests.java @@ -18,6 +18,7 @@ package org.springframework.boot.http.converter.autoconfigure; import java.nio.charset.StandardCharsets; import java.util.List; +import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; @@ -29,6 +30,7 @@ import tools.jackson.databind.json.JsonMapper; import tools.jackson.dataformat.xml.XmlMapper; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener; import org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration.JacksonJsonHttpMessageConverterConfiguration; @@ -348,6 +350,27 @@ class HttpMessageConvertersAutoConfigurationTests { }); } + @Test + void defaultServerConvertersCustomizerHasOrderZero() { + defaultConvertersCustomizerHasOrderZero(DefaultServerHttpMessageConvertersCustomizer.class); + } + + @Test + void defaultClientConvertersCustomizerHasOrderZero() { + defaultConvertersCustomizerHasOrderZero(DefaultClientHttpMessageConvertersCustomizer.class); + } + + private void defaultConvertersCustomizerHasOrderZero(Class customizerType) { + new WebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class)) + .run((context) -> { + Map customizers = context.getBeansOfType(customizerType); + assertThat(customizers).hasSize(1); + DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory(); + customizers.keySet().forEach((beanName) -> assertThat(beanFactory.getOrder(beanName)).isZero()); + }); + } + private ApplicationContextRunner allOptionsRunner() { return this.contextRunner.withBean(Gson.class) .withBean(JsonMapper.class) diff --git a/module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/RestClientAutoConfigurationTests.java b/module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/RestClientAutoConfigurationTests.java index 7816e0a2a68..1e08fbc2dda 100644 --- a/module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/RestClientAutoConfigurationTests.java +++ b/module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/RestClientAutoConfigurationTests.java @@ -22,6 +22,7 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.api.condition.JRE; +import org.mockito.InOrder; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; @@ -31,6 +32,7 @@ import org.springframework.boot.http.client.HttpRedirects; import org.springframework.boot.http.client.autoconfigure.HttpClientAutoConfiguration; import org.springframework.boot.http.client.autoconfigure.imperative.ImperativeHttpClientAutoConfiguration; import org.springframework.boot.http.client.autoconfigure.reactive.ReactiveHttpClientAutoConfiguration; +import org.springframework.boot.http.converter.autoconfigure.ClientHttpMessageConvertersCustomizer; import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.restclient.RestClientCustomizer; import org.springframework.boot.ssl.SslBundle; @@ -40,9 +42,11 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContex import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverters.ClientBuilder; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.client.RestClient; import org.springframework.web.client.RestClient.Builder; @@ -50,6 +54,7 @@ import org.springframework.web.client.RestClient.Builder; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; /** @@ -296,6 +301,24 @@ class RestClientAutoConfigurationTests { .run((context) -> assertThat(context).doesNotHaveBean(RestClient.Builder.class)); } + @Test + void clientHttpMessageConverterCustomizersAreAppliedInOrder() { + this.contextRunner.withUserConfiguration(ClientHttpMessageConverterCustomizersConfiguration.class) + .run((context) -> { + context.getBean(RestClient.Builder.class).build(); + ClientHttpMessageConvertersCustomizer customizer1 = context.getBean("customizer1", + ClientHttpMessageConvertersCustomizer.class); + ClientHttpMessageConvertersCustomizer customizer2 = context.getBean("customizer2", + ClientHttpMessageConvertersCustomizer.class); + ClientHttpMessageConvertersCustomizer customizer3 = context.getBean("customizer3", + ClientHttpMessageConvertersCustomizer.class); + InOrder inOrder = inOrder(customizer1, customizer2, customizer3); + inOrder.verify(customizer3).customize(any(ClientBuilder.class)); + inOrder.verify(customizer1).customize(any(ClientBuilder.class)); + inOrder.verify(customizer2).customize(any(ClientBuilder.class)); + }); + } + @Configuration(proxyBeanMethods = false) static class RestClientCustomizerConfig { @@ -334,4 +357,27 @@ class RestClientAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class ClientHttpMessageConverterCustomizersConfiguration { + + @Bean + @Order(-5) + ClientHttpMessageConvertersCustomizer customizer1() { + return mock(ClientHttpMessageConvertersCustomizer.class); + } + + @Bean + @Order(5) + ClientHttpMessageConvertersCustomizer customizer2() { + return mock(ClientHttpMessageConvertersCustomizer.class); + } + + @Bean + @Order(-10) + ClientHttpMessageConvertersCustomizer customizer3() { + return mock(ClientHttpMessageConvertersCustomizer.class); + } + + } + } diff --git a/module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/RestTemplateAutoConfigurationTests.java b/module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/RestTemplateAutoConfigurationTests.java index f488ed0e53b..c35411ab694 100644 --- a/module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/RestTemplateAutoConfigurationTests.java +++ b/module/spring-boot-restclient/src/test/java/org/springframework/boot/restclient/autoconfigure/RestTemplateAutoConfigurationTests.java @@ -19,11 +19,13 @@ package org.springframework.boot.restclient.autoconfigure; import java.util.Collections; import org.junit.jupiter.api.Test; +import org.mockito.InOrder; import org.springframework.beans.factory.support.BeanDefinitionOverrideException; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.http.client.autoconfigure.HttpClientAutoConfiguration; import org.springframework.boot.http.client.autoconfigure.imperative.ImperativeHttpClientAutoConfiguration; +import org.springframework.boot.http.converter.autoconfigure.ClientHttpMessageConvertersCustomizer; import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.restclient.RestTemplateBuilder; import org.springframework.boot.restclient.RestTemplateCustomizer; @@ -33,11 +35,13 @@ import org.springframework.boot.test.context.runner.ReactiveWebApplicationContex import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverters.ClientBuilder; import org.springframework.mock.http.client.MockClientHttpRequest; import org.springframework.mock.http.client.MockClientHttpResponse; import org.springframework.web.client.RestTemplate; @@ -47,6 +51,7 @@ import static org.assertj.core.api.Assertions.entry; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; /** @@ -192,6 +197,24 @@ class RestTemplateAutoConfigurationTests { }); } + @Test + void clientHttpMessageConverterCustomizersAreAppliedInOrder() { + this.contextRunner.withUserConfiguration(ClientHttpMessageConverterCustomizersConfiguration.class) + .run((context) -> { + context.getBean(RestTemplateBuilder.class).build(); + ClientHttpMessageConvertersCustomizer customizer1 = context.getBean("customizer1", + ClientHttpMessageConvertersCustomizer.class); + ClientHttpMessageConvertersCustomizer customizer2 = context.getBean("customizer2", + ClientHttpMessageConvertersCustomizer.class); + ClientHttpMessageConvertersCustomizer customizer3 = context.getBean("customizer3", + ClientHttpMessageConvertersCustomizer.class); + InOrder inOrder = inOrder(customizer1, customizer2, customizer3); + inOrder.verify(customizer3).customize(any(ClientBuilder.class)); + inOrder.verify(customizer1).customize(any(ClientBuilder.class)); + inOrder.verify(customizer2).customize(any(ClientBuilder.class)); + }); + } + @Configuration(proxyBeanMethods = false) static class RestTemplateConfig { @@ -283,6 +306,29 @@ class RestTemplateAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class ClientHttpMessageConverterCustomizersConfiguration { + + @Bean + @Order(-5) + ClientHttpMessageConvertersCustomizer customizer1() { + return mock(ClientHttpMessageConvertersCustomizer.class); + } + + @Bean + @Order(5) + ClientHttpMessageConvertersCustomizer customizer2() { + return mock(ClientHttpMessageConvertersCustomizer.class); + } + + @Bean + @Order(-10) + ClientHttpMessageConvertersCustomizer customizer3() { + return mock(ClientHttpMessageConvertersCustomizer.class); + } + + } + static class CustomHttpMessageConverter extends ByteArrayHttpMessageConverter { } diff --git a/module/spring-boot-resttestclient/src/test/java/org/springframework/boot/resttestclient/autoconfigure/RestTestClientAutoConfigurationTests.java b/module/spring-boot-resttestclient/src/test/java/org/springframework/boot/resttestclient/autoconfigure/RestTestClientAutoConfigurationTests.java index 620b3c829f2..1282790372b 100644 --- a/module/spring-boot-resttestclient/src/test/java/org/springframework/boot/resttestclient/autoconfigure/RestTestClientAutoConfigurationTests.java +++ b/module/spring-boot-resttestclient/src/test/java/org/springframework/boot/resttestclient/autoconfigure/RestTestClientAutoConfigurationTests.java @@ -19,8 +19,10 @@ package org.springframework.boot.resttestclient.autoconfigure; import org.assertj.core.extractor.Extractors; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; +import org.mockito.InOrder; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.http.converter.autoconfigure.ClientHttpMessageConvertersCustomizer; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.test.http.server.LocalTestWebServer; @@ -28,6 +30,8 @@ import org.springframework.boot.test.http.server.LocalTestWebServer.Scheme; import org.springframework.boot.testsupport.classpath.resources.WithResource; 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.test.web.servlet.client.RestTestClient; import org.springframework.web.client.RestClient; import org.springframework.web.util.UriBuilderFactory; @@ -35,6 +39,7 @@ import org.springframework.web.util.UriBuilderFactory; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; /** @@ -86,6 +91,23 @@ class RestTestClientAutoConfigurationTests { }); } + @Test + void clientHttpMessageConverterCustomizersAreAppliedInOrder() { + this.contextRunner.withUserConfiguration(ClientHttpMessageConverterCustomizersConfiguration.class) + .run((context) -> { + ClientHttpMessageConvertersCustomizer customizer1 = context.getBean("customizer1", + ClientHttpMessageConvertersCustomizer.class); + ClientHttpMessageConvertersCustomizer customizer2 = context.getBean("customizer2", + ClientHttpMessageConvertersCustomizer.class); + ClientHttpMessageConvertersCustomizer customizer3 = context.getBean("customizer3", + ClientHttpMessageConvertersCustomizer.class); + InOrder inOrder = inOrder(customizer1, customizer2, customizer3); + inOrder.verify(customizer3).customize(any(ClientBuilder.class)); + inOrder.verify(customizer1).customize(any(ClientBuilder.class)); + inOrder.verify(customizer2).customize(any(ClientBuilder.class)); + }); + } + @Configuration(proxyBeanMethods = false) static class RestTestClientCustomConfig { @@ -106,4 +128,27 @@ class RestTestClientAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class ClientHttpMessageConverterCustomizersConfiguration { + + @Bean + @Order(-5) + ClientHttpMessageConvertersCustomizer customizer1() { + return mock(ClientHttpMessageConvertersCustomizer.class); + } + + @Bean + @Order(5) + ClientHttpMessageConvertersCustomizer customizer2() { + return mock(ClientHttpMessageConvertersCustomizer.class); + } + + @Bean + @Order(-10) + ClientHttpMessageConvertersCustomizer customizer3() { + return mock(ClientHttpMessageConvertersCustomizer.class); + } + + } + } diff --git a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.java b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.java index 53ad6523acb..28e042fb836 100644 --- a/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.java +++ b/module/spring-boot-webmvc/src/main/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfiguration.java @@ -244,7 +244,8 @@ public final class WebMvcAutoConfiguration { @Override public void configureMessageConverters(ServerBuilder builder) { - this.httpMessageConvertersCustomizerProvider.forEach((customizer) -> customizer.customize(builder)); + this.httpMessageConvertersCustomizerProvider.orderedStream() + .forEach((customizer) -> customizer.customize(builder)); } @Override diff --git a/module/spring-boot-webmvc/src/test/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfigurationTests.java b/module/spring-boot-webmvc/src/test/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfigurationTests.java index 8f51b73bce7..c8384e7c9ac 100644 --- a/module/spring-boot-webmvc/src/test/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfigurationTests.java +++ b/module/spring-boot-webmvc/src/test/java/org/springframework/boot/webmvc/autoconfigure/WebMvcAutoConfigurationTests.java @@ -42,12 +42,14 @@ import org.aspectj.lang.annotation.Aspect; import org.assertj.core.api.InstanceOfAssertFactories; import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; +import org.mockito.InOrder; import org.springframework.aop.support.AopUtils; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration; import org.springframework.boot.http.converter.autoconfigure.HttpMessageConvertersAutoConfiguration; +import org.springframework.boot.http.converter.autoconfigure.ServerHttpMessageConvertersCustomizer; import org.springframework.boot.servlet.filter.OrderedFormContentFilter; import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; @@ -83,6 +85,7 @@ import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverters.ServerBuilder; import org.springframework.http.server.RequestPath; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.util.ReflectionTestUtils; @@ -156,6 +159,8 @@ import org.springframework.web.util.UrlPathHelper; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; /** @@ -1115,6 +1120,23 @@ class WebMvcAutoConfigurationTests { }); } + @Test + void serverHttpMessageConverterCustomizersAreAppliedInOrder() { + this.contextRunner.withUserConfiguration(ServerHttpMessageConverterCustomizersConfiguration.class) + .run((context) -> { + ServerHttpMessageConvertersCustomizer customizer1 = context.getBean("customizer1", + ServerHttpMessageConvertersCustomizer.class); + ServerHttpMessageConvertersCustomizer customizer2 = context.getBean("customizer2", + ServerHttpMessageConvertersCustomizer.class); + ServerHttpMessageConvertersCustomizer customizer3 = context.getBean("customizer3", + ServerHttpMessageConvertersCustomizer.class); + InOrder inOrder = inOrder(customizer1, customizer2, customizer3); + inOrder.verify(customizer3).customize(any(ServerBuilder.class)); + inOrder.verify(customizer1).customize(any(ServerBuilder.class)); + inOrder.verify(customizer2).customize(any(ServerBuilder.class)); + }); + } + private void assertResourceHttpRequestHandler(AssertableWebApplicationContext context, Consumer handlerConsumer) { Map handlerMap = getHandlerMap(context.getBean("resourceHandlerMapping", HandlerMapping.class)); @@ -1701,4 +1723,27 @@ class WebMvcAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class ServerHttpMessageConverterCustomizersConfiguration { + + @Bean + @Order(-5) + ServerHttpMessageConvertersCustomizer customizer1() { + return mock(ServerHttpMessageConvertersCustomizer.class); + } + + @Bean + @Order(5) + ServerHttpMessageConvertersCustomizer customizer2() { + return mock(ServerHttpMessageConvertersCustomizer.class); + } + + @Bean + @Order(-10) + ServerHttpMessageConvertersCustomizer customizer3() { + return mock(ServerHttpMessageConvertersCustomizer.class); + } + + } + }