Create spring-boot-http module

This commit is contained in:
Andy Wilkinson
2025-03-27 15:05:29 +00:00
committed by Phillip Webb
parent 31c9d3c17a
commit e83f4eed4e
87 changed files with 215 additions and 349 deletions

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.http.autoconfigure;
import com.google.gson.Gson;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
/**
* Configuration for HTTP Message converters that use Gson.
*
* @author Andy Wilkinson
* @author Eddú Meléndez
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Gson.class)
class GsonHttpMessageConvertersConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(Gson.class)
@Conditional(PreferGsonOrJacksonAndJsonbUnavailableCondition.class)
static class GsonHttpMessageConverterConfiguration {
@Bean
@ConditionalOnMissingBean
GsonHttpMessageConverter gsonHttpMessageConverter(Gson gson) {
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
converter.setGson(gson);
return converter;
}
}
private static class PreferGsonOrJacksonAndJsonbUnavailableCondition extends AnyNestedCondition {
PreferGsonOrJacksonAndJsonbUnavailableCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
havingValue = "gson")
static class GsonPreferred {
}
@Conditional(JacksonAndJsonbUnavailableCondition.class)
static class JacksonJsonbUnavailable {
}
}
private static class JacksonAndJsonbUnavailableCondition extends NoneNestedConditions {
JacksonAndJsonbUnavailableCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@SuppressWarnings({ "deprecation", "removal" })
@ConditionalOnBean(org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class)
static class JacksonAvailable {
}
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
havingValue = "jsonb")
static class JsonbPreferred {
}
}
}

View File

@@ -0,0 +1,248 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.http.autoconfigure;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.http.converter.xml.AbstractXmlHttpMessageConverter;
import org.springframework.util.ClassUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* Bean used to manage the {@link HttpMessageConverter}s used in a Spring Boot
* application. Provides a convenient way to add and merge additional
* {@link HttpMessageConverter}s to a web application.
* <p>
* An instance of this bean can be registered with specific
* {@link #HttpMessageConverters(HttpMessageConverter...) additional converters} if
* needed, otherwise default converters will be used.
* <p>
* NOTE: The default converters used are the same as standard Spring MVC (see
* {@link WebMvcConfigurationSupport}) with some slight re-ordering to put XML converters
* at the back of the list.
*
* @author Dave Syer
* @author Phillip Webb
* @author Andy Wilkinson
* @since 2.0.0
* @see #HttpMessageConverters(HttpMessageConverter...)
* @see #HttpMessageConverters(Collection)
* @see #getConverters()
*/
public class HttpMessageConverters implements Iterable<HttpMessageConverter<?>> {
private static final List<Class<?>> NON_REPLACING_CONVERTERS;
static {
List<Class<?>> nonReplacingConverters = new ArrayList<>();
addClassIfExists(nonReplacingConverters,
"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter");
NON_REPLACING_CONVERTERS = Collections.unmodifiableList(nonReplacingConverters);
}
private static final Map<Class<?>, Class<?>> EQUIVALENT_CONVERTERS;
static {
Map<Class<?>, Class<?>> equivalentConverters = new HashMap<>();
putIfExists(equivalentConverters, "org.springframework.http.converter.json.MappingJackson2HttpMessageConverter",
"org.springframework.http.converter.json.GsonHttpMessageConverter");
EQUIVALENT_CONVERTERS = Collections.unmodifiableMap(equivalentConverters);
}
private final List<HttpMessageConverter<?>> converters;
/**
* Create a new {@link HttpMessageConverters} instance with the specified additional
* converters.
* @param additionalConverters additional converters to be added. Items are added just
* before any default converter of the same type (or at the front of the list if no
* default converter is found). The {@link #postProcessConverters(List)} method can be
* used for further converter manipulation.
*/
public HttpMessageConverters(HttpMessageConverter<?>... additionalConverters) {
this(Arrays.asList(additionalConverters));
}
/**
* Create a new {@link HttpMessageConverters} instance with the specified additional
* converters.
* @param additionalConverters additional converters to be added. Items are added just
* before any default converter of the same type (or at the front of the list if no
* default converter is found). The {@link #postProcessConverters(List)} method can be
* used for further converter manipulation.
*/
public HttpMessageConverters(Collection<HttpMessageConverter<?>> additionalConverters) {
this(true, additionalConverters);
}
/**
* Create a new {@link HttpMessageConverters} instance with the specified converters.
* @param addDefaultConverters if default converters should be added
* @param converters converters to be added. Items are added just before any default
* converter of the same type (or at the front of the list if no default converter is
* found). The {@link #postProcessConverters(List)} method can be used for further
* converter manipulation.
*/
public HttpMessageConverters(boolean addDefaultConverters, Collection<HttpMessageConverter<?>> converters) {
List<HttpMessageConverter<?>> combined = getCombinedConverters(converters,
addDefaultConverters ? getDefaultConverters() : Collections.emptyList());
combined = postProcessConverters(combined);
this.converters = Collections.unmodifiableList(combined);
}
private List<HttpMessageConverter<?>> getCombinedConverters(Collection<HttpMessageConverter<?>> converters,
List<HttpMessageConverter<?>> defaultConverters) {
List<HttpMessageConverter<?>> combined = new ArrayList<>();
List<HttpMessageConverter<?>> processing = new ArrayList<>(converters);
for (HttpMessageConverter<?> defaultConverter : defaultConverters) {
Iterator<HttpMessageConverter<?>> iterator = processing.iterator();
while (iterator.hasNext()) {
HttpMessageConverter<?> candidate = iterator.next();
if (isReplacement(defaultConverter, candidate)) {
combined.add(candidate);
iterator.remove();
}
}
combined.add(defaultConverter);
if (defaultConverter instanceof AllEncompassingFormHttpMessageConverter allEncompassingConverter) {
configurePartConverters(allEncompassingConverter, converters);
}
}
combined.addAll(0, processing);
return combined;
}
private boolean isReplacement(HttpMessageConverter<?> defaultConverter, HttpMessageConverter<?> candidate) {
for (Class<?> nonReplacingConverter : NON_REPLACING_CONVERTERS) {
if (nonReplacingConverter.isInstance(candidate)) {
return false;
}
}
Class<?> converterClass = defaultConverter.getClass();
if (ClassUtils.isAssignableValue(converterClass, candidate)) {
return true;
}
Class<?> equivalentClass = EQUIVALENT_CONVERTERS.get(converterClass);
return equivalentClass != null && ClassUtils.isAssignableValue(equivalentClass, candidate);
}
private void configurePartConverters(AllEncompassingFormHttpMessageConverter formConverter,
Collection<HttpMessageConverter<?>> converters) {
List<HttpMessageConverter<?>> partConverters = formConverter.getPartConverters();
List<HttpMessageConverter<?>> combinedConverters = getCombinedConverters(converters, partConverters);
combinedConverters = postProcessPartConverters(combinedConverters);
formConverter.setPartConverters(combinedConverters);
}
/**
* Method that can be used to post-process the {@link HttpMessageConverter} list
* before it is used.
* @param converters a mutable list of the converters that will be used.
* @return the final converts list to use
*/
protected List<HttpMessageConverter<?>> postProcessConverters(List<HttpMessageConverter<?>> converters) {
return converters;
}
/**
* Method that can be used to post-process the {@link HttpMessageConverter} list
* before it is used to configure the part converters of
* {@link AllEncompassingFormHttpMessageConverter}.
* @param converters a mutable list of the converters that will be used.
* @return the final converts list to use
* @since 1.3.0
*/
protected List<HttpMessageConverter<?>> postProcessPartConverters(List<HttpMessageConverter<?>> converters) {
return converters;
}
private List<HttpMessageConverter<?>> getDefaultConverters() {
List<HttpMessageConverter<?>> converters = new ArrayList<>();
if (ClassUtils.isPresent("org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport",
null)) {
converters.addAll(new WebMvcConfigurationSupport() {
public List<HttpMessageConverter<?>> defaultMessageConverters() {
return super.getMessageConverters();
}
}.defaultMessageConverters());
}
else {
converters.addAll(new RestTemplate().getMessageConverters());
}
reorderXmlConvertersToEnd(converters);
return converters;
}
@SuppressWarnings("removal")
private void reorderXmlConvertersToEnd(List<HttpMessageConverter<?>> converters) {
List<HttpMessageConverter<?>> xml = new ArrayList<>();
for (Iterator<HttpMessageConverter<?>> iterator = converters.iterator(); iterator.hasNext();) {
HttpMessageConverter<?> converter = iterator.next();
if ((converter instanceof AbstractXmlHttpMessageConverter)
|| (converter instanceof org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter)) {
xml.add(converter);
iterator.remove();
}
}
converters.addAll(xml);
}
@Override
public Iterator<HttpMessageConverter<?>> iterator() {
return getConverters().iterator();
}
/**
* Return an immutable list of the converters in the order that they will be
* registered.
* @return the converters
*/
public List<HttpMessageConverter<?>> getConverters() {
return this.converters;
}
private static void addClassIfExists(List<Class<?>> list, String className) {
try {
list.add(Class.forName(className));
}
catch (ClassNotFoundException | NoClassDefFoundError ex) {
// Ignore
}
}
private static void putIfExists(Map<Class<?>, Class<?>> map, String keyClassName, String valueClassName) {
try {
map.put(Class.forName(keyClassName), Class.forName(valueClassName));
}
catch (ClassNotFoundException | NoClassDefFoundError ex) {
// Ignore
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.http.autoconfigure;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.condition.NoneNestedConditions;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.http.autoconfigure.HttpMessageConvertersAutoConfiguration.NotReactiveWebApplicationCondition;
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.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link HttpMessageConverter}s.
*
* @author Dave Syer
* @author Christian Dupuis
* @author Piotr Maj
* @author Oliver Gierke
* @author David Liu
* @author Andy Wilkinson
* @author Sebastien Deleuze
* @author Stephane Nicoll
* @author Eddú Meléndez
* @since 2.0.0
*/
@AutoConfiguration(afterName = { "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration",
"org.springframework.boot.jsonb.autoconfigure.JsonbAutoConfiguration",
"org.springframework.boot.gson.autoconfigure.GsonAutoConfiguration" })
@ConditionalOnClass(HttpMessageConverter.class)
@Conditional(NotReactiveWebApplicationCondition.class)
@Import({ JacksonHttpMessageConvertersConfiguration.class, GsonHttpMessageConvertersConfiguration.class,
JsonbHttpMessageConvertersConfiguration.class })
public class HttpMessageConvertersAutoConfiguration {
static final String PREFERRED_MAPPER_PROPERTY = "spring.http.converters.preferred-json-mapper";
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
return new HttpMessageConverters(converters.orderedStream().toList());
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(StringHttpMessageConverter.class)
@EnableConfigurationProperties(HttpMessageConvertersProperties.class)
protected static class StringHttpMessageConverterConfiguration {
@Bean
@ConditionalOnMissingBean
public StringHttpMessageConverter stringHttpMessageConverter(HttpMessageConvertersProperties properties) {
StringHttpMessageConverter converter = new StringHttpMessageConverter(
properties.getStringEncodingCharset());
converter.setWriteAcceptCharset(false);
return converter;
}
}
static class NotReactiveWebApplicationCondition extends NoneNestedConditions {
NotReactiveWebApplicationCondition() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnWebApplication(type = Type.REACTIVE)
private static final class ReactiveWebApplication {
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.http.autoconfigure;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* {@link ConfigurationProperties @ConfigurationProperties} for HTTP message conversion.
*
* @author Andy Wilkinson
* @since 4.0.0
*/
@ConfigurationProperties("spring.http.converters")
public class HttpMessageConvertersProperties {
/**
* The charset to use for String conversion.
*/
private Charset stringEncodingCharset = StandardCharsets.UTF_8;
public Charset getStringEncodingCharset() {
return this.stringEncodingCharset;
}
public void setStringEncodingCharset(Charset stringEncodingCharset) {
this.stringEncodingCharset = stringEncodingCharset;
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.http.autoconfigure;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
/**
* Configuration for HTTP message converters that use Jackson.
*
* @author Andy Wilkinson
*/
@Configuration(proxyBeanMethods = false)
@SuppressWarnings("removal")
class JacksonHttpMessageConvertersConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObjectMapper.class)
@ConditionalOnBean(ObjectMapper.class)
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
havingValue = "jackson", matchIfMissing = true)
static class MappingJackson2HttpMessageConverterConfiguration {
@Bean
@ConditionalOnMissingBean(ignoredType = {
"org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter",
"org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter" })
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
return new MappingJackson2HttpMessageConverter(objectMapper);
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(XmlMapper.class)
@ConditionalOnBean(Jackson2ObjectMapperBuilder.class)
protected static class MappingJackson2XmlHttpMessageConverterConfiguration {
@Bean
@ConditionalOnMissingBean
public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter(
Jackson2ObjectMapperBuilder builder) {
return new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build());
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.http.autoconfigure;
import jakarta.json.bind.Jsonb;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
/**
* Configuration for HTTP Message converters that use JSON-B.
*
* @author Eddú Meléndez
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Jsonb.class)
class JsonbHttpMessageConvertersConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(Jsonb.class)
@Conditional(PreferJsonbOrMissingJacksonAndGsonCondition.class)
static class JsonbHttpMessageConverterConfiguration {
@Bean
@ConditionalOnMissingBean
JsonbHttpMessageConverter jsonbHttpMessageConverter(Jsonb jsonb) {
JsonbHttpMessageConverter converter = new JsonbHttpMessageConverter();
converter.setJsonb(jsonb);
return converter;
}
}
private static class PreferJsonbOrMissingJacksonAndGsonCondition extends AnyNestedCondition {
PreferJsonbOrMissingJacksonAndGsonCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
havingValue = "jsonb")
static class JsonbPreferred {
}
@SuppressWarnings("removal")
@ConditionalOnMissingBean({ org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class,
GsonHttpMessageConverter.class })
static class JacksonAndGsonMissing {
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.http.autoconfigure;
import org.springframework.boot.autoconfigure.preinitialize.BackgroundPreinitializer;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
/**
* {@link BackgroundPreinitializer} Spring's {@link HttpMessageConverter} implementations.
*
* @author Phillip Webb
*/
final class MessageConverterBackgroundPreinitializer implements BackgroundPreinitializer {
@Override
public void preinitialize() throws Exception {
new AllEncompassingFormHttpMessageConverter();
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Auto-configuration for HTTP concerns.
*/
package org.springframework.boot.http.autoconfigure;

View File

@@ -0,0 +1,31 @@
{
"properties": [
{
"name": "spring.http.converters.preferred-json-mapper",
"type": "java.lang.String",
"defaultValue": "jackson",
"description": "Preferred JSON mapper to use for HTTP message conversion. By default, auto-detected according to the environment. Supported values are 'jackson', 'gson', and 'jsonb'. When other json mapping libraries (such as kotlinx.serialization) are present, use a custom HttpMessageConverters bean to control the preferred mapper."
}
],
"hints": [
{
"name": "spring.http.converters.preferred-json-mapper",
"values": [
{
"value": "gson"
},
{
"value": "jackson"
},
{
"value": "jsonb"
}
],
"providers": [
{
"name": "any"
}
]
}
]
}

View File

@@ -0,0 +1,3 @@
# Background Preinitializers
org.springframework.boot.autoconfigure.preinitialize.BackgroundPreinitializer=\
org.springframework.boot.http.autoconfigure.MessageConverterBackgroundPreinitializer

View File

@@ -0,0 +1 @@
org.springframework.boot.http.autoconfigure.HttpMessageConvertersAutoConfiguration

View File

@@ -0,0 +1,385 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.http.autoconfigure;
import java.nio.charset.StandardCharsets;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.http.autoconfigure.JacksonHttpMessageConvertersConfiguration.MappingJackson2HttpMessageConverterConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.server.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HttpMessageConvertersAutoConfiguration}.
*
* @author Dave Syer
* @author Oliver Gierke
* @author David Liu
* @author Andy Wilkinson
* @author Sebastien Deleuze
* @author Eddú Meléndez
* @author Moritz Halbritter
* @author Sebastien Deleuze
*/
@SuppressWarnings("removal")
class HttpMessageConvertersAutoConfigurationTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class));
@Test
void jacksonNotAvailable() {
this.contextRunner.run((context) -> {
assertThat(context).doesNotHaveBean(ObjectMapper.class);
assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class);
assertThat(context).doesNotHaveBean(MappingJackson2XmlHttpMessageConverter.class);
});
}
@Test
void jacksonDefaultConverter() {
this.contextRunner.withUserConfiguration(JacksonObjectMapperConfig.class)
.run(assertConverter(MappingJackson2HttpMessageConverter.class, "mappingJackson2HttpMessageConverter"));
}
@Test
void jacksonConverterWithBuilder() {
this.contextRunner.withUserConfiguration(JacksonObjectMapperBuilderConfig.class)
.run(assertConverter(MappingJackson2HttpMessageConverter.class, "mappingJackson2HttpMessageConverter"));
}
@Test
void jacksonXmlConverterWithBuilder() {
this.contextRunner.withUserConfiguration(JacksonObjectMapperBuilderConfig.class)
.run(assertConverter(MappingJackson2XmlHttpMessageConverter.class,
"mappingJackson2XmlHttpMessageConverter"));
}
@Test
void jacksonCustomConverter() {
this.contextRunner.withUserConfiguration(JacksonObjectMapperConfig.class, JacksonConverterConfig.class)
.run(assertConverter(MappingJackson2HttpMessageConverter.class, "customJacksonMessageConverter"));
}
@Test
void gsonNotAvailable() {
this.contextRunner.run((context) -> {
assertThat(context).doesNotHaveBean(Gson.class);
assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class);
});
}
@Test
void gsonDefaultConverter() {
this.contextRunner.withBean(Gson.class)
.run(assertConverter(GsonHttpMessageConverter.class, "gsonHttpMessageConverter"));
}
@Test
void gsonCustomConverter() {
this.contextRunner.withUserConfiguration(GsonConverterConfig.class)
.withBean(Gson.class)
.run(assertConverter(GsonHttpMessageConverter.class, "customGsonMessageConverter"));
}
@Test
void gsonCanBePreferred() {
allOptionsRunner().withPropertyValues("spring.http.converters.preferred-json-mapper:gson").run((context) -> {
assertConverterBeanExists(context, GsonHttpMessageConverter.class, "gsonHttpMessageConverter");
assertConverterBeanRegisteredWithHttpMessageConverters(context, GsonHttpMessageConverter.class);
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class);
});
}
@Test
void jsonbNotAvailable() {
this.contextRunner.run((context) -> {
assertThat(context).doesNotHaveBean(Jsonb.class);
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
});
}
@Test
void jsonbDefaultConverter() {
this.contextRunner.withBean(Jsonb.class, JsonbBuilder::create)
.run(assertConverter(JsonbHttpMessageConverter.class, "jsonbHttpMessageConverter"));
}
@Test
void jsonbCustomConverter() {
this.contextRunner.withUserConfiguration(JsonbConverterConfig.class)
.withBean(Jsonb.class, JsonbBuilder::create)
.run(assertConverter(JsonbHttpMessageConverter.class, "customJsonbMessageConverter"));
}
@Test
void jsonbCanBePreferred() {
allOptionsRunner().withPropertyValues("spring.http.converters.preferred-json-mapper:jsonb").run((context) -> {
assertConverterBeanExists(context, JsonbHttpMessageConverter.class, "jsonbHttpMessageConverter");
assertConverterBeanRegisteredWithHttpMessageConverters(context, JsonbHttpMessageConverter.class);
assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class);
assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class);
});
}
@Test
void stringDefaultConverter() {
this.contextRunner.run(assertConverter(StringHttpMessageConverter.class, "stringHttpMessageConverter"));
}
@Test
void stringCustomConverter() {
this.contextRunner.withUserConfiguration(StringConverterConfig.class)
.run(assertConverter(StringHttpMessageConverter.class, "customStringMessageConverter"));
}
@Test
void typeConstrainedConverterDoesNotPreventAutoConfigurationOfJacksonConverter() {
this.contextRunner
.withUserConfiguration(JacksonObjectMapperBuilderConfig.class, TypeConstrainedConverterConfiguration.class)
.run((context) -> {
BeanDefinition beanDefinition = ((GenericApplicationContext) context.getSourceApplicationContext())
.getBeanDefinition("mappingJackson2HttpMessageConverter");
assertThat(beanDefinition.getFactoryBeanName())
.isEqualTo(MappingJackson2HttpMessageConverterConfiguration.class.getName());
});
}
@Test
void typeConstrainedConverterFromSpringDataDoesNotPreventAutoConfigurationOfJacksonConverter() {
this.contextRunner
.withUserConfiguration(JacksonObjectMapperBuilderConfig.class, RepositoryRestMvcConfiguration.class)
.run((context) -> {
BeanDefinition beanDefinition = ((GenericApplicationContext) context.getSourceApplicationContext())
.getBeanDefinition("mappingJackson2HttpMessageConverter");
assertThat(beanDefinition.getFactoryBeanName())
.isEqualTo(MappingJackson2HttpMessageConverterConfiguration.class.getName());
});
}
@Test
void jacksonIsPreferredByDefault() {
allOptionsRunner().run((context) -> {
assertConverterBeanExists(context, MappingJackson2HttpMessageConverter.class,
"mappingJackson2HttpMessageConverter");
assertConverterBeanRegisteredWithHttpMessageConverters(context, MappingJackson2HttpMessageConverter.class);
assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class);
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
});
}
@Test
void gsonIsPreferredIfJacksonIsNotAvailable() {
allOptionsRunner().withClassLoader(new FilteredClassLoader(ObjectMapper.class.getPackage().getName()))
.run((context) -> {
assertConverterBeanExists(context, GsonHttpMessageConverter.class, "gsonHttpMessageConverter");
assertConverterBeanRegisteredWithHttpMessageConverters(context, GsonHttpMessageConverter.class);
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
});
}
@Test
void jsonbIsPreferredIfJacksonAndGsonAreNotAvailable() {
allOptionsRunner()
.withClassLoader(new FilteredClassLoader(ObjectMapper.class.getPackage().getName(),
Gson.class.getPackage().getName()))
.run(assertConverter(JsonbHttpMessageConverter.class, "jsonbHttpMessageConverter"));
}
@Test
void whenServletWebApplicationHttpMessageConvertersIsConfigured() {
new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
.run((context) -> assertThat(context).hasSingleBean(HttpMessageConverters.class));
}
@Test
void whenReactiveWebApplicationHttpMessageConvertersIsNotConfigured() {
new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
.run((context) -> assertThat(context).doesNotHaveBean(HttpMessageConverters.class));
}
@Test
void whenEncodingCharsetIsNotConfiguredThenStringMessageConverterUsesUtf8() {
new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
.run((context) -> {
assertThat(context).hasSingleBean(StringHttpMessageConverter.class);
assertThat(context.getBean(StringHttpMessageConverter.class).getDefaultCharset())
.isEqualTo(StandardCharsets.UTF_8);
});
}
@Test
void whenEncodingCharsetIsConfiguredThenStringMessageConverterUsesSpecificCharset() {
new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
.withPropertyValues("spring.http.converters.string-encoding-charset=UTF-16")
.run((context) -> {
assertThat(context).hasSingleBean(StringHttpMessageConverter.class);
assertThat(context.getBean(StringHttpMessageConverter.class).getDefaultCharset())
.isEqualTo(StandardCharsets.UTF_16);
});
}
@Test // gh-21789
void whenAutoConfigurationIsActiveThenServerPropertiesConfigurationPropertiesAreNotEnabled() {
new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
.run((context) -> {
assertThat(context).hasSingleBean(HttpMessageConverters.class);
assertThat(context).doesNotHaveBean(ServerProperties.class);
});
}
private ApplicationContextRunner allOptionsRunner() {
return this.contextRunner.withBean(Gson.class)
.withBean(ObjectMapper.class)
.withBean(Jsonb.class, JsonbBuilder::create);
}
private ContextConsumer<AssertableApplicationContext> assertConverter(
Class<? extends HttpMessageConverter<?>> converterType, String beanName) {
return (context) -> {
assertConverterBeanExists(context, converterType, beanName);
assertConverterBeanRegisteredWithHttpMessageConverters(context, converterType);
};
}
private void assertConverterBeanExists(AssertableApplicationContext context, Class<?> type, String beanName) {
assertThat(context).hasSingleBean(type);
assertThat(context).hasBean(beanName);
}
private void assertConverterBeanRegisteredWithHttpMessageConverters(AssertableApplicationContext context,
Class<? extends HttpMessageConverter<?>> type) {
HttpMessageConverter<?> converter = context.getBean(type);
HttpMessageConverters converters = context.getBean(HttpMessageConverters.class);
assertThat(converters.getConverters()).contains(converter);
}
@Configuration(proxyBeanMethods = false)
static class JacksonObjectMapperConfig {
@Bean
ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
@Configuration(proxyBeanMethods = false)
static class JacksonObjectMapperBuilderConfig {
@Bean
ObjectMapper objectMapper() {
return new ObjectMapper();
}
@Bean
Jackson2ObjectMapperBuilder builder() {
return new Jackson2ObjectMapperBuilder();
}
}
@Configuration(proxyBeanMethods = false)
static class JacksonConverterConfig {
@Bean
MappingJackson2HttpMessageConverter customJacksonMessageConverter(ObjectMapper objectMapper) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper);
return converter;
}
}
@Configuration(proxyBeanMethods = false)
static class GsonConverterConfig {
@Bean
GsonHttpMessageConverter customGsonMessageConverter(Gson gson) {
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
converter.setGson(gson);
return converter;
}
}
@Configuration(proxyBeanMethods = false)
static class JsonbConverterConfig {
@Bean
JsonbHttpMessageConverter customJsonbMessageConverter(Jsonb jsonb) {
JsonbHttpMessageConverter converter = new JsonbHttpMessageConverter();
converter.setJsonb(jsonb);
return converter;
}
}
@Configuration(proxyBeanMethods = false)
static class StringConverterConfig {
@Bean
StringHttpMessageConverter customStringMessageConverter() {
return new StringHttpMessageConverter();
}
}
@Configuration(proxyBeanMethods = false)
static class TypeConstrainedConverterConfiguration {
@Bean
TypeConstrainedMappingJackson2HttpMessageConverter typeConstrainedConverter() {
return new TypeConstrainedMappingJackson2HttpMessageConverter(RepresentationModel.class);
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.http.autoconfigure;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.testsupport.classpath.ClassPathExclusions;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link HttpMessageConvertersAutoConfiguration} without Jackson on the
* classpath.
*
* @author Andy Wilkinson
*/
@ClassPathExclusions("jackson-*.jar")
class HttpMessageConvertersAutoConfigurationWithoutJacksonTests {
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class));
@Test
void autoConfigurationWorksWithSpringHateoasButWithoutJackson() {
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(HttpMessageConverters.class));
}
}

View File

@@ -0,0 +1,167 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.http.autoconfigure;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.ResourceRegionHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link HttpMessageConverters}.
*
* @author Dave Syer
* @author Phillip Webb
*/
@SuppressWarnings("removal")
class HttpMessageConvertersTests {
@Test
void containsDefaults() {
HttpMessageConverters converters = new HttpMessageConverters();
List<Class<?>> converterClasses = new ArrayList<>();
for (HttpMessageConverter<?> converter : converters) {
converterClasses.add(converter.getClass());
}
assertThat(converterClasses).containsExactly(ByteArrayHttpMessageConverter.class,
StringHttpMessageConverter.class, ResourceHttpMessageConverter.class,
ResourceRegionHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class,
MappingJackson2HttpMessageConverter.class, MappingJackson2CborHttpMessageConverter.class,
MappingJackson2XmlHttpMessageConverter.class);
}
@Test
void addBeforeExistingConverter() {
MappingJackson2HttpMessageConverter converter1 = new MappingJackson2HttpMessageConverter();
MappingJackson2HttpMessageConverter converter2 = new MappingJackson2HttpMessageConverter();
HttpMessageConverters converters = new HttpMessageConverters(converter1, converter2);
assertThat(converters.getConverters()).contains(converter1);
assertThat(converters.getConverters()).contains(converter2);
List<MappingJackson2HttpMessageConverter> httpConverters = new ArrayList<>();
for (HttpMessageConverter<?> candidate : converters) {
if (candidate instanceof MappingJackson2HttpMessageConverter) {
httpConverters.add((MappingJackson2HttpMessageConverter) candidate);
}
}
// The existing converter is still there, but with a lower priority
assertThat(httpConverters).hasSize(3);
assertThat(httpConverters.indexOf(converter1)).isZero();
assertThat(httpConverters.indexOf(converter2)).isOne();
assertThat(converters.getConverters().indexOf(converter1)).isNotZero();
}
@Test
void addBeforeExistingEquivalentConverter() {
GsonHttpMessageConverter converter1 = new GsonHttpMessageConverter();
HttpMessageConverters converters = new HttpMessageConverters(converter1);
Stream<Class<?>> converterClasses = converters.getConverters().stream().map(HttpMessageConverter::getClass);
assertThat(converterClasses).containsSequence(GsonHttpMessageConverter.class,
MappingJackson2HttpMessageConverter.class);
}
@Test
void addNewConverters() {
HttpMessageConverter<?> converter1 = mock(HttpMessageConverter.class);
HttpMessageConverter<?> converter2 = mock(HttpMessageConverter.class);
HttpMessageConverters converters = new HttpMessageConverters(converter1, converter2);
assertThat(converters.getConverters().get(0)).isEqualTo(converter1);
assertThat(converters.getConverters().get(1)).isEqualTo(converter2);
}
@Test
void convertersAreAddedToFormPartConverter() {
HttpMessageConverter<?> converter1 = mock(HttpMessageConverter.class);
HttpMessageConverter<?> converter2 = mock(HttpMessageConverter.class);
List<HttpMessageConverter<?>> converters = new HttpMessageConverters(converter1, converter2).getConverters();
List<HttpMessageConverter<?>> partConverters = extractFormPartConverters(converters);
assertThat(partConverters.get(0)).isEqualTo(converter1);
assertThat(partConverters.get(1)).isEqualTo(converter2);
}
@Test
void postProcessConverters() {
HttpMessageConverters converters = new HttpMessageConverters() {
@Override
protected List<HttpMessageConverter<?>> postProcessConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(MappingJackson2XmlHttpMessageConverter.class::isInstance);
return converters;
}
};
List<Class<?>> converterClasses = new ArrayList<>();
for (HttpMessageConverter<?> converter : converters) {
converterClasses.add(converter.getClass());
}
assertThat(converterClasses).containsExactly(ByteArrayHttpMessageConverter.class,
StringHttpMessageConverter.class, ResourceHttpMessageConverter.class,
ResourceRegionHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class,
MappingJackson2HttpMessageConverter.class, MappingJackson2CborHttpMessageConverter.class);
}
@Test
void postProcessPartConverters() {
HttpMessageConverters converters = new HttpMessageConverters() {
@Override
protected List<HttpMessageConverter<?>> postProcessPartConverters(
List<HttpMessageConverter<?>> converters) {
converters.removeIf(MappingJackson2XmlHttpMessageConverter.class::isInstance);
return converters;
}
};
List<Class<?>> converterClasses = new ArrayList<>();
for (HttpMessageConverter<?> converter : extractFormPartConverters(converters.getConverters())) {
converterClasses.add(converter.getClass());
}
assertThat(converterClasses).containsExactly(ByteArrayHttpMessageConverter.class,
StringHttpMessageConverter.class, ResourceHttpMessageConverter.class,
MappingJackson2HttpMessageConverter.class, MappingJackson2CborHttpMessageConverter.class);
}
private List<HttpMessageConverter<?>> extractFormPartConverters(List<HttpMessageConverter<?>> converters) {
AllEncompassingFormHttpMessageConverter formConverter = findFormConverter(converters);
return formConverter.getPartConverters();
}
private AllEncompassingFormHttpMessageConverter findFormConverter(Collection<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof AllEncompassingFormHttpMessageConverter allEncompassingConverter) {
return allEncompassingConverter;
}
}
return null;
}
}