Browse Source

Align HttpMessageConverters builder with WebFlux codecs variants

As of #33894, we introduced a new `HttpMessageConverters` API.
While this achieved our goal of focusing converters classpath detection
in a single place and avoiding waste, a single `HttpMessageConverters`
instance for both client and server added more complexity for developers.

This commit aligns the API here with the WebFlux `CodecsConfigurer` to
opt for a client/server flavor as the first step in the builder.
While this make the sharing of converter instances between server and
client impossible, this allows for a simpler API and separates concerns.

Closes gh-35187
pull/35192/head
Brian Clozel 5 months ago
parent
commit
b59dca9c7f
  1. 2
      framework-docs/src/main/java/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigmessageconverters/WebConfiguration.java
  2. 2
      framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigmessageconverters/WebConfiguration.kt
  3. 461
      spring-web/src/main/java/org/springframework/http/converter/DefaultHttpMessageConverters.java
  4. 152
      spring-web/src/main/java/org/springframework/http/converter/HttpMessageConverters.java
  5. 2
      spring-web/src/main/java/org/springframework/http/converter/support/AllEncompassingFormHttpMessageConverter.java
  6. 10
      spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java
  7. 13
      spring-web/src/main/java/org/springframework/web/client/RestClient.java
  8. 4
      spring-web/src/main/java/org/springframework/web/client/RestTemplate.java
  9. 159
      spring-web/src/test/java/org/springframework/http/converter/DefaultHttpMessageConvertersTests.java
  10. 14
      spring-web/src/test/java/org/springframework/web/client/RestClientBuilderTests.java
  11. 8
      spring-web/src/test/java/org/springframework/web/client/RestClientObservationTests.java
  12. 2
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.java
  13. 14
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
  14. 6
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java
  15. 2
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java
  16. 4
      spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfigurationTests.java
  17. 4
      spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java

2
framework-docs/src/main/java/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigmessageconverters/WebConfiguration.java

@ -34,7 +34,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -34,7 +34,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configureMessageConverters(HttpMessageConverters.Builder builder) {
public void configureMessageConverters(HttpMessageConverters.ServerBuilder builder) {
JsonMapper jsonMapper = JsonMapper.builder()
.findAndAddModules()
.enable(SerializationFeature.INDENT_OUTPUT)

2
framework-docs/src/main/kotlin/org/springframework/docs/web/webmvc/mvcconfig/mvcconfigmessageconverters/WebConfiguration.kt

@ -16,7 +16,7 @@ import java.text.SimpleDateFormat @@ -16,7 +16,7 @@ import java.text.SimpleDateFormat
@Configuration
class WebConfiguration : WebMvcConfigurer {
override fun configureMessageConverters(builder: HttpMessageConverters.Builder) {
override fun configureMessageConverters(builder: HttpMessageConverters.ServerBuilder) {
val jsonMapper = JsonMapper.builder()
.findAndAddModules()
.enable(SerializationFeature.INDENT_OUTPUT)

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

@ -19,6 +19,7 @@ package org.springframework.http.converter; @@ -19,6 +19,7 @@ package org.springframework.http.converter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
@ -55,113 +56,19 @@ import org.springframework.util.ClassUtils; @@ -55,113 +56,19 @@ import org.springframework.util.ClassUtils;
@SuppressWarnings("removal")
class DefaultHttpMessageConverters implements HttpMessageConverters {
private final List<HttpMessageConverter<?>> clientMessageConverters;
private final List<HttpMessageConverter<?>> messageConverters;
private final List<HttpMessageConverter<?>> serverMessageConverters;
DefaultHttpMessageConverters(List<HttpMessageConverter<?>> clientMessageConverters, List<HttpMessageConverter<?>> serverMessageConverters) {
this.clientMessageConverters = clientMessageConverters;
this.serverMessageConverters = serverMessageConverters;
}
@Override
public Iterable<HttpMessageConverter<?>> forClient() {
return this.clientMessageConverters;
DefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
this.messageConverters = messageConverters;
}
@Override
public Iterable<HttpMessageConverter<?>> forServer() {
return this.serverMessageConverters;
public Iterator<HttpMessageConverter<?>> iterator() {
return this.messageConverters.iterator();
}
static class DefaultBuilder implements HttpMessageConverters.Builder {
private final DefaultMessageConverterConfigurer commonMessageConverters;
private final DefaultClientMessageConverterConfigurer clientMessageConverterConfigurer;
private final DefaultServerMessageConverterConfigurer serverMessageConverterConfigurer;
DefaultBuilder(boolean registerDefaults) {
this(registerDefaults, DefaultHttpMessageConverters.class.getClassLoader());
}
DefaultBuilder(boolean registerDefaults, ClassLoader classLoader) {
this.commonMessageConverters = new DefaultMessageConverterConfigurer();
this.clientMessageConverterConfigurer = new DefaultClientMessageConverterConfigurer(this.commonMessageConverters);
this.serverMessageConverterConfigurer = new DefaultServerMessageConverterConfigurer(this.commonMessageConverters);
if (registerDefaults) {
this.commonMessageConverters.registerDefaults();
this.clientMessageConverterConfigurer.registerDefaults();
this.serverMessageConverterConfigurer.registerDefaults();
}
}
@Override
public Builder configureClient(Consumer<ClientMessageConverterConfigurer> consumer) {
consumer.accept(this.clientMessageConverterConfigurer);
return this;
}
@Override
public Builder configureServer(Consumer<ServerMessageConverterConfigurer> consumer) {
consumer.accept(this.serverMessageConverterConfigurer);
return this;
}
@Override
public Builder stringMessageConverter(HttpMessageConverter<?> stringMessageConverter) {
this.commonMessageConverters.setStringMessageConverter(stringMessageConverter);
return this;
}
@Override
public DefaultBuilder jsonMessageConverter(HttpMessageConverter<?> jsonMessageConverter) {
this.commonMessageConverters.setJsonMessageConverter(jsonMessageConverter);
return this;
}
@Override
public DefaultBuilder xmlMessageConverter(HttpMessageConverter<?> xmlMessageConverter) {
this.commonMessageConverters.setXmlMessageConverter(xmlMessageConverter);
return this;
}
@Override
public DefaultBuilder smileMessageConverter(HttpMessageConverter<?> smileMessageConverter) {
this.commonMessageConverters.setSmileMessageConverter(smileMessageConverter);
return this;
}
@Override
public Builder cborMessageConverter(HttpMessageConverter<?> cborMessageConverter) {
this.commonMessageConverters.setCborMessageConverter(cborMessageConverter);
return this;
}
@Override
public Builder yamlMessageConverter(HttpMessageConverter<?> yamlMessageConverter) {
this.commonMessageConverters.setYamlMessageConverter(yamlMessageConverter);
return this;
}
@Override
public DefaultBuilder additionalMessageConverter(HttpMessageConverter<?> customConverter) {
Assert.notNull(customConverter, "'customConverter' must not be null");
this.commonMessageConverters.customMessageConverters.add(customConverter);
return this;
}
@Override
public DefaultHttpMessageConverters build() {
return new DefaultHttpMessageConverters(this.clientMessageConverterConfigurer.getMessageConverters(),
this.serverMessageConverterConfigurer.getMessageConverters());
}
}
static class DefaultMessageConverterConfigurer {
abstract static class DefaultBuilder {
private static final boolean isJacksonPresent;
@ -197,35 +104,37 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { @@ -197,35 +104,37 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
private static final boolean isRomePresent;
boolean registerDefaults;
private final @Nullable DefaultMessageConverterConfigurer inheritedMessageConverters;
private @Nullable ByteArrayHttpMessageConverter byteArrayMessageConverter;
@Nullable ByteArrayHttpMessageConverter byteArrayMessageConverter;
private @Nullable HttpMessageConverter<?> stringMessageConverter;
@Nullable HttpMessageConverter<?> stringMessageConverter;
List<HttpMessageConverter<?>> resourceMessageConverters = Collections.emptyList();
private @Nullable HttpMessageConverter<?> jsonMessageConverter;
@Nullable Consumer<HttpMessageConverter<?>> configurer;
private @Nullable HttpMessageConverter<?> xmlMessageConverter;
@Nullable HttpMessageConverter<?> jsonMessageConverter;
private @Nullable HttpMessageConverter<?> smileMessageConverter;
@Nullable HttpMessageConverter<?> xmlMessageConverter;
private @Nullable HttpMessageConverter<?> cborMessageConverter;
@Nullable HttpMessageConverter<?> smileMessageConverter;
private @Nullable HttpMessageConverter<?> yamlMessageConverter;
@Nullable HttpMessageConverter<?> cborMessageConverter;
private @Nullable HttpMessageConverter<?> protobufMessageConverter;
@Nullable HttpMessageConverter<?> yamlMessageConverter;
private @Nullable HttpMessageConverter<?> atomMessageConverter;
@Nullable HttpMessageConverter<?> protobufMessageConverter;
private @Nullable HttpMessageConverter<?> rssMessageConverter;
@Nullable HttpMessageConverter<?> atomMessageConverter;
@Nullable HttpMessageConverter<?> rssMessageConverter;
final List<HttpMessageConverter<?>> customMessageConverters = new ArrayList<>();
private final List<HttpMessageConverter<?>> customMessageConverters = new ArrayList<>();
static {
ClassLoader classLoader = DefaultClientMessageConverterConfigurer.class.getClassLoader();
ClassLoader classLoader = DefaultBuilder.class.getClassLoader();
isJacksonPresent = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader);
isJackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
@ -246,13 +155,6 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { @@ -246,13 +155,6 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
isRomePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
}
DefaultMessageConverterConfigurer() {
this(null);
}
DefaultMessageConverterConfigurer(@Nullable DefaultMessageConverterConfigurer inheritedMessageConverters) {
this.inheritedMessageConverters = inheritedMessageConverters;
}
void setStringMessageConverter(HttpMessageConverter<?> stringMessageConverter) {
Assert.isTrue(stringMessageConverter.getSupportedMediaTypes().contains(MediaType.TEXT_PLAIN),
@ -290,22 +192,23 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { @@ -290,22 +192,23 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
this.yamlMessageConverter = yamlMessageConverter;
}
void addCustomMessageConverter(HttpMessageConverter<?> customConverter) {
Assert.notNull(customConverter, "'customConverter' must not be null");
this.customMessageConverters.add(customConverter);
}
void addMessageConverterConfigurer(Consumer<HttpMessageConverter<?>> configurer) {
this.configurer = (this.configurer != null) ? configurer.andThen(this.configurer) : configurer;
}
List<HttpMessageConverter<?>> getBaseConverters() {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
if (this.byteArrayMessageConverter != null) {
converters.add(this.byteArrayMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.byteArrayMessageConverter != null) {
converters.add(this.inheritedMessageConverters.byteArrayMessageConverter);
}
if (this.stringMessageConverter != null) {
converters.add(this.stringMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.stringMessageConverter != null) {
converters.add(this.inheritedMessageConverters.stringMessageConverter);
}
return converters;
}
@ -314,305 +217,277 @@ class DefaultHttpMessageConverters implements HttpMessageConverters { @@ -314,305 +217,277 @@ class DefaultHttpMessageConverters implements HttpMessageConverters {
if (this.jsonMessageConverter != null) {
converters.add(this.jsonMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.jsonMessageConverter != null) {
converters.add(this.inheritedMessageConverters.jsonMessageConverter);
}
if (this.smileMessageConverter != null) {
converters.add(this.smileMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.smileMessageConverter != null) {
converters.add(this.inheritedMessageConverters.smileMessageConverter);
}
if (this.cborMessageConverter!= null) {
converters.add(this.cborMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.cborMessageConverter != null) {
converters.add(this.inheritedMessageConverters.cborMessageConverter);
}
if (this.yamlMessageConverter!= null) {
converters.add(this.yamlMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
this.inheritedMessageConverters.yamlMessageConverter != null) {
converters.add(this.inheritedMessageConverters.yamlMessageConverter);
}
if (this.xmlMessageConverter!= null) {
converters.add(this.xmlMessageConverter);
}
else if (this.inheritedMessageConverters != null &&
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.customMessageConverters);
if (this.inheritedMessageConverters != null) {
result.addAll(this.inheritedMessageConverters.customMessageConverters);
}
return result;
return this.customMessageConverters;
}
void registerDefaults() {
void detectMessageConverters() {
this.byteArrayMessageConverter = new ByteArrayHttpMessageConverter();
this.stringMessageConverter = new StringHttpMessageConverter();
if (isJacksonPresent) {
this.jsonMessageConverter = new JacksonJsonHttpMessageConverter();
}
else if (isJackson2Present) {
this.jsonMessageConverter = new MappingJackson2HttpMessageConverter();
}
else if (isGsonPresent) {
this.jsonMessageConverter = new GsonHttpMessageConverter();
}
else if (isJsonbPresent) {
this.jsonMessageConverter = new JsonbHttpMessageConverter();
}
else if (isKotlinSerializationJsonPresent) {
this.jsonMessageConverter = new KotlinSerializationJsonHttpMessageConverter();
}
if (isJacksonXmlPresent) {
this.xmlMessageConverter = new JacksonXmlHttpMessageConverter();
}
else if (isJackson2XmlPresent) {
this.xmlMessageConverter = new MappingJackson2XmlHttpMessageConverter();
}
else if (isJaxb2Present) {
this.xmlMessageConverter = new Jaxb2RootElementHttpMessageConverter();
}
if (isJacksonSmilePresent) {
this.smileMessageConverter = new JacksonSmileHttpMessageConverter();
}
else if (isJackson2SmilePresent) {
this.smileMessageConverter = new MappingJackson2SmileHttpMessageConverter();
}
if (isJacksonCborPresent) {
this.cborMessageConverter = new JacksonCborHttpMessageConverter();
}
else if (isJackson2CborPresent) {
this.cborMessageConverter = new MappingJackson2CborHttpMessageConverter();
}
else if (isKotlinSerializationCborPresent) {
this.cborMessageConverter = new KotlinSerializationCborHttpMessageConverter();
}
if (isJacksonYamlPresent) {
this.yamlMessageConverter = new JacksonYamlHttpMessageConverter();
}
else if (isJackson2YamlPresent) {
this.yamlMessageConverter = new MappingJackson2YamlHttpMessageConverter();
}
if (isKotlinSerializationProtobufPresent) {
this.protobufMessageConverter = new KotlinSerializationProtobufHttpMessageConverter();
if (this.jsonMessageConverter == null) {
if (isJacksonPresent) {
this.jsonMessageConverter = new JacksonJsonHttpMessageConverter();
}
else if (isJackson2Present) {
this.jsonMessageConverter = new MappingJackson2HttpMessageConverter();
}
else if (isGsonPresent) {
this.jsonMessageConverter = new GsonHttpMessageConverter();
}
else if (isJsonbPresent) {
this.jsonMessageConverter = new JsonbHttpMessageConverter();
}
else if (isKotlinSerializationJsonPresent) {
this.jsonMessageConverter = new KotlinSerializationJsonHttpMessageConverter();
}
}
if (this.xmlMessageConverter == null) {
if (isJacksonXmlPresent) {
this.xmlMessageConverter = new JacksonXmlHttpMessageConverter();
}
else if (isJackson2XmlPresent) {
this.xmlMessageConverter = new MappingJackson2XmlHttpMessageConverter();
}
else if (isJaxb2Present) {
this.xmlMessageConverter = new Jaxb2RootElementHttpMessageConverter();
}
}
if (this.smileMessageConverter == null) {
if (isJacksonSmilePresent) {
this.smileMessageConverter = new JacksonSmileHttpMessageConverter();
}
else if (isJackson2SmilePresent) {
this.smileMessageConverter = new MappingJackson2SmileHttpMessageConverter();
}
}
if (this.cborMessageConverter == null) {
if (isJacksonCborPresent) {
this.cborMessageConverter = new JacksonCborHttpMessageConverter();
}
else if (isJackson2CborPresent) {
this.cborMessageConverter = new MappingJackson2CborHttpMessageConverter();
}
else if (isKotlinSerializationCborPresent) {
this.cborMessageConverter = new KotlinSerializationCborHttpMessageConverter();
}
}
if (this.yamlMessageConverter == null) {
if (isJacksonYamlPresent) {
this.yamlMessageConverter = new JacksonYamlHttpMessageConverter();
}
else if (isJackson2YamlPresent) {
this.yamlMessageConverter = new MappingJackson2YamlHttpMessageConverter();
}
}
if (this.protobufMessageConverter == null) {
if (isKotlinSerializationProtobufPresent) {
this.protobufMessageConverter = new KotlinSerializationProtobufHttpMessageConverter();
}
}
if (isRomePresent) {
this.atomMessageConverter = new AtomFeedHttpMessageConverter();
this.rssMessageConverter = new RssChannelHttpMessageConverter();
if (this.atomMessageConverter == null) {
this.atomMessageConverter = new AtomFeedHttpMessageConverter();
}
if (this.rssMessageConverter == null) {
this.rssMessageConverter = new RssChannelHttpMessageConverter();
}
}
}
}
static class DefaultClientMessageConverterConfigurer extends DefaultMessageConverterConfigurer implements ClientMessageConverterConfigurer {
private @Nullable Consumer<HttpMessageConverter<?>> configurer;
private final DefaultMessageConverterConfigurer clientMessageConverters;
public DefaultClientMessageConverterConfigurer(DefaultMessageConverterConfigurer parentMessageConverters) {
this.clientMessageConverters = new DefaultMessageConverterConfigurer(parentMessageConverters);
}
static class DefaultClientBuilder extends DefaultBuilder implements ClientBuilder {
@Override
public ClientMessageConverterConfigurer stringMessageConverter(HttpMessageConverter<?> stringMessageConverter) {
this.clientMessageConverters.setStringMessageConverter(stringMessageConverter);
public DefaultClientBuilder registerDefaults() {
this.registerDefaults = true;
return this;
}
@Override
public ClientMessageConverterConfigurer jsonMessageConverter(HttpMessageConverter<?> jsonMessageConverter) {
this.clientMessageConverters.setJsonMessageConverter(jsonMessageConverter);
public ClientBuilder stringMessageConverter(HttpMessageConverter<?> stringMessageConverter) {
setStringMessageConverter(stringMessageConverter);
return this;
}
@Override
public ClientMessageConverterConfigurer xmlMessageConverter(HttpMessageConverter<?> xmlMessageConverter) {
this.clientMessageConverters.setXmlMessageConverter(xmlMessageConverter);
public ClientBuilder jsonMessageConverter(HttpMessageConverter<?> jsonMessageConverter) {
setJsonMessageConverter(jsonMessageConverter);
return this;
}
@Override
public ClientMessageConverterConfigurer smileMessageConverter(HttpMessageConverter<?> smileMessageConverter) {
this.clientMessageConverters.setSmileMessageConverter(smileMessageConverter);
public ClientBuilder xmlMessageConverter(HttpMessageConverter<?> xmlMessageConverter) {
setXmlMessageConverter(xmlMessageConverter);
return this;
}
@Override
public ClientMessageConverterConfigurer cborMessageConverter(HttpMessageConverter<?> cborMessageConverter) {
this.clientMessageConverters.setCborMessageConverter(cborMessageConverter);
public ClientBuilder smileMessageConverter(HttpMessageConverter<?> smileMessageConverter) {
setSmileMessageConverter(smileMessageConverter);
return this;
}
@Override
public ClientMessageConverterConfigurer yamlMessageConverter(HttpMessageConverter<?> yamlMessageConverter) {
this.clientMessageConverters.setYamlMessageConverter(yamlMessageConverter);
public ClientBuilder cborMessageConverter(HttpMessageConverter<?> cborMessageConverter) {
setCborMessageConverter(cborMessageConverter);
return this;
}
@Override
public ClientMessageConverterConfigurer additionalMessageConverter(HttpMessageConverter<?> customConverter) {
Assert.notNull(customConverter, "'customConverter' must not be null");
this.clientMessageConverters.customMessageConverters.add(customConverter);
public ClientBuilder yamlMessageConverter(HttpMessageConverter<?> yamlMessageConverter) {
setYamlMessageConverter(yamlMessageConverter);
return this;
}
@Override
public ClientMessageConverterConfigurer configureClientMessageConverters(Consumer<HttpMessageConverter<?>> configurer) {
this.configurer = (this.configurer != null) ? configurer.andThen(this.configurer) : configurer;
public ClientBuilder customMessageConverter(HttpMessageConverter<?> customConverter) {
addCustomMessageConverter(customConverter);
return this;
}
@Override
void registerDefaults() {
this.resourceMessageConverters = Collections.singletonList(new ResourceHttpMessageConverter(false));
public ClientBuilder configureMessageConverters(Consumer<HttpMessageConverter<?>> configurer) {
addMessageConverterConfigurer(configurer);
return this;
}
List<HttpMessageConverter<?>> getMessageConverters() {
@Override
public HttpMessageConverters build() {
if (this.registerDefaults) {
this.resourceMessageConverters = Collections.singletonList(new ResourceHttpMessageConverter(false));
detectMessageConverters();
}
List<HttpMessageConverter<?>> allConverters = new ArrayList<>();
List<HttpMessageConverter<?>> partConverters = new ArrayList<>();
partConverters.addAll(this.getCustomConverters());
partConverters.addAll(this.getCoreConverters());
partConverters.addAll(this.clientMessageConverters.getCustomConverters());
partConverters.addAll(this.clientMessageConverters.getCoreConverters());
allConverters.addAll(this.clientMessageConverters.getCustomConverters());
allConverters.addAll(this.clientMessageConverters.getBaseConverters());
allConverters.addAll(this.getCustomConverters());
allConverters.addAll(this.getBaseConverters());
allConverters.addAll(this.resourceMessageConverters);
if (!partConverters.isEmpty()) {
allConverters.add(new AllEncompassingFormHttpMessageConverter(partConverters));
}
allConverters.addAll(this.clientMessageConverters.getCoreConverters());
allConverters.addAll(this.getCoreConverters());
if (this.configurer != null) {
allConverters.forEach(this.configurer);
}
return allConverters;
return new DefaultHttpMessageConverters(allConverters);
}
}
static class DefaultServerMessageConverterConfigurer extends DefaultMessageConverterConfigurer implements ServerMessageConverterConfigurer {
private @Nullable Consumer<HttpMessageConverter<?>> configurer;
private final DefaultMessageConverterConfigurer serverMessageConverters;
static class DefaultServerBuilder extends DefaultBuilder implements ServerBuilder {
DefaultServerMessageConverterConfigurer(DefaultMessageConverterConfigurer commonMessageConverters) {
this.serverMessageConverters = new DefaultMessageConverterConfigurer(commonMessageConverters);
}
@Override
public ServerMessageConverterConfigurer stringMessageConverter(HttpMessageConverter<?> stringMessageConverter) {
this.serverMessageConverters.setStringMessageConverter(stringMessageConverter);
public ServerBuilder registerDefaults() {
this.registerDefaults = true;
return this;
}
@Override
public ServerMessageConverterConfigurer jsonMessageConverter(HttpMessageConverter<?> jsonMessageConverter) {
this.serverMessageConverters.setJsonMessageConverter(jsonMessageConverter);
public ServerBuilder stringMessageConverter(HttpMessageConverter<?> stringMessageConverter) {
setStringMessageConverter(stringMessageConverter);
return this;
}
@Override
public ServerMessageConverterConfigurer xmlMessageConverter(HttpMessageConverter<?> xmlMessageConverter) {
this.serverMessageConverters.setXmlMessageConverter(xmlMessageConverter);
public ServerBuilder jsonMessageConverter(HttpMessageConverter<?> jsonMessageConverter) {
setJsonMessageConverter(jsonMessageConverter);
return this;
}
@Override
public ServerMessageConverterConfigurer smileMessageConverter(HttpMessageConverter<?> smileMessageConverter) {
this.serverMessageConverters.setSmileMessageConverter(smileMessageConverter);
public ServerBuilder xmlMessageConverter(HttpMessageConverter<?> xmlMessageConverter) {
setXmlMessageConverter(xmlMessageConverter);
return this;
}
@Override
public ServerMessageConverterConfigurer cborMessageConverter(HttpMessageConverter<?> cborMessageConverter) {
this.serverMessageConverters.setCborMessageConverter(cborMessageConverter);
public ServerBuilder smileMessageConverter(HttpMessageConverter<?> smileMessageConverter) {
setSmileMessageConverter(smileMessageConverter);
return this;
}
@Override
public ServerMessageConverterConfigurer yamlMessageConverter(HttpMessageConverter<?> yamlMessageConverter) {
this.serverMessageConverters.setYamlMessageConverter(yamlMessageConverter);
public ServerBuilder cborMessageConverter(HttpMessageConverter<?> cborMessageConverter) {
setCborMessageConverter(cborMessageConverter);
return this;
}
@Override
public ServerMessageConverterConfigurer additionalMessageConverter(HttpMessageConverter<?> customConverter) {
Assert.notNull(customConverter, "'customConverter' must not be null");
this.serverMessageConverters.customMessageConverters.add(customConverter);
public ServerBuilder yamlMessageConverter(HttpMessageConverter<?> yamlMessageConverter) {
setYamlMessageConverter(yamlMessageConverter);
return this;
}
@Override
public ServerMessageConverterConfigurer configureServerMessageConverters(Consumer<HttpMessageConverter<?>> configurer) {
this.configurer = (this.configurer != null) ? configurer.andThen(this.configurer) : configurer;
public ServerBuilder customMessageConverter(HttpMessageConverter<?> customConverter) {
addCustomMessageConverter(customConverter);
return this;
}
@Override
void registerDefaults() {
this.resourceMessageConverters = Arrays.asList(new ResourceHttpMessageConverter(), new ResourceRegionHttpMessageConverter());
public ServerBuilder configureMessageConverters(Consumer<HttpMessageConverter<?>> configurer) {
addMessageConverterConfigurer(configurer);
return this;
}
List<HttpMessageConverter<?>> getMessageConverters() {
@Override
public HttpMessageConverters build() {
if (this.registerDefaults) {
this.resourceMessageConverters = Arrays.asList(new ResourceHttpMessageConverter(), new ResourceRegionHttpMessageConverter());
detectMessageConverters();
}
List<HttpMessageConverter<?>> allConverters = new ArrayList<>();
List<HttpMessageConverter<?>> partConverters = new ArrayList<>();
partConverters.addAll(this.serverMessageConverters.getCustomConverters());
partConverters.addAll(this.serverMessageConverters.getCoreConverters());
partConverters.addAll(this.getCustomConverters());
partConverters.addAll(this.getCoreConverters());
allConverters.addAll(this.serverMessageConverters.getCustomConverters());
allConverters.addAll(this.serverMessageConverters.getBaseConverters());
allConverters.addAll(this.getCustomConverters());
allConverters.addAll(this.getBaseConverters());
allConverters.addAll(this.resourceMessageConverters);
if (!partConverters.isEmpty()) {
allConverters.add(new AllEncompassingFormHttpMessageConverter(partConverters));
}
allConverters.addAll(this.serverMessageConverters.getCoreConverters());
allConverters.addAll(this.getCoreConverters());
if (this.configurer != null) {
allConverters.forEach(this.configurer);
}
return allConverters;
return new DefaultHttpMessageConverters(allConverters);
}
}

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

@ -20,79 +20,69 @@ import java.util.function.Consumer; @@ -20,79 +20,69 @@ import java.util.function.Consumer;
/**
* Utility for building and configuring an immutable collection of {@link HttpMessageConverter}
* instances for client and server usage. You can {@link #create() create}
* a new empty instance or ask to {@link #withDefaults() register default converters},
* if available in your classpath.
*
* <p>This class offers a flexible arrangement for {@link HttpMessageConverters.Builder configuring message converters shared between}
* client and server, or {@link HttpMessageConverters.Builder#configureClient(Consumer) configuring client-specific}
* and {@link HttpMessageConverters.Builder#configureServer(Consumer) server-specific} converters.
*
* <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
* <li>a Multipart converter, using all detected and custom converters for part conversion
* <li>A JSON converter
* <li>A Smile converter
* <li>A CBOR converter
* <li>A YAML converter
* <li>An XML converter
* <li>An ProtoBuf converter
* <li>ATOM and RSS converters
* </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}
* <li>{@link ResourceRegionHttpMessageConverter}
* <li>A JSON converter
* <li>A Smile converter
* <li>A CBOR converter
* <li>A YAML converter
* <li>An XML converter
* <li>An ProtoBuf converter
* <li>ATOM and RSS converters
* <li>a Multipart converter, using all detected and custom converters for part conversion
* </ol>
* instances for {@link #forClient() client} or {@link #forServer() server} usage. You can
* ask to {@link Builder#registerDefaults() register default converters with classpath detection},
* add custom converters and post-process configured converters.
*
* @author Brian Clozel
* @since 7.0
*/
public interface HttpMessageConverters {
public interface HttpMessageConverters extends Iterable<HttpMessageConverter<?>> {
/**
* Return the list of configured message converters, tailored for HTTP client usage.
*/
Iterable<HttpMessageConverter<?>> forClient();
/**
* Return the list of configured message converters, tailored for HTTP server usage.
*/
Iterable<HttpMessageConverter<?>> forServer();
/**
* Create a builder instance, without any message converter pre-configured.
* Create a builder instance, tailored for HTTP client usage.
* <p>The following HTTP message converters can be detected and registered if available, in order:
* <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
* <li>a Multipart converter, using all detected and custom converters for part conversion
* <li>A JSON converter
* <li>A Smile converter
* <li>A CBOR converter
* <li>A YAML converter
* <li>An XML converter
* <li>A ProtoBuf converter
* <li>ATOM and RSS converters
* </ol>
*/
static Builder create() {
return new DefaultHttpMessageConverters.DefaultBuilder(false);
static ClientBuilder forClient() {
return new DefaultHttpMessageConverters.DefaultClientBuilder();
}
/**
* Create a builder instance with default message converters pre-configured.
* Create a builder instance, tailored for HTTP server usage.
* <p>The following HTTP message converters can be detected and registered if available, in order:
* <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}
* <li>{@link ResourceRegionHttpMessageConverter}
* <li>A JSON converter
* <li>A Smile converter
* <li>A CBOR converter
* <li>A YAML converter
* <li>An XML converter
* <li>A ProtoBuf converter
* <li>ATOM and RSS converters
* <li>a Multipart converter, using all detected and custom converters for part conversion
* </ol>
*/
static Builder withDefaults() {
return new DefaultHttpMessageConverters.DefaultBuilder(true);
static ServerBuilder forServer() {
return new DefaultHttpMessageConverters.DefaultServerBuilder();
}
interface Builder<T extends Builder<T>> {
interface MessageConverterConfigurer<T extends MessageConverterConfigurer<T>> {
/**
* Register default converters using classpath detection.
* Manual registrations like {@link #jsonMessageConverter(HttpMessageConverter)} will
* override auto-detected ones.
*/
T registerDefaults();
/**
* Override the default String {@code HttpMessageConverter}
@ -146,27 +136,13 @@ public interface HttpMessageConverters { @@ -146,27 +136,13 @@ public interface HttpMessageConverters {
* Add a custom {@code HttpMessageConverter} to the list of converters.
* @param customConverter the converter instance to add
*/
T additionalMessageConverter(HttpMessageConverter<?> customConverter);
}
/**
* Builder for an {@link HttpMessageConverters}.
* This builder manages the configuration of common and client/server-specific message converters.
*/
interface Builder extends MessageConverterConfigurer<Builder> {
/**
* Configure client-specific message converters.
* If no opinion is provided here, message converters defined in this builder will be used.
*/
Builder configureClient(Consumer<ClientMessageConverterConfigurer> consumer);
T customMessageConverter(HttpMessageConverter<?> customConverter);
/**
* Configure server-specific message converters.
* If no opinion is provided here, message converters defined in this builder will be used.
* Add a consumer for configuring the selected message converters.
* @param configurer the configurer to use
*/
Builder configureServer(Consumer<ServerMessageConverterConfigurer> consumer);
T configureMessageConverters(Consumer<HttpMessageConverter<?>> configurer);
/**
* Build and return the {@link HttpMessageConverters} instance configured by this builder.
@ -174,25 +150,17 @@ public interface HttpMessageConverters { @@ -174,25 +150,17 @@ public interface HttpMessageConverters {
HttpMessageConverters build();
}
interface ClientMessageConverterConfigurer extends MessageConverterConfigurer<ClientMessageConverterConfigurer> {
/**
* Register a consumer to apply to configured converter instances.
* This can be used to configure rather than replace one or more specific converters.
* @param configurer the consumer to apply
*/
ClientMessageConverterConfigurer configureClientMessageConverters(Consumer<HttpMessageConverter<?>> configurer);
/**
* Client builder for an {@link HttpMessageConverters} instance.
*/
interface ClientBuilder extends Builder<ClientBuilder> {
}
interface ServerMessageConverterConfigurer extends MessageConverterConfigurer<ServerMessageConverterConfigurer> {
/**
* Register a consumer to apply to configured converter instances.
* This can be used to configure rather than replace one or more specific converters.
* @param configurer the consumer to apply
*/
ServerMessageConverterConfigurer configureServerMessageConverters(Consumer<HttpMessageConverter<?>> configurer);
/**
* Server builder for an {@link HttpMessageConverters} instance.
*/
interface ServerBuilder extends Builder<ServerBuilder> {
}

2
spring-web/src/main/java/org/springframework/http/converter/support/AllEncompassingFormHttpMessageConverter.java

@ -39,7 +39,7 @@ public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConv @@ -39,7 +39,7 @@ public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConv
*/
@SuppressWarnings("removal")
public AllEncompassingFormHttpMessageConverter() {
HttpMessageConverters.withDefaults().build().forClient().forEach(this::addPartConverter);
HttpMessageConverters.forClient().registerDefaults().build().forEach(this::addPartConverter);
}
/**

10
spring-web/src/main/java/org/springframework/web/client/DefaultRestClientBuilder.java

@ -361,6 +361,7 @@ final class DefaultRestClientBuilder implements RestClient.Builder { @@ -361,6 +361,7 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
}
@Override
@SuppressWarnings("removal")
public RestClient.Builder messageConverters(Consumer<List<HttpMessageConverter<?>>> configurer) {
configurer.accept(initMessageConverters());
validateConverters(this.messageConverters);
@ -376,6 +377,13 @@ final class DefaultRestClientBuilder implements RestClient.Builder { @@ -376,6 +377,13 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
return this;
}
@Override
public RestClient.Builder configureMessageConverters(Consumer<HttpMessageConverters.ClientBuilder> configurer) {
HttpMessageConverters.ClientBuilder clientBuilder = HttpMessageConverters.forClient();
configurer.accept(clientBuilder);
return messageConverters(clientBuilder.build());
}
@Override
public RestClient.Builder observationRegistry(ObservationRegistry observationRegistry) {
Assert.notNull(observationRegistry, "observationRegistry must not be null");
@ -399,7 +407,7 @@ final class DefaultRestClientBuilder implements RestClient.Builder { @@ -399,7 +407,7 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
private List<HttpMessageConverter<?>> initMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<>();
HttpMessageConverters.withDefaults().build().forClient().forEach(this.messageConverters::add);
HttpMessageConverters.forClient().registerDefaults().build().forEach(this.messageConverters::add);
}
return this.messageConverters;
}

13
spring-web/src/main/java/org/springframework/web/client/RestClient.java

@ -47,6 +47,7 @@ import org.springframework.http.client.ClientHttpRequestInterceptor; @@ -47,6 +47,7 @@ import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.observation.ClientRequestObservationConvention;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverters;
import org.springframework.lang.CheckReturnValue;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.DefaultUriBuilderFactory;
@ -452,7 +453,9 @@ public interface RestClient { @@ -452,7 +453,9 @@ public interface RestClient {
* {@link HttpMessageConverter} pre-initialized
* @return this builder
* @see #messageConverters(Iterable)
* @deprecated since 7.0 in favor of {@link #configureMessageConverters(Consumer)}
*/
@Deprecated(since = "7.0", forRemoval = true)
Builder messageConverters(Consumer<List<HttpMessageConverter<?>>> configurer);
/**
@ -460,10 +463,18 @@ public interface RestClient { @@ -460,10 +463,18 @@ public interface RestClient {
* @param messageConverters the list of {@link HttpMessageConverter} to use
* @return this builder
* @since 6.2
* @see #messageConverters(Consumer)
* @see #configureMessageConverters(Consumer)
*/
Builder messageConverters(Iterable<HttpMessageConverter<?>> messageConverters);
/**
* Configure the message converters for the {@code RestClient} to use.
* @param configurer the configurer to apply on an empty {@link HttpMessageConverters.ClientBuilder}.
* @return this builder
* @since 7.0
*/
Builder configureMessageConverters(Consumer<HttpMessageConverters.ClientBuilder> configurer);
/**
* Configure the {@link io.micrometer.observation.ObservationRegistry} to use
* for recording HTTP client observations.

4
spring-web/src/main/java/org/springframework/web/client/RestTemplate.java

@ -124,9 +124,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat @@ -124,9 +124,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
* Default {@link HttpMessageConverter HttpMessageConverters} are initialized.
*/
public RestTemplate() {
HttpMessageConverters.withDefaults().build().forClient().forEach(this.messageConverters::add);
updateErrorHandlerConverters();
this.uriTemplateHandler = initUriTemplateHandler();
this(HttpMessageConverters.forClient().registerDefaults().build());
}
/**

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

@ -17,11 +17,6 @@ @@ -17,11 +17,6 @@
package org.springframework.http.converter;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
@ -30,7 +25,6 @@ import org.junit.jupiter.api.Test; @@ -30,7 +25,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.core.SmartClassLoader;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter;
@ -60,59 +54,50 @@ class DefaultHttpMessageConvertersTests { @@ -60,59 +54,50 @@ class DefaultHttpMessageConvertersTests {
static Stream<Iterable<HttpMessageConverter<?>>> emptyMessageConverters() {
return Stream.of(
HttpMessageConverters.create().build().forClient(),
HttpMessageConverters.create().build().forServer()
HttpMessageConverters.forClient().build(),
HttpMessageConverters.forServer().build()
);
}
@Test
void clientAndServerConvertersAreShared() {
var converters = HttpMessageConverters.withDefaults().build();
Set<HttpMessageConverter<?>> allConverters = new HashSet<>();
converters.forClient().forEach(allConverters::add);
converters.forServer().forEach(allConverters::add);
assertThat(allConverters).hasSize(15);
}
@Test
void failsWhenStringConverterDoesNotSupportMediaType() {
assertThatIllegalArgumentException()
.isThrownBy(() -> HttpMessageConverters.create().stringMessageConverter(new CustomHttpMessageConverter()).build())
.isThrownBy(() -> HttpMessageConverters.forClient().stringMessageConverter(new CustomHttpMessageConverter()).build())
.withMessage("stringMessageConverter should support 'text/plain'");
}
@Test
void failsWhenJsonConverterDoesNotSupportMediaType() {
assertThatIllegalArgumentException()
.isThrownBy(() -> HttpMessageConverters.create().jsonMessageConverter(new CustomHttpMessageConverter()).build())
.isThrownBy(() -> HttpMessageConverters.forClient().jsonMessageConverter(new CustomHttpMessageConverter()).build())
.withMessage("jsonMessageConverter should support 'application/json'");
}
@Test
void failsWhenXmlConverterDoesNotSupportMediaType() {
assertThatIllegalArgumentException()
.isThrownBy(() -> HttpMessageConverters.create().xmlMessageConverter(new CustomHttpMessageConverter()).build())
.isThrownBy(() -> HttpMessageConverters.forClient().xmlMessageConverter(new CustomHttpMessageConverter()).build())
.withMessage("xmlMessageConverter should support 'text/xml'");
}
@Test
void failsWhenSmileConverterDoesNotSupportMediaType() {
assertThatIllegalArgumentException()
.isThrownBy(() -> HttpMessageConverters.create().smileMessageConverter(new CustomHttpMessageConverter()).build())
.isThrownBy(() -> HttpMessageConverters.forClient().smileMessageConverter(new CustomHttpMessageConverter()).build())
.withMessage("smileMessageConverter should support 'application/x-jackson-smile'");
}
@Test
void failsWhenCborConverterDoesNotSupportMediaType() {
assertThatIllegalArgumentException()
.isThrownBy(() -> HttpMessageConverters.create().cborMessageConverter(new CustomHttpMessageConverter()).build())
.isThrownBy(() -> HttpMessageConverters.forClient().cborMessageConverter(new CustomHttpMessageConverter()).build())
.withMessage("cborMessageConverter should support 'application/cbor'");
}
@Test
void failsWhenYamlConverterDoesNotSupportMediaType() {
assertThatIllegalArgumentException()
.isThrownBy(() -> HttpMessageConverters.create().yamlMessageConverter(new CustomHttpMessageConverter()).build())
.isThrownBy(() -> HttpMessageConverters.forClient().yamlMessageConverter(new CustomHttpMessageConverter()).build())
.withMessage("yamlMessageConverter should support 'application/yaml'");
}
@ -122,8 +107,8 @@ class DefaultHttpMessageConvertersTests { @@ -122,8 +107,8 @@ class DefaultHttpMessageConvertersTests {
@Test
void defaultConverters() {
var converters = HttpMessageConverters.withDefaults().build();
assertThat(converters.forClient()).hasExactlyElementsOfTypes(ByteArrayHttpMessageConverter.class,
var converters = HttpMessageConverters.forClient().registerDefaults().build();
assertThat(converters).hasExactlyElementsOfTypes(ByteArrayHttpMessageConverter.class,
StringHttpMessageConverter.class, ResourceHttpMessageConverter.class,
AllEncompassingFormHttpMessageConverter.class,
JacksonJsonHttpMessageConverter.class, JacksonSmileHttpMessageConverter.class,
@ -134,8 +119,8 @@ class DefaultHttpMessageConvertersTests { @@ -134,8 +119,8 @@ class DefaultHttpMessageConvertersTests {
@Test
void multipartConverterContainsOtherConverters() {
var converters = HttpMessageConverters.withDefaults().build();
AllEncompassingFormHttpMessageConverter multipartConverter = findMessageConverter(AllEncompassingFormHttpMessageConverter.class, converters.forClient());
var converters = HttpMessageConverters.forClient().registerDefaults().build();
var multipartConverter = findMessageConverter(AllEncompassingFormHttpMessageConverter.class, converters);
assertThat(multipartConverter.getPartConverters()).hasExactlyElementsOfTypes(
ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class,
@ -148,16 +133,16 @@ class DefaultHttpMessageConvertersTests { @@ -148,16 +133,16 @@ class DefaultHttpMessageConvertersTests {
@Test
void registerCustomMessageConverter() {
var converters = HttpMessageConverters.create()
.additionalMessageConverter(new CustomHttpMessageConverter()).build();
assertThat(converters.forClient()).hasExactlyElementsOfTypes(CustomHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class);
var converters = HttpMessageConverters.forClient()
.customMessageConverter(new CustomHttpMessageConverter()).build();
assertThat(converters).hasExactlyElementsOfTypes(CustomHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class);
}
@Test
void registerCustomMessageConverterAheadOfDefaults() {
var converters = HttpMessageConverters.withDefaults()
.additionalMessageConverter(new CustomHttpMessageConverter()).build();
assertThat(converters.forClient()).hasExactlyElementsOfTypes(
var converters = HttpMessageConverters.forClient().registerDefaults()
.customMessageConverter(new CustomHttpMessageConverter()).build();
assertThat(converters).hasExactlyElementsOfTypes(
CustomHttpMessageConverter.class, ByteArrayHttpMessageConverter.class,
StringHttpMessageConverter.class, ResourceHttpMessageConverter.class,
AllEncompassingFormHttpMessageConverter.class,
@ -169,33 +154,31 @@ class DefaultHttpMessageConvertersTests { @@ -169,33 +154,31 @@ class DefaultHttpMessageConvertersTests {
@Test
void registerCustomConverterInMultipartConverter() {
var converters = HttpMessageConverters.withDefaults()
.additionalMessageConverter(new CustomHttpMessageConverter()).build();
AllEncompassingFormHttpMessageConverter multipartConverter = findMessageConverter(AllEncompassingFormHttpMessageConverter.class, converters.forClient());
var converters = HttpMessageConverters.forClient().registerDefaults()
.customMessageConverter(new CustomHttpMessageConverter()).build();
var multipartConverter = findMessageConverter(AllEncompassingFormHttpMessageConverter.class, converters);
assertThat(multipartConverter.getPartConverters()).hasAtLeastOneElementOfType(CustomHttpMessageConverter.class);
}
@Test
void shouldUseServerSpecificConverter() {
JacksonJsonHttpMessageConverter jacksonConverter = new JacksonJsonHttpMessageConverter();
var converters = HttpMessageConverters.withDefaults()
.configureClient(configurer -> configurer.jsonMessageConverter(jacksonConverter)).build();
void shouldUseSpecificConverter() {
var jacksonConverter = new JacksonJsonHttpMessageConverter();
var converters = HttpMessageConverters.forClient().registerDefaults()
.jsonMessageConverter(jacksonConverter).build();
JacksonJsonHttpMessageConverter customConverter = findMessageConverter(JacksonJsonHttpMessageConverter.class, converters.forClient());
var customConverter = findMessageConverter(JacksonJsonHttpMessageConverter.class, converters);
assertThat(customConverter).isEqualTo(jacksonConverter);
}
@Test
void shouldConfigureConverter() {
CustomHttpMessageConverter customConverter = new CustomHttpMessageConverter();
HttpMessageConverters.withDefaults()
.additionalMessageConverter(customConverter)
.configureClient(configurer -> {
configurer.configureClientMessageConverters(converter -> {
if (converter instanceof CustomHttpMessageConverter custom) {
custom.processed = true;
}
});
var customConverter = new CustomHttpMessageConverter();
HttpMessageConverters.forClient()
.customMessageConverter(customConverter)
.configureMessageConverters(converter -> {
if (converter instanceof CustomHttpMessageConverter custom) {
custom.processed = true;
}
}).build();
assertThat(customConverter.processed).isTrue();
@ -209,8 +192,8 @@ class DefaultHttpMessageConvertersTests { @@ -209,8 +192,8 @@ class DefaultHttpMessageConvertersTests {
@Test
void defaultConverters() {
var converters = HttpMessageConverters.withDefaults().build();
assertThat(converters.forServer()).hasExactlyElementsOfTypes(
var converters = HttpMessageConverters.forServer().registerDefaults().build();
assertThat(converters).hasExactlyElementsOfTypes(
ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class,
ResourceHttpMessageConverter.class, ResourceRegionHttpMessageConverter.class,
AllEncompassingFormHttpMessageConverter.class,
@ -222,8 +205,8 @@ class DefaultHttpMessageConvertersTests { @@ -222,8 +205,8 @@ class DefaultHttpMessageConvertersTests {
@Test
void multipartConverterContainsOtherConverters() {
var converters = HttpMessageConverters.withDefaults().build();
AllEncompassingFormHttpMessageConverter multipartConverter = findMessageConverter(AllEncompassingFormHttpMessageConverter.class, converters.forServer());
var converters = HttpMessageConverters.forServer().registerDefaults().build();
var multipartConverter = findMessageConverter(AllEncompassingFormHttpMessageConverter.class, converters);
assertThat(multipartConverter.getPartConverters()).hasExactlyElementsOfTypes(
ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class,
@ -236,16 +219,16 @@ class DefaultHttpMessageConvertersTests { @@ -236,16 +219,16 @@ class DefaultHttpMessageConvertersTests {
@Test
void registerCustomMessageConverter() {
var converters = HttpMessageConverters.create()
.additionalMessageConverter(new CustomHttpMessageConverter()).build();
assertThat(converters.forServer()).hasExactlyElementsOfTypes(CustomHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class);
var converters = HttpMessageConverters.forServer()
.customMessageConverter(new CustomHttpMessageConverter()).build();
assertThat(converters).hasExactlyElementsOfTypes(CustomHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class);
}
@Test
void registerCustomMessageConverterAheadOfDefaults() {
var converters = HttpMessageConverters.withDefaults()
.additionalMessageConverter(new CustomHttpMessageConverter()).build();
assertThat(converters.forServer()).hasExactlyElementsOfTypes(
var converters = HttpMessageConverters.forServer().registerDefaults()
.customMessageConverter(new CustomHttpMessageConverter()).build();
assertThat(converters).hasExactlyElementsOfTypes(
CustomHttpMessageConverter.class,
ByteArrayHttpMessageConverter.class, StringHttpMessageConverter.class,
ResourceHttpMessageConverter.class, ResourceRegionHttpMessageConverter.class,
@ -258,33 +241,31 @@ class DefaultHttpMessageConvertersTests { @@ -258,33 +241,31 @@ class DefaultHttpMessageConvertersTests {
@Test
void registerCustomConverterInMultipartConverter() {
var converters = HttpMessageConverters.withDefaults()
.additionalMessageConverter(new CustomHttpMessageConverter()).build();
AllEncompassingFormHttpMessageConverter multipartConverter = findMessageConverter(AllEncompassingFormHttpMessageConverter.class, converters.forServer());
var converters = HttpMessageConverters.forServer().registerDefaults()
.customMessageConverter(new CustomHttpMessageConverter()).build();
var multipartConverter = findMessageConverter(AllEncompassingFormHttpMessageConverter.class, converters);
assertThat(multipartConverter.getPartConverters()).hasAtLeastOneElementOfType(CustomHttpMessageConverter.class);
}
@Test
void shouldUseServerSpecificConverter() {
JacksonJsonHttpMessageConverter jacksonConverter = new JacksonJsonHttpMessageConverter();
var converters = HttpMessageConverters.withDefaults()
.configureServer(configurer -> configurer.jsonMessageConverter(jacksonConverter)).build();
var jacksonConverter = new JacksonJsonHttpMessageConverter();
var converters = HttpMessageConverters.forServer().registerDefaults()
.jsonMessageConverter(jacksonConverter).build();
JacksonJsonHttpMessageConverter customConverter = findMessageConverter(JacksonJsonHttpMessageConverter.class, converters.forServer());
var customConverter = findMessageConverter(JacksonJsonHttpMessageConverter.class, converters);
assertThat(customConverter).isEqualTo(jacksonConverter);
}
@Test
void shouldConfigureConverter() {
CustomHttpMessageConverter customConverter = new CustomHttpMessageConverter();
HttpMessageConverters.withDefaults()
.additionalMessageConverter(customConverter)
.configureServer(configurer -> {
configurer.configureServerMessageConverters(converter -> {
if (converter instanceof CustomHttpMessageConverter custom) {
custom.processed = true;
}
});
var customConverter = new CustomHttpMessageConverter();
HttpMessageConverters.forServer().registerDefaults()
.customMessageConverter(customConverter)
.configureMessageConverters(converter -> {
if (converter instanceof CustomHttpMessageConverter custom) {
custom.processed = true;
}
}).build();
assertThat(customConverter.processed).isTrue();
@ -299,32 +280,6 @@ class DefaultHttpMessageConvertersTests { @@ -299,32 +280,6 @@ class DefaultHttpMessageConvertersTests {
.findFirst().orElseThrow();
}
static class FilteredClassLoader extends URLClassLoader implements SmartClassLoader {
private final Collection<Class<?>> hiddenClasses;
public FilteredClassLoader(Class<?>... hiddenClasses) {
this(java.util.Arrays.asList(hiddenClasses));
}
FilteredClassLoader(Collection<Class<?>> hiddenClasses) {
super(new URL[0], FilteredClassLoader.class.getClassLoader());
this.hiddenClasses = hiddenClasses;
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
for (Class<?> hiddenClass : this.hiddenClasses) {
if (hiddenClass.getName().equals(name)) {
throw new ClassNotFoundException();
}
}
return super.loadClass(name, resolve);
}
}
static class CustomHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
boolean processed = false;

14
spring-web/src/test/java/org/springframework/web/client/RestClientBuilderTests.java

@ -41,6 +41,7 @@ import static org.assertj.core.api.Assertions.fail; @@ -41,6 +41,7 @@ import static org.assertj.core.api.Assertions.fail;
/**
* Tests for {@link DefaultRestClientBuilder}.
* @author Arjen Poutsma
* @author Sebastien Deleuze
* @author Nicklas Wiegandt
@ -139,6 +140,19 @@ public class RestClientBuilderTests { @@ -139,6 +140,19 @@ public class RestClientBuilderTests {
assertThatIllegalArgumentException().isThrownBy(() -> builder.messageConverters(converters));
}
@Test
void configureMessageConverters() {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
RestClient.Builder builder = RestClient.builder();
builder.configureMessageConverters(clientBuilder -> clientBuilder.stringMessageConverter(stringConverter));
assertThat(builder).isInstanceOf(DefaultRestClientBuilder.class);
DefaultRestClientBuilder defaultBuilder = (DefaultRestClientBuilder) builder;
assertThat(fieldValue("messageConverters", defaultBuilder))
.asInstanceOf(InstanceOfAssertFactories.LIST)
.containsExactly(stringConverter);
}
@Test
void defaultCookieAddsCookieToDefaultCookiesMap() {
RestClient.Builder builder = RestClient.builder();

8
spring-web/src/test/java/org/springframework/web/client/RestClientObservationTests.java

@ -44,7 +44,7 @@ import org.springframework.http.client.ClientHttpResponse; @@ -44,7 +44,7 @@ import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.observation.ClientRequestObservationContext;
import org.springframework.http.client.observation.ClientRequestObservationConvention;
import org.springframework.http.client.observation.DefaultClientRequestObservationConvention;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat;
@ -70,8 +70,6 @@ class RestClientObservationTests { @@ -70,8 +70,6 @@ class RestClientObservationTests {
private final ClientHttpResponse response = mock();
private final HttpMessageConverter<String> converter = mock();
private RestClient client;
@ -84,7 +82,7 @@ class RestClientObservationTests { @@ -84,7 +82,7 @@ class RestClientObservationTests {
RestClient.Builder createBuilder() {
return RestClient.builder()
.baseUrl("https://example.com/base")
.messageConverters(converters -> converters.add(0, this.converter))
.configureMessageConverters(converters -> converters.customMessageConverter(new StringHttpMessageConverter()))
.requestFactory(this.requestFactory)
.observationRegistry(this.observationRegistry);
}
@ -203,7 +201,7 @@ class RestClientObservationTests { @@ -203,7 +201,7 @@ class RestClientObservationTests {
assertThatExceptionOfType(RestClientException.class).isThrownBy(() ->
client.get().uri(url).retrieve().body(User.class));
assertThatHttpObservation().hasLowCardinalityKeyValue("exception", "RestClientException");
assertThatHttpObservation().hasLowCardinalityKeyValue("exception", "UnknownContentTypeException");
}
@Test

2
spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfiguration.java

@ -122,7 +122,7 @@ public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { @@ -122,7 +122,7 @@ public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
}
@Override
protected void configureMessageConverters(HttpMessageConverters.Builder builder) {
protected void configureMessageConverters(HttpMessageConverters.ServerBuilder builder) {
this.configurers.configureMessageConverters(builder);
}

14
spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java

@ -863,9 +863,9 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv @@ -863,9 +863,9 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
* @since 7.0
*/
protected HttpMessageConverters createMessageConverters() {
HttpMessageConverters.Builder builder = HttpMessageConverters.withDefaults();
configureMessageConverters(builder);
return builder.build();
HttpMessageConverters.ServerBuilder serverBuilder = HttpMessageConverters.forServer().registerDefaults();
configureMessageConverters(serverBuilder);
return serverBuilder.build();
}
/**
@ -874,7 +874,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv @@ -874,7 +874,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
* @param builder the {@code HttpMessageConverters} builder to configure
* @since 7.0
*/
protected void configureMessageConverters(HttpMessageConverters.Builder builder) {
protected void configureMessageConverters(HttpMessageConverters.ServerBuilder builder) {
}
/**
@ -885,7 +885,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv @@ -885,7 +885,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
* otherwise be registered by default. Also see {@link #addDefaultHttpMessageConverters}
* for adding default message converters.
* @param converters a list to add message converters to (initially an empty list)
* @deprecated since 7.0 in favor of {@link #configureMessageConverters(HttpMessageConverters.Builder)}
* @deprecated since 7.0 in favor of {@link #configureMessageConverters(HttpMessageConverters.ServerBuilder)}
*/
@Deprecated(since = "7.0", forRemoval = true)
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
@ -897,7 +897,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv @@ -897,7 +897,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
* to be registered and then insert a custom converter through this method.
* @param converters the list of configured converters to extend
* @since 4.1.3
* @deprecated since 7.0 in favor of {@link #configureMessageConverters(HttpMessageConverters.Builder)}
* @deprecated since 7.0 in favor of {@link #configureMessageConverters(HttpMessageConverters.ServerBuilder)}
*/
@Deprecated(since = "7.0", forRemoval = true)
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
@ -912,7 +912,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv @@ -912,7 +912,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
@Deprecated(since = "7.0", forRemoval = true)
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
HttpMessageConverters converters = createMessageConverters();
converters.forServer().forEach(messageConverters::add);
converters.forEach(messageConverters::add);
}
/**

6
spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurer.java

@ -177,7 +177,7 @@ public interface WebMvcConfigurer { @@ -177,7 +177,7 @@ public interface WebMvcConfigurer {
* @param builder the builder to configure
* @since 7.0
*/
default void configureMessageConverters(HttpMessageConverters.Builder builder) {
default void configureMessageConverters(HttpMessageConverters.ServerBuilder builder) {
}
/**
@ -195,7 +195,7 @@ public interface WebMvcConfigurer { @@ -195,7 +195,7 @@ public interface WebMvcConfigurer {
* {@link #extendMessageConverters(java.util.List)} to modify the configured
* list of message converters.
* @param converters initially an empty list of converters
* @deprecated since 7.0 in favor of configuring converters on {@link #configureMessageConverters(HttpMessageConverters.Builder)}
* @deprecated since 7.0 in favor of configuring converters on {@link #configureMessageConverters(HttpMessageConverters.ServerBuilder)}
*/
@Deprecated(since = "7.0", forRemoval = true)
default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
@ -210,7 +210,7 @@ public interface WebMvcConfigurer { @@ -210,7 +210,7 @@ public interface WebMvcConfigurer {
* the converters configured earlier will be preferred.
* @param converters the list of configured converters to be extended
* @since 4.1.3
* @deprecated since 7.0 in favor of configuring converters on {@link #configureMessageConverters(HttpMessageConverters.Builder)}
* @deprecated since 7.0 in favor of configuring converters on {@link #configureMessageConverters(HttpMessageConverters.ServerBuilder)}
*/
@Deprecated(since = "7.0", forRemoval = true)
default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

2
spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurerComposite.java

@ -142,7 +142,7 @@ class WebMvcConfigurerComposite implements WebMvcConfigurer { @@ -142,7 +142,7 @@ class WebMvcConfigurerComposite implements WebMvcConfigurer {
}
@Override
public void configureMessageConverters(HttpMessageConverters.Builder builder) {
public void configureMessageConverters(HttpMessageConverters.ServerBuilder builder) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.configureMessageConverters(builder);
}

4
spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/DelegatingWebMvcConfigurationTests.java

@ -126,8 +126,8 @@ public class DelegatingWebMvcConfigurationTests { @@ -126,8 +126,8 @@ public class DelegatingWebMvcConfigurationTests {
WebMvcConfigurer configurer = new WebMvcConfigurer() {
@Override
public void configureMessageConverters(HttpMessageConverters.Builder builder) {
builder.additionalMessageConverter(customConverter);
public void configureMessageConverters(HttpMessageConverters.ServerBuilder builder) {
builder.customMessageConverter(customConverter);
}
};
webMvcConfig.setConfigurers(Collections.singletonList(configurer));

4
spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportExtensionTests.java

@ -356,11 +356,11 @@ class WebMvcConfigurationSupportExtensionTests { @@ -356,11 +356,11 @@ class WebMvcConfigurationSupportExtensionTests {
@Override
protected HttpMessageConverters createMessageConverters() {
return HttpMessageConverters.create().jsonMessageConverter(new JacksonJsonHttpMessageConverter()).build();
return HttpMessageConverters.forServer().jsonMessageConverter(new JacksonJsonHttpMessageConverter()).build();
}
@Override
public void configureMessageConverters(HttpMessageConverters.Builder builder) {
public void configureMessageConverters(HttpMessageConverters.ServerBuilder builder) {
}
@Override

Loading…
Cancel
Save