Skip to content

Commit beedf0a

Browse files
committed
Use HttpMessageConverters in client and server config
This commit uses the new `HttpMessageConverters` class for the HTTP client (`RestTemplate` and `RestClient`) and HTTP server support. This effectively removes the duplication of classpath detection for message converters in multiple places: clients, server and the multipart converter itself. Instead of creating multiple instances of the same converters, this allows applications to share converter instances as much as possible for better memory efficiency. As a result, this change also deprecates configuration methods in the MVC support that are superseded by the new methods introduced for `HttpMessageConverters` support. Closes gh-33894
1 parent 1af25e9 commit beedf0a

File tree

12 files changed

+130
-546
lines changed

12 files changed

+130
-546
lines changed

spring-test/src/main/java/org/springframework/test/web/servlet/setup/RouterFunctionMockMvcBuilder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ public RouterFunctionMapping getHandlerMapping(
268268
}
269269

270270
@Override
271+
@SuppressWarnings("removal")
271272
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
272273
converters.addAll(messageConverters);
273274
}

spring-test/src/main/java/org/springframework/test/web/servlet/setup/StandaloneMockMvcBuilder.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,7 @@ else if (patternParser != null) {
470470
}
471471

472472
@Override
473+
@SuppressWarnings("removal")
473474
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
474475
converters.addAll(messageConverters);
475476
}

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

Lines changed: 16 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,8 @@
1717
package org.springframework.http.converter.support;
1818

1919
import org.springframework.http.converter.FormHttpMessageConverter;
20-
import org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter;
21-
import org.springframework.http.converter.cbor.KotlinSerializationCborHttpMessageConverter;
22-
import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
23-
import org.springframework.http.converter.json.GsonHttpMessageConverter;
24-
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
25-
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
26-
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
27-
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
28-
import org.springframework.http.converter.protobuf.KotlinSerializationProtobufHttpMessageConverter;
29-
import org.springframework.http.converter.smile.JacksonSmileHttpMessageConverter;
30-
import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter;
31-
import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
32-
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
33-
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
34-
import org.springframework.http.converter.yaml.JacksonYamlHttpMessageConverter;
35-
import org.springframework.http.converter.yaml.MappingJackson2YamlHttpMessageConverter;
36-
import org.springframework.util.ClassUtils;
20+
import org.springframework.http.converter.HttpMessageConverter;
21+
import org.springframework.http.converter.HttpMessageConverters;
3722

3823
/**
3924
* Extension of {@link org.springframework.http.converter.FormHttpMessageConverter},
@@ -47,117 +32,24 @@
4732
*/
4833
public class AllEncompassingFormHttpMessageConverter extends FormHttpMessageConverter {
4934

50-
private static final boolean jaxb2Present;
51-
52-
private static final boolean jacksonPresent;
53-
54-
private static final boolean jackson2Present;
55-
56-
private static final boolean jacksonXmlPresent;
57-
58-
private static final boolean jackson2XmlPresent;
59-
60-
private static final boolean jacksonSmilePresent;
61-
62-
private static final boolean jackson2SmilePresent;
63-
64-
private static final boolean jacksonCborPresent;
65-
66-
private static final boolean jackson2CborPresent;
67-
68-
private static final boolean jacksonYamlPresent;
69-
70-
private static final boolean jackson2YamlPresent;
71-
72-
private static final boolean gsonPresent;
73-
74-
private static final boolean jsonbPresent;
75-
76-
private static final boolean kotlinSerializationCborPresent;
77-
78-
private static final boolean kotlinSerializationJsonPresent;
79-
80-
private static final boolean kotlinSerializationProtobufPresent;
81-
82-
static {
83-
ClassLoader classLoader = AllEncompassingFormHttpMessageConverter.class.getClassLoader();
84-
jaxb2Present = ClassUtils.isPresent("jakarta.xml.bind.Binder", classLoader);
85-
jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", classLoader);
86-
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
87-
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
88-
jacksonXmlPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.xml.XmlMapper", classLoader);
89-
jackson2XmlPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
90-
jacksonSmilePresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.smile.SmileMapper", classLoader);
91-
jackson2SmilePresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
92-
jacksonCborPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.cbor.CBORMapper", classLoader);
93-
jackson2CborPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
94-
jacksonYamlPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.yaml.YAMLMapper", classLoader);
95-
jackson2YamlPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.yaml.YAMLFactory", classLoader);
96-
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
97-
jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", classLoader);
98-
kotlinSerializationCborPresent = ClassUtils.isPresent("kotlinx.serialization.cbor.Cbor", classLoader);
99-
kotlinSerializationJsonPresent = ClassUtils.isPresent("kotlinx.serialization.json.Json", classLoader);
100-
kotlinSerializationProtobufPresent = ClassUtils.isPresent("kotlinx.serialization.protobuf.ProtoBuf", classLoader);
101-
}
102-
10335

36+
/**
37+
* Create a new {@link AllEncompassingFormHttpMessageConverter} instance
38+
* that will auto-detect part converters.
39+
*/
10440
@SuppressWarnings("removal")
10541
public AllEncompassingFormHttpMessageConverter() {
42+
HttpMessageConverters.withDefaults().build().forClient().forEach(this::addPartConverter);
43+
}
10644

107-
if (jaxb2Present && !jacksonXmlPresent && !jackson2XmlPresent) {
108-
addPartConverter(new Jaxb2RootElementHttpMessageConverter());
109-
}
110-
111-
if (jacksonPresent) {
112-
addPartConverter(new JacksonJsonHttpMessageConverter());
113-
}
114-
else if (jackson2Present) {
115-
addPartConverter(new MappingJackson2HttpMessageConverter());
116-
}
117-
else if (gsonPresent) {
118-
addPartConverter(new GsonHttpMessageConverter());
119-
}
120-
else if (jsonbPresent) {
121-
addPartConverter(new JsonbHttpMessageConverter());
122-
}
123-
else if (kotlinSerializationJsonPresent) {
124-
addPartConverter(new KotlinSerializationJsonHttpMessageConverter());
125-
}
126-
127-
if (jacksonXmlPresent) {
128-
addPartConverter(new JacksonXmlHttpMessageConverter());
129-
}
130-
else if (jackson2XmlPresent) {
131-
addPartConverter(new MappingJackson2XmlHttpMessageConverter());
132-
}
133-
134-
if (jacksonSmilePresent) {
135-
addPartConverter(new JacksonSmileHttpMessageConverter());
136-
}
137-
else if (jackson2SmilePresent) {
138-
addPartConverter(new MappingJackson2SmileHttpMessageConverter());
139-
}
140-
141-
if (jacksonCborPresent) {
142-
addPartConverter(new JacksonCborHttpMessageConverter());
143-
}
144-
else if (jackson2CborPresent) {
145-
addPartConverter(new MappingJackson2CborHttpMessageConverter());
146-
}
147-
else if (kotlinSerializationCborPresent) {
148-
addPartConverter(new KotlinSerializationCborHttpMessageConverter());
149-
}
150-
151-
if (jacksonYamlPresent) {
152-
addPartConverter(new JacksonYamlHttpMessageConverter());
153-
}
154-
else if (jackson2YamlPresent) {
155-
addPartConverter(new MappingJackson2YamlHttpMessageConverter());
156-
}
157-
158-
if (kotlinSerializationProtobufPresent) {
159-
addPartConverter(new KotlinSerializationProtobufHttpMessageConverter());
160-
}
45+
/**
46+
* Create a new {@link AllEncompassingFormHttpMessageConverter} instance
47+
* using the given message converters.
48+
* @param converters the message converters to use for part conversion
49+
* @since 7.0
50+
*/
51+
public AllEncompassingFormHttpMessageConverter(Iterable<HttpMessageConverter<?>> converters) {
52+
converters.forEach(this::addPartConverter);
16153
}
16254

16355
}

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

Lines changed: 11 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -43,29 +43,8 @@
4343
import org.springframework.http.client.ReactorClientHttpRequestFactory;
4444
import org.springframework.http.client.SimpleClientHttpRequestFactory;
4545
import org.springframework.http.client.observation.ClientRequestObservationConvention;
46-
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
4746
import org.springframework.http.converter.HttpMessageConverter;
48-
import org.springframework.http.converter.ResourceHttpMessageConverter;
49-
import org.springframework.http.converter.StringHttpMessageConverter;
50-
import org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter;
51-
import org.springframework.http.converter.cbor.KotlinSerializationCborHttpMessageConverter;
52-
import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
53-
import org.springframework.http.converter.feed.AtomFeedHttpMessageConverter;
54-
import org.springframework.http.converter.feed.RssChannelHttpMessageConverter;
55-
import org.springframework.http.converter.json.GsonHttpMessageConverter;
56-
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
57-
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
58-
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
59-
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
60-
import org.springframework.http.converter.protobuf.KotlinSerializationProtobufHttpMessageConverter;
61-
import org.springframework.http.converter.smile.JacksonSmileHttpMessageConverter;
62-
import org.springframework.http.converter.smile.MappingJackson2SmileHttpMessageConverter;
63-
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
64-
import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
65-
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
66-
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
67-
import org.springframework.http.converter.yaml.JacksonYamlHttpMessageConverter;
68-
import org.springframework.http.converter.yaml.MappingJackson2YamlHttpMessageConverter;
47+
import org.springframework.http.converter.HttpMessageConverters;
6948
import org.springframework.util.Assert;
7049
import org.springframework.util.ClassUtils;
7150
import org.springframework.util.CollectionUtils;
@@ -95,68 +74,13 @@ final class DefaultRestClientBuilder implements RestClient.Builder {
9574

9675
private static final boolean jdkClientPresent;
9776

98-
// message factories
99-
100-
private static final boolean romePresent;
101-
102-
private static final boolean jaxb2Present;
103-
104-
private static final boolean jacksonPresent;
105-
106-
private static final boolean jackson2Present;
107-
108-
private static final boolean jacksonXmlPresent;
109-
110-
private static final boolean jackson2XmlPresent;
111-
112-
private static final boolean jacksonSmilePresent;
113-
114-
private static final boolean jackson2SmilePresent;
115-
116-
private static final boolean jacksonCborPresent;
117-
118-
private static final boolean jackson2CborPresent;
119-
120-
private static final boolean jacksonYamlPresent;
121-
122-
private static final boolean jackson2YamlPresent;
123-
124-
private static final boolean gsonPresent;
125-
126-
private static final boolean jsonbPresent;
127-
128-
private static final boolean kotlinSerializationCborPresent;
129-
130-
private static final boolean kotlinSerializationJsonPresent;
131-
132-
private static final boolean kotlinSerializationProtobufPresent;
133-
13477
static {
13578
ClassLoader loader = DefaultRestClientBuilder.class.getClassLoader();
13679

13780
httpComponentsClientPresent = ClassUtils.isPresent("org.apache.hc.client5.http.classic.HttpClient", loader);
13881
jettyClientPresent = ClassUtils.isPresent("org.eclipse.jetty.client.HttpClient", loader);
13982
reactorNettyClientPresent = ClassUtils.isPresent("reactor.netty.http.client.HttpClient", loader);
14083
jdkClientPresent = ClassUtils.isPresent("java.net.http.HttpClient", loader);
141-
142-
romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", loader);
143-
jaxb2Present = ClassUtils.isPresent("jakarta.xml.bind.Binder", loader);
144-
jacksonPresent = ClassUtils.isPresent("tools.jackson.databind.ObjectMapper", loader);
145-
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", loader) &&
146-
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", loader);
147-
jacksonXmlPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.xml.XmlMapper", loader);
148-
jackson2XmlPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", loader);
149-
jacksonSmilePresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.smile.SmileMapper", loader);
150-
jackson2SmilePresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", loader);
151-
jacksonCborPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.cbor.CBORMapper", loader);
152-
jackson2CborPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", loader);
153-
jacksonYamlPresent = jacksonPresent && ClassUtils.isPresent("tools.jackson.dataformat.yaml.YAMLMapper", loader);
154-
jackson2YamlPresent = jackson2Present && ClassUtils.isPresent("com.fasterxml.jackson.dataformat.yaml.YAMLFactory", loader);
155-
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", loader);
156-
jsonbPresent = ClassUtils.isPresent("jakarta.json.bind.Jsonb", loader);
157-
kotlinSerializationCborPresent = ClassUtils.isPresent("kotlinx.serialization.cbor.Cbor", loader);
158-
kotlinSerializationJsonPresent = ClassUtils.isPresent("kotlinx.serialization.json.Json", loader);
159-
kotlinSerializationProtobufPresent = ClassUtils.isPresent("kotlinx.serialization.protobuf.ProtoBuf", loader);
16084
}
16185

16286
private @Nullable String baseUrl;
@@ -444,9 +368,11 @@ public RestClient.Builder messageConverters(Consumer<List<HttpMessageConverter<?
444368
}
445369

446370
@Override
447-
public RestClient.Builder messageConverters(List<HttpMessageConverter<?>> messageConverters) {
371+
public RestClient.Builder messageConverters(Iterable<HttpMessageConverter<?>> messageConverters) {
448372
validateConverters(messageConverters);
449-
this.messageConverters = Collections.unmodifiableList(messageConverters);
373+
List<HttpMessageConverter<?>> converters = new ArrayList<>();
374+
messageConverters.forEach(converter -> converters.add(converter));
375+
this.messageConverters = Collections.unmodifiableList(converters);
450376
return this;
451377
}
452378

@@ -473,77 +399,16 @@ public RestClient.Builder apply(Consumer<RestClient.Builder> builderConsumer) {
473399
private List<HttpMessageConverter<?>> initMessageConverters() {
474400
if (this.messageConverters == null) {
475401
this.messageConverters = new ArrayList<>();
476-
477-
this.messageConverters.add(new ByteArrayHttpMessageConverter());
478-
this.messageConverters.add(new StringHttpMessageConverter());
479-
this.messageConverters.add(new ResourceHttpMessageConverter(false));
480-
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
481-
482-
if (romePresent) {
483-
this.messageConverters.add(new AtomFeedHttpMessageConverter());
484-
this.messageConverters.add(new RssChannelHttpMessageConverter());
485-
}
486-
487-
if (jacksonXmlPresent) {
488-
this.messageConverters.add(new JacksonXmlHttpMessageConverter());
489-
}
490-
else if (jackson2XmlPresent) {
491-
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
492-
}
493-
else if (jaxb2Present) {
494-
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
495-
}
496-
497-
if (kotlinSerializationProtobufPresent) {
498-
this.messageConverters.add(new KotlinSerializationProtobufHttpMessageConverter());
499-
}
500-
501-
if (jacksonPresent) {
502-
this.messageConverters.add(new JacksonJsonHttpMessageConverter());
503-
}
504-
else if (jackson2Present) {
505-
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
506-
}
507-
else if (gsonPresent) {
508-
this.messageConverters.add(new GsonHttpMessageConverter());
509-
}
510-
else if (jsonbPresent) {
511-
this.messageConverters.add(new JsonbHttpMessageConverter());
512-
}
513-
else if (kotlinSerializationJsonPresent) {
514-
this.messageConverters.add(new KotlinSerializationJsonHttpMessageConverter());
515-
}
516-
517-
if (jacksonSmilePresent) {
518-
this.messageConverters.add(new JacksonSmileHttpMessageConverter());
519-
}
520-
if (jackson2SmilePresent) {
521-
this.messageConverters.add(new MappingJackson2SmileHttpMessageConverter());
522-
}
523-
524-
if (jacksonCborPresent) {
525-
this.messageConverters.add(new JacksonCborHttpMessageConverter());
526-
}
527-
else if (jackson2CborPresent) {
528-
this.messageConverters.add(new MappingJackson2CborHttpMessageConverter());
529-
}
530-
else if (kotlinSerializationCborPresent) {
531-
this.messageConverters.add(new KotlinSerializationCborHttpMessageConverter());
532-
}
533-
534-
if (jacksonYamlPresent) {
535-
this.messageConverters.add(new JacksonYamlHttpMessageConverter());
536-
}
537-
else if (jackson2YamlPresent) {
538-
this.messageConverters.add(new MappingJackson2YamlHttpMessageConverter());
539-
}
402+
HttpMessageConverters.withDefaults().build().forClient().forEach(this.messageConverters::add);
540403
}
541404
return this.messageConverters;
542405
}
543406

544-
private void validateConverters(@Nullable List<HttpMessageConverter<?>> messageConverters) {
545-
Assert.notEmpty(messageConverters, "At least one HttpMessageConverter is required");
546-
Assert.noNullElements(messageConverters, "The HttpMessageConverter list must not contain null elements");
407+
private void validateConverters(@Nullable Iterable<HttpMessageConverter<?>> messageConverters) {
408+
Assert.notNull(messageConverters, "At least one HttpMessageConverter is required");
409+
Assert.isTrue(messageConverters.iterator().hasNext(), "At least one HttpMessageConverter is required");
410+
messageConverters.forEach(converter ->
411+
Assert.notNull(converter, "The HttpMessageConverter list must not contain null elements"));
547412
}
548413

549414

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ Builder defaultStatusHandler(Predicate<HttpStatusCode> statusPredicate,
450450
* @param configurer the configurer to apply on the list of default
451451
* {@link HttpMessageConverter} pre-initialized
452452
* @return this builder
453-
* @see #messageConverters(List)
453+
* @see #messageConverters(Iterable)
454454
*/
455455
Builder messageConverters(Consumer<List<HttpMessageConverter<?>>> configurer);
456456

@@ -461,7 +461,7 @@ Builder defaultStatusHandler(Predicate<HttpStatusCode> statusPredicate,
461461
* @since 6.2
462462
* @see #messageConverters(Consumer)
463463
*/
464-
Builder messageConverters(List<HttpMessageConverter<?>> messageConverters);
464+
Builder messageConverters(Iterable<HttpMessageConverter<?>> messageConverters);
465465

466466
/**
467467
* Configure the {@link io.micrometer.observation.ObservationRegistry} to use

0 commit comments

Comments
 (0)