mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-10-07 09:49:39 +00:00
Allow customizations of default customizers to be overridden
Previously, the default client and server HTTP message converter customizers were ordered with lowest precedence. This made it impossible to guarantee that another customizer would be able to go after the default customizers, preventing their customizations from being reliably overridden. This commit updates the definitions of the default customizers to order them at 0. This allows additional customizers to be ordered either before or after them. Usage of the customizers is now always ordered (previously Spring MVC's was not). See gh-47798
This commit is contained in:
+2
-3
@@ -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<HttpMessageConverters> legacyConverters,
|
||||
@@ -77,7 +76,7 @@ public final class HttpMessageConvertersAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
@Order(0)
|
||||
@SuppressWarnings("deprecation")
|
||||
ServerHttpMessageConvertersCustomizer serverConvertersCustomizer(
|
||||
ObjectProvider<HttpMessageConverters> legacyConverters,
|
||||
|
||||
+23
@@ -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 <T> void defaultConvertersCustomizerHasOrderZero(Class<T> customizerType) {
|
||||
new WebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
|
||||
.run((context) -> {
|
||||
Map<String, T> 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)
|
||||
|
||||
+46
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+46
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
+45
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
+45
@@ -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<ResourceHttpRequestHandler> handlerConsumer) {
|
||||
Map<String, Object> 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user