Create spring-boot-http module
This commit is contained in:
committed by
Phillip Webb
parent
31c9d3c17a
commit
e83f4eed4e
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Background Preinitializers
|
||||
org.springframework.boot.autoconfigure.preinitialize.BackgroundPreinitializer=\
|
||||
org.springframework.boot.http.autoconfigure.MessageConverterBackgroundPreinitializer
|
||||
@@ -0,0 +1 @@
|
||||
org.springframework.boot.http.autoconfigure.HttpMessageConvertersAutoConfiguration
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user