mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-09-17 12:09:16 +00:00
Separate client and server HttpMessageConverters
Prior to this commit, the `HttpMessageConverters` auto-configuration would pick up `HttpMessageConverter<?>` beans from the context and broadly apply them to both server and client converters setup. This can cause several types of problems. First, specific configurations only meant for server setup will also be applied to the client side. For example, the Actuator JSOn configuration is only meant to be applied to the server infrastructure. Also, picking up converters from the context does not convey whether such converters are meant to override the default ones or should be configured as custom, in addition to the defaults. For example, a bean extending `JacksonJsonHttpMessageConverter` can be both meant to override the default with `builder.withJsonConverter` or meant as an additional converter with `builder.addCustomConverter`. This commit ensures that the auto-configurations contribute `ClientHttpMessageConvertersCustomizer` and `ServerHttpMessageConvertersCustomizer` beans instead of converter beans directly. Applications can still contribute such beans and those will be used. Fixes gh-48310
This commit is contained in:
+15
-8
@@ -40,6 +40,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.boot.graphql.autoconfigure.GraphQlAutoConfiguration;
|
||||
import org.springframework.boot.graphql.autoconfigure.GraphQlCorsProperties;
|
||||
import org.springframework.boot.graphql.autoconfigure.GraphQlProperties;
|
||||
import org.springframework.boot.http.converter.autoconfigure.ServerHttpMessageConvertersCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||
@@ -60,6 +61,8 @@ import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverters;
|
||||
import org.springframework.http.converter.HttpMessageConverters.ServerBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
@@ -183,17 +186,21 @@ public final class GraphQlWebMvcAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
GraphQlWebSocketHandler graphQlWebSocketHandler(WebGraphQlHandler webGraphQlHandler,
|
||||
GraphQlProperties properties, ObjectProvider<HttpMessageConverter<?>> converters) {
|
||||
return new GraphQlWebSocketHandler(webGraphQlHandler, getJsonConverter(converters),
|
||||
GraphQlProperties properties, ObjectProvider<ServerHttpMessageConvertersCustomizer> customizers) {
|
||||
return new GraphQlWebSocketHandler(webGraphQlHandler, getJsonConverter(customizers),
|
||||
properties.getWebsocket().getConnectionInitTimeout(), properties.getWebsocket().getKeepAlive());
|
||||
}
|
||||
|
||||
private HttpMessageConverter<Object> getJsonConverter(ObjectProvider<HttpMessageConverter<?>> converters) {
|
||||
return converters.orderedStream()
|
||||
.filter(this::canReadJsonMap)
|
||||
.findFirst()
|
||||
.map(this::asObjectHttpMessageConverter)
|
||||
.orElseThrow(() -> new IllegalStateException("No JSON converter"));
|
||||
private HttpMessageConverter<Object> getJsonConverter(
|
||||
ObjectProvider<ServerHttpMessageConvertersCustomizer> customizers) {
|
||||
ServerBuilder serverBuilder = HttpMessageConverters.forServer().registerDefaults();
|
||||
customizers.forEach((customizer) -> customizer.customize(serverBuilder));
|
||||
for (HttpMessageConverter<?> converter : serverBuilder.build()) {
|
||||
if (canReadJsonMap(converter)) {
|
||||
return asObjectHttpMessageConverter(converter);
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("No JSON converter");
|
||||
}
|
||||
|
||||
private boolean canReadJsonMap(HttpMessageConverter<?> candidate) {
|
||||
|
||||
+1
-21
@@ -20,10 +20,8 @@ import java.util.Collection;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverters.ClientBuilder;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@@ -48,18 +46,9 @@ class DefaultClientHttpMessageConvertersCustomizer implements ClientHttpMessageC
|
||||
else {
|
||||
builder.registerDefaults();
|
||||
this.converters.forEach((converter) -> {
|
||||
if (converter instanceof StringHttpMessageConverter) {
|
||||
builder.withStringConverter(converter);
|
||||
}
|
||||
else if (converter instanceof KotlinSerializationJsonHttpMessageConverter) {
|
||||
if (converter instanceof KotlinSerializationJsonHttpMessageConverter) {
|
||||
builder.withKotlinSerializationJsonConverter(converter);
|
||||
}
|
||||
else if (supportsMediaType(converter, MediaType.APPLICATION_JSON)) {
|
||||
builder.withJsonConverter(converter);
|
||||
}
|
||||
else if (supportsMediaType(converter, MediaType.APPLICATION_XML)) {
|
||||
builder.withXmlConverter(converter);
|
||||
}
|
||||
else {
|
||||
builder.addCustomConverter(converter);
|
||||
}
|
||||
@@ -67,13 +56,4 @@ class DefaultClientHttpMessageConvertersCustomizer implements ClientHttpMessageC
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean supportsMediaType(HttpMessageConverter<?> converter, MediaType mediaType) {
|
||||
for (MediaType supportedMediaType : converter.getSupportedMediaTypes()) {
|
||||
if (supportedMediaType.equalsTypeAndSubtype(mediaType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-21
@@ -20,10 +20,8 @@ import java.util.Collection;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverters.ServerBuilder;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@@ -48,18 +46,9 @@ class DefaultServerHttpMessageConvertersCustomizer implements ServerHttpMessageC
|
||||
else {
|
||||
builder.registerDefaults();
|
||||
this.converters.forEach((converter) -> {
|
||||
if (converter instanceof StringHttpMessageConverter) {
|
||||
builder.withStringConverter(converter);
|
||||
}
|
||||
else if (converter instanceof KotlinSerializationJsonHttpMessageConverter) {
|
||||
if (converter instanceof KotlinSerializationJsonHttpMessageConverter) {
|
||||
builder.withKotlinSerializationJsonConverter(converter);
|
||||
}
|
||||
else if (supportsMediaType(converter, MediaType.APPLICATION_JSON)) {
|
||||
builder.withJsonConverter(converter);
|
||||
}
|
||||
else if (supportsMediaType(converter, MediaType.APPLICATION_XML)) {
|
||||
builder.withXmlConverter(converter);
|
||||
}
|
||||
else {
|
||||
builder.addCustomConverter(converter);
|
||||
}
|
||||
@@ -67,13 +56,4 @@ class DefaultServerHttpMessageConvertersCustomizer implements ServerHttpMessageC
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean supportsMediaType(HttpMessageConverter<?> converter, MediaType mediaType) {
|
||||
for (MediaType supportedMediaType : converter.getSupportedMediaTypes()) {
|
||||
if (supportedMediaType.equalsTypeAndSubtype(mediaType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+29
-8
@@ -27,14 +27,16 @@ 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.HttpMessageConverters.ClientBuilder;
|
||||
import org.springframework.http.converter.HttpMessageConverters.ServerBuilder;
|
||||
import org.springframework.http.converter.json.GsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
|
||||
|
||||
/**
|
||||
* Configuration for HTTP Message converters that use Gson.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Eddú Meléndez
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(Gson.class)
|
||||
@@ -46,11 +48,30 @@ class GsonHttpMessageConvertersConfiguration {
|
||||
static class GsonHttpMessageConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
GsonHttpMessageConverter gsonHttpMessageConverter(Gson gson) {
|
||||
GsonHttpMessageConverter converter = new GsonHttpMessageConverter();
|
||||
converter.setGson(gson);
|
||||
return converter;
|
||||
@ConditionalOnMissingBean(GsonHttpMessageConverter.class)
|
||||
GsonHttpConvertersCustomizer gsonHttpMessageConvertersCustomizer(Gson gson) {
|
||||
return new GsonHttpConvertersCustomizer(gson);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class GsonHttpConvertersCustomizer
|
||||
implements ClientHttpMessageConvertersCustomizer, ServerHttpMessageConvertersCustomizer {
|
||||
|
||||
private final GsonHttpMessageConverter converter;
|
||||
|
||||
GsonHttpConvertersCustomizer(Gson gson) {
|
||||
this.converter = new GsonHttpMessageConverter(gson);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ClientBuilder builder) {
|
||||
builder.withJsonConverter(this.converter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ServerBuilder builder) {
|
||||
builder.withJsonConverter(this.converter);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -80,13 +101,13 @@ class GsonHttpMessageConvertersConfiguration {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnBean(JacksonJsonHttpMessageConverter.class)
|
||||
@ConditionalOnBean(JacksonHttpMessageConvertersConfiguration.JacksonJsonHttpMessageConvertersCustomizer.class)
|
||||
static class JacksonAvailable {
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
@ConditionalOnBean(org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class)
|
||||
@ConditionalOnBean(Jackson2HttpMessageConvertersConfiguration.Jackson2JsonMessageConvertersCustomizer.class)
|
||||
static class Jackson2Available {
|
||||
|
||||
}
|
||||
|
||||
+28
-7
@@ -32,6 +32,8 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverters.ClientBuilder;
|
||||
import org.springframework.http.converter.HttpMessageConverters.ServerBuilder;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
|
||||
/**
|
||||
@@ -86,17 +88,36 @@ public final class HttpMessageConvertersAutoConfiguration {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(StringHttpMessageConverter.class)
|
||||
@EnableConfigurationProperties(HttpMessageConvertersProperties.class)
|
||||
protected static class StringHttpMessageConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
StringHttpMessageConverter stringHttpMessageConverter(HttpMessageConvertersProperties properties) {
|
||||
StringHttpMessageConverter converter = new StringHttpMessageConverter(
|
||||
properties.getStringEncodingCharset());
|
||||
converter.setWriteAcceptCharset(false);
|
||||
return converter;
|
||||
@ConditionalOnMissingBean(StringHttpMessageConverter.class)
|
||||
StringHttpMessageConvertersCustomizer stringHttpMessageConvertersCustomizer(
|
||||
HttpMessageConvertersProperties properties) {
|
||||
return new StringHttpMessageConvertersCustomizer(properties);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class StringHttpMessageConvertersCustomizer
|
||||
implements ClientHttpMessageConvertersCustomizer, ServerHttpMessageConvertersCustomizer {
|
||||
|
||||
StringHttpMessageConverter converter;
|
||||
|
||||
StringHttpMessageConvertersCustomizer(HttpMessageConvertersProperties properties) {
|
||||
this.converter = new StringHttpMessageConverter(properties.getStringEncodingCharset());
|
||||
this.converter.setWriteAcceptCharset(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ClientBuilder builder) {
|
||||
builder.withStringConverter(this.converter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ServerBuilder builder) {
|
||||
builder.withStringConverter(this.converter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+58
-11
@@ -24,22 +24,24 @@ 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.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration.JacksonJsonHttpMessageConvertersCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.converter.HttpMessageConverters.ClientBuilder;
|
||||
import org.springframework.http.converter.HttpMessageConverters.ServerBuilder;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
|
||||
|
||||
/**
|
||||
* Configuration for HTTP message converters that use Jackson 2.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Brian Clozel
|
||||
* @deprecated since 4.0.0 for removal in 4.2.0 in favor of Jackson 3.
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@Deprecated(since = "4.0.0", forRemoval = true)
|
||||
@SuppressWarnings({ "deprecation", "removal" })
|
||||
@SuppressWarnings("removal")
|
||||
class Jackson2HttpMessageConvertersConfiguration {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@@ -49,10 +51,9 @@ class Jackson2HttpMessageConvertersConfiguration {
|
||||
static class MappingJackson2HttpMessageConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(
|
||||
ObjectMapper objectMapper) {
|
||||
return new org.springframework.http.converter.json.MappingJackson2HttpMessageConverter(objectMapper);
|
||||
@ConditionalOnMissingBean(org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class)
|
||||
Jackson2JsonMessageConvertersCustomizer jackson2HttpMessageConvertersCustomizer(ObjectMapper objectMapper) {
|
||||
return new Jackson2JsonMessageConvertersCustomizer(objectMapper);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -63,10 +64,56 @@ class Jackson2HttpMessageConvertersConfiguration {
|
||||
protected static class MappingJackson2XmlHttpMessageConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter(
|
||||
@ConditionalOnMissingBean(org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter.class)
|
||||
Jackson2XmlMessageConvertersCustomizer mappingJackson2XmlHttpMessageConverter(
|
||||
Jackson2ObjectMapperBuilder builder) {
|
||||
return new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build());
|
||||
return new Jackson2XmlMessageConvertersCustomizer(builder.createXmlMapper(true).build());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Jackson2JsonMessageConvertersCustomizer
|
||||
implements ClientHttpMessageConvertersCustomizer, ServerHttpMessageConvertersCustomizer {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
Jackson2JsonMessageConvertersCustomizer(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ClientBuilder builder) {
|
||||
builder.withJsonConverter(
|
||||
new org.springframework.http.converter.json.MappingJackson2HttpMessageConverter(this.objectMapper));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ServerBuilder builder) {
|
||||
builder.withJsonConverter(
|
||||
new org.springframework.http.converter.json.MappingJackson2HttpMessageConverter(this.objectMapper));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class Jackson2XmlMessageConvertersCustomizer
|
||||
implements ClientHttpMessageConvertersCustomizer, ServerHttpMessageConvertersCustomizer {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
Jackson2XmlMessageConvertersCustomizer(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ClientBuilder builder) {
|
||||
builder.withXmlConverter(new org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter(
|
||||
this.objectMapper));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ServerBuilder builder) {
|
||||
builder.withXmlConverter(new org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter(
|
||||
this.objectMapper));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -83,7 +130,7 @@ class Jackson2HttpMessageConvertersConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnMissingBean(JacksonJsonHttpMessageConverter.class)
|
||||
@ConditionalOnMissingBean(JacksonJsonHttpMessageConvertersCustomizer.class)
|
||||
static class JacksonUnavailable {
|
||||
|
||||
}
|
||||
|
||||
+51
-6
@@ -25,6 +25,8 @@ 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.HttpMessageConverters.ClientBuilder;
|
||||
import org.springframework.http.converter.HttpMessageConverters.ServerBuilder;
|
||||
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
|
||||
|
||||
@@ -32,6 +34,7 @@ import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
|
||||
* Configuration for HTTP message converters that use Jackson.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
class JacksonHttpMessageConvertersConfiguration {
|
||||
@@ -44,11 +47,11 @@ class JacksonHttpMessageConvertersConfiguration {
|
||||
static class JacksonJsonHttpMessageConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(
|
||||
@ConditionalOnMissingBean(value = JacksonJsonHttpMessageConverter.class,
|
||||
ignoredType = { "org.springframework.hateoas.server.mvc.TypeConstrainedJacksonJsonHttpMessageConverter",
|
||||
"org.springframework.data.rest.webmvc.alps.AlpsJacksonJsonHttpMessageConverter" })
|
||||
JacksonJsonHttpMessageConverter jacksonJsonHttpMessageConverter(JsonMapper jsonMapper) {
|
||||
return new JacksonJsonHttpMessageConverter(jsonMapper);
|
||||
JacksonJsonHttpMessageConvertersCustomizer jacksonJsonHttpMessageConvertersCustomizer(JsonMapper jsonMapper) {
|
||||
return new JacksonJsonHttpMessageConvertersCustomizer(jsonMapper);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -59,9 +62,51 @@ class JacksonHttpMessageConvertersConfiguration {
|
||||
protected static class JacksonXmlHttpMessageConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public JacksonXmlHttpMessageConverter jacksonXmlHttpMessageConverter(XmlMapper xmlMapper) {
|
||||
return new JacksonXmlHttpMessageConverter(xmlMapper);
|
||||
@ConditionalOnMissingBean(JacksonXmlHttpMessageConverter.class)
|
||||
JacksonXmlHttpMessageConvertersCustomizer jacksonXmlHttpMessageConvertersCustomizer(XmlMapper xmlMapper) {
|
||||
return new JacksonXmlHttpMessageConvertersCustomizer(xmlMapper);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class JacksonJsonHttpMessageConvertersCustomizer
|
||||
implements ClientHttpMessageConvertersCustomizer, ServerHttpMessageConvertersCustomizer {
|
||||
|
||||
private final JsonMapper jsonMapper;
|
||||
|
||||
JacksonJsonHttpMessageConvertersCustomizer(JsonMapper jsonMapper) {
|
||||
this.jsonMapper = jsonMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ClientBuilder builder) {
|
||||
builder.withJsonConverter(new JacksonJsonHttpMessageConverter(this.jsonMapper));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ServerBuilder builder) {
|
||||
builder.withJsonConverter(new JacksonJsonHttpMessageConverter(this.jsonMapper));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class JacksonXmlHttpMessageConvertersCustomizer
|
||||
implements ClientHttpMessageConvertersCustomizer, ServerHttpMessageConvertersCustomizer {
|
||||
|
||||
private final XmlMapper xmlMapper;
|
||||
|
||||
JacksonXmlHttpMessageConvertersCustomizer(XmlMapper xmlMapper) {
|
||||
this.xmlMapper = xmlMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ClientBuilder builder) {
|
||||
builder.withXmlConverter(new JacksonXmlHttpMessageConverter(this.xmlMapper));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ServerBuilder builder) {
|
||||
builder.withXmlConverter(new JacksonXmlHttpMessageConverter(this.xmlMapper));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+31
-10
@@ -23,11 +23,13 @@ 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.http.converter.autoconfigure.GsonHttpMessageConvertersConfiguration.GsonHttpConvertersCustomizer;
|
||||
import org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration.JacksonJsonHttpMessageConvertersCustomizer;
|
||||
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.JacksonJsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverters.ClientBuilder;
|
||||
import org.springframework.http.converter.HttpMessageConverters.ServerBuilder;
|
||||
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
|
||||
|
||||
/**
|
||||
@@ -45,11 +47,30 @@ class JsonbHttpMessageConvertersConfiguration {
|
||||
static class JsonbHttpMessageConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
JsonbHttpMessageConverter jsonbHttpMessageConverter(Jsonb jsonb) {
|
||||
JsonbHttpMessageConverter converter = new JsonbHttpMessageConverter();
|
||||
converter.setJsonb(jsonb);
|
||||
return converter;
|
||||
@ConditionalOnMissingBean(JsonbHttpMessageConverter.class)
|
||||
JsonbHttpMessageConvertersCustomizer jsonbHttpMessageConvertersCustomizer(Jsonb jsonb) {
|
||||
return new JsonbHttpMessageConvertersCustomizer(jsonb);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class JsonbHttpMessageConvertersCustomizer
|
||||
implements ClientHttpMessageConvertersCustomizer, ServerHttpMessageConvertersCustomizer {
|
||||
|
||||
private final JsonbHttpMessageConverter converter;
|
||||
|
||||
JsonbHttpMessageConvertersCustomizer(Jsonb jsonb) {
|
||||
this.converter = new JsonbHttpMessageConverter(jsonb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ClientBuilder builder) {
|
||||
builder.withJsonConverter(this.converter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ServerBuilder builder) {
|
||||
builder.withJsonConverter(this.converter);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -67,9 +88,9 @@ class JsonbHttpMessageConvertersConfiguration {
|
||||
}
|
||||
|
||||
@SuppressWarnings("removal")
|
||||
@ConditionalOnMissingBean({ JacksonJsonHttpMessageConverter.class,
|
||||
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class,
|
||||
GsonHttpMessageConverter.class })
|
||||
@ConditionalOnMissingBean({ JacksonJsonHttpMessageConvertersCustomizer.class,
|
||||
Jackson2HttpMessageConvertersConfiguration.Jackson2JsonMessageConvertersCustomizer.class,
|
||||
GsonHttpConvertersCustomizer.class })
|
||||
static class JacksonAndGsonMissing {
|
||||
|
||||
}
|
||||
|
||||
+30
-17
@@ -19,15 +19,16 @@ package org.springframework.boot.http.converter.autoconfigure;
|
||||
import kotlinx.serialization.Serializable;
|
||||
import kotlinx.serialization.json.Json;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.http.converter.HttpMessageConverters.ClientBuilder;
|
||||
import org.springframework.http.converter.HttpMessageConverters.ServerBuilder;
|
||||
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Configuration for HTTP message converters that use Kotlin Serialization.
|
||||
@@ -41,24 +42,36 @@ import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessag
|
||||
class KotlinSerializationHttpMessageConvertersConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
KotlinSerializationJsonHttpMessageConverter kotlinSerializationJsonHttpMessageConverter(Json json,
|
||||
ObjectProvider<HttpMessageConverter<?>> converters) {
|
||||
return supportsApplicationJson(converters) ? new KotlinSerializationJsonHttpMessageConverter(json)
|
||||
: new KotlinSerializationJsonHttpMessageConverter(json, (type) -> true);
|
||||
@ConditionalOnMissingBean(KotlinSerializationJsonHttpMessageConverter.class)
|
||||
KotlinSerializationJsonConvertersCustomizer kotlinSerializationJsonConvertersCustomizer(Json json,
|
||||
ResourceLoader resourceLoader) {
|
||||
return new KotlinSerializationJsonConvertersCustomizer(json, resourceLoader);
|
||||
}
|
||||
|
||||
private boolean supportsApplicationJson(ObjectProvider<HttpMessageConverter<?>> converters) {
|
||||
return converters.orderedStream().filter(this::supportsApplicationJson).findFirst().isPresent();
|
||||
}
|
||||
static class KotlinSerializationJsonConvertersCustomizer
|
||||
implements ClientHttpMessageConvertersCustomizer, ServerHttpMessageConvertersCustomizer {
|
||||
|
||||
private boolean supportsApplicationJson(HttpMessageConverter<?> converter) {
|
||||
for (MediaType mediaType : converter.getSupportedMediaTypes()) {
|
||||
if (!mediaType.equals(MediaType.ALL) && mediaType.isCompatibleWith(MediaType.APPLICATION_JSON)) {
|
||||
return true;
|
||||
}
|
||||
private final KotlinSerializationJsonHttpMessageConverter converter;
|
||||
|
||||
KotlinSerializationJsonConvertersCustomizer(Json json, ResourceLoader resourceLoader) {
|
||||
ClassLoader classLoader = resourceLoader.getClassLoader();
|
||||
boolean hasAnyJsonSupport = ClassUtils.isPresent("tools.jackson.databind.json.JsonMapper", classLoader)
|
||||
|| ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader)
|
||||
|| ClassUtils.isPresent("com.google.gson.Gson", classLoader);
|
||||
this.converter = hasAnyJsonSupport ? new KotlinSerializationJsonHttpMessageConverter(json)
|
||||
: new KotlinSerializationJsonHttpMessageConverter(json, (type) -> true);
|
||||
}
|
||||
return false;
|
||||
|
||||
@Override
|
||||
public void customize(ClientBuilder builder) {
|
||||
builder.withKotlinSerializationJsonConverter(this.converter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(ServerBuilder builder) {
|
||||
builder.withKotlinSerializationJsonConverter(this.converter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+173
-127
@@ -17,6 +17,7 @@
|
||||
package org.springframework.boot.http.converter.autoconfigure;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -29,23 +30,27 @@ import org.junit.jupiter.api.Test;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
import tools.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
|
||||
import org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration.JacksonJsonHttpMessageConverterConfiguration;
|
||||
import org.springframework.boot.http.converter.autoconfigure.GsonHttpMessageConvertersConfiguration.GsonHttpConvertersCustomizer;
|
||||
import org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration.JacksonJsonHttpMessageConvertersCustomizer;
|
||||
import org.springframework.boot.http.converter.autoconfigure.JacksonHttpMessageConvertersConfiguration.JacksonXmlHttpMessageConvertersCustomizer;
|
||||
import org.springframework.boot.http.converter.autoconfigure.JsonbHttpMessageConvertersConfiguration.JsonbHttpMessageConvertersCustomizer;
|
||||
import org.springframework.boot.http.converter.autoconfigure.KotlinSerializationHttpMessageConvertersConfiguration.KotlinSerializationJsonConvertersCustomizer;
|
||||
import org.springframework.boot.logging.LogLevel;
|
||||
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.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.data.rest.webmvc.alps.AlpsJacksonJsonHttpMessageConverter;
|
||||
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
|
||||
import org.springframework.hateoas.RepresentationModel;
|
||||
import org.springframework.hateoas.mediatype.hal.forms.HalFormsHttpMessageConverter;
|
||||
import org.springframework.hateoas.server.mvc.TypeConstrainedJacksonJsonHttpMessageConverter;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
@@ -73,6 +78,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Moritz Halbritter
|
||||
* @author Sebastien Deleuze
|
||||
* @author Dmitry Sulman
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class HttpMessageConvertersAutoConfigurationTests {
|
||||
|
||||
@@ -81,35 +87,59 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
void jacksonNotAvailable() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(JsonMapper.class);
|
||||
assertThat(context).doesNotHaveBean(JacksonJsonHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(JacksonXmlHttpMessageConverter.class);
|
||||
});
|
||||
this.contextRunner.withClassLoader(new FilteredClassLoader(JsonMapper.class.getPackage().getName()))
|
||||
.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(JsonMapper.class);
|
||||
assertThat(context).doesNotHaveBean(JacksonJsonHttpMessageConvertersCustomizer.class);
|
||||
assertThat(context).doesNotHaveBean(JacksonXmlHttpMessageConvertersCustomizer.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void jacksonDefaultConverter() {
|
||||
this.contextRunner.withUserConfiguration(JacksonJsonMapperConfig.class)
|
||||
.run(assertConverter(JacksonJsonHttpMessageConverter.class, "jacksonJsonHttpMessageConverter"));
|
||||
this.contextRunner.withUserConfiguration(JacksonJsonMapperConfig.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(JacksonJsonHttpMessageConvertersCustomizer.class);
|
||||
assertConverterIsRegistered(context, JacksonJsonHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void jacksonConverterWithBuilder() {
|
||||
this.contextRunner.withUserConfiguration(JacksonJsonMapperBuilderConfig.class)
|
||||
.run(assertConverter(JacksonJsonHttpMessageConverter.class, "jacksonJsonHttpMessageConverter"));
|
||||
this.contextRunner.withUserConfiguration(JacksonJsonMapperBuilderConfig.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(JacksonJsonHttpMessageConvertersCustomizer.class);
|
||||
assertConverterIsRegistered(context, JacksonJsonHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void jacksonXmlConverterWithBuilder() {
|
||||
this.contextRunner.withUserConfiguration(JacksonXmlMapperBuilderConfig.class)
|
||||
.run(assertConverter(JacksonXmlHttpMessageConverter.class, "jacksonXmlHttpMessageConverter"));
|
||||
this.contextRunner.withUserConfiguration(JacksonXmlMapperBuilderConfig.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(JacksonXmlHttpMessageConvertersCustomizer.class);
|
||||
assertConverterIsRegistered(context, JacksonXmlHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void jacksonCustomConverter() {
|
||||
this.contextRunner.withUserConfiguration(JacksonJsonMapperConfig.class, JacksonConverterConfig.class)
|
||||
.run(assertConverter(JacksonJsonHttpMessageConverter.class, "customJacksonMessageConverter"));
|
||||
.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(JacksonJsonHttpMessageConvertersCustomizer.class);
|
||||
HttpMessageConverters serverConverters = getServerConverters(context);
|
||||
assertThat(serverConverters)
|
||||
.contains(context.getBean("customJacksonMessageConverter", JacksonJsonHttpMessageConverter.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void jacksonServerAndClientConvertersShouldBeDifferent() {
|
||||
this.contextRunner.withUserConfiguration(JacksonJsonMapperConfig.class).run((context) -> {
|
||||
assertThat(context).hasSingleBean(JacksonJsonHttpMessageConvertersCustomizer.class);
|
||||
JacksonJsonHttpMessageConverter serverConverter = findConverter(getServerConverters(context),
|
||||
JacksonJsonHttpMessageConverter.class);
|
||||
JacksonJsonHttpMessageConverter clientConverter = findConverter(getClientConverters(context),
|
||||
JacksonJsonHttpMessageConverter.class);
|
||||
assertThat(serverConverter).isNotEqualTo(clientConverter);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -118,8 +148,8 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
void jackson2DefaultConverter() {
|
||||
this.contextRunner.withUserConfiguration(Jackson2ObjectMapperConfig.class)
|
||||
.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
|
||||
.run(assertConverter(org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class,
|
||||
"mappingJackson2HttpMessageConverter"));
|
||||
.run((context) -> assertConverterIsRegistered(context,
|
||||
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -127,8 +157,8 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
@SuppressWarnings("removal")
|
||||
void jackson2ConverterWithBuilder() {
|
||||
this.contextRunner.withUserConfiguration(Jackson2ObjectMapperBuilderConfig.class)
|
||||
.run(assertConverter(org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class,
|
||||
"mappingJackson2HttpMessageConverter"));
|
||||
.run((context) -> assertConverterIsRegistered(context,
|
||||
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -136,38 +166,55 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
@SuppressWarnings("removal")
|
||||
void jackson2CustomConverter() {
|
||||
this.contextRunner.withUserConfiguration(Jackson2ObjectMapperConfig.class, Jackson2ConverterConfig.class)
|
||||
.run(assertConverter(org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class,
|
||||
"customJacksonMessageConverter"));
|
||||
.run((context) -> assertConverterIsRegistered(context,
|
||||
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Deprecated(since = "4.0.0", forRemoval = true)
|
||||
@SuppressWarnings("removal")
|
||||
void jackson2ServerAndClientConvertersShouldBeDifferent() {
|
||||
this.contextRunner.withUserConfiguration(Jackson2ObjectMapperConfig.class)
|
||||
.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(
|
||||
Jackson2HttpMessageConvertersConfiguration.Jackson2JsonMessageConvertersCustomizer.class);
|
||||
HttpMessageConverter<?> serverConverter = findConverter(getServerConverters(context),
|
||||
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class);
|
||||
HttpMessageConverter<?> clientConverter = findConverter(getClientConverters(context),
|
||||
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class);
|
||||
assertThat(serverConverter).isNotEqualTo(clientConverter);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void gsonNotAvailable() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(Gson.class);
|
||||
assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class);
|
||||
assertConverterIsNotRegistered(context, GsonHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void gsonDefaultConverter() {
|
||||
this.contextRunner.withBean(Gson.class)
|
||||
.run(assertConverter(GsonHttpMessageConverter.class, "gsonHttpMessageConverter"));
|
||||
.run((context) -> assertConverterIsRegistered(context, GsonHttpMessageConverter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void gsonCustomConverter() {
|
||||
this.contextRunner.withUserConfiguration(GsonConverterConfig.class)
|
||||
.withBean(Gson.class)
|
||||
.run(assertConverter(GsonHttpMessageConverter.class, "customGsonMessageConverter"));
|
||||
.run((context) -> assertThat(getServerConverters(context))
|
||||
.contains(context.getBean("customGsonMessageConverter", GsonHttpMessageConverter.class)));
|
||||
}
|
||||
|
||||
@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(JacksonJsonHttpMessageConverter.class);
|
||||
assertConverterIsRegistered(context, GsonHttpMessageConverter.class);
|
||||
assertConverterIsNotRegistered(context, JsonbHttpMessageConverter.class);
|
||||
assertConverterIsNotRegistered(context, JacksonJsonHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -175,30 +222,30 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
void jsonbNotAvailable() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(Jsonb.class);
|
||||
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
|
||||
assertConverterIsNotRegistered(context, JsonbHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonbDefaultConverter() {
|
||||
this.contextRunner.withBean(Jsonb.class, JsonbBuilder::create)
|
||||
.run(assertConverter(JsonbHttpMessageConverter.class, "jsonbHttpMessageConverter"));
|
||||
.run((context) -> assertConverterIsRegistered(context, JsonbHttpMessageConverter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonbCustomConverter() {
|
||||
this.contextRunner.withUserConfiguration(JsonbConverterConfig.class)
|
||||
.withBean(Jsonb.class, JsonbBuilder::create)
|
||||
.run(assertConverter(JsonbHttpMessageConverter.class, "customJsonbMessageConverter"));
|
||||
.run((context) -> assertThat(getServerConverters(context))
|
||||
.contains(context.getBean("customJsonbMessageConverter", JsonbHttpMessageConverter.class)));
|
||||
}
|
||||
|
||||
@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(JacksonJsonHttpMessageConverter.class);
|
||||
assertConverterIsRegistered(context, JsonbHttpMessageConverter.class);
|
||||
assertConverterIsNotRegistered(context, GsonHttpMessageConverter.class);
|
||||
assertConverterIsNotRegistered(context, JacksonJsonHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -206,7 +253,7 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
void kotlinSerializationNotAvailable() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(Json.class);
|
||||
assertThat(context).doesNotHaveBean(KotlinSerializationJsonHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(KotlinSerializationJsonConvertersCustomizer.class);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -214,18 +261,14 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
void kotlinSerializationCustomConverter() {
|
||||
this.contextRunner.withUserConfiguration(KotlinSerializationConverterConfig.class)
|
||||
.withBean(Json.class, () -> Json.Default)
|
||||
.run(assertConverter(KotlinSerializationJsonHttpMessageConverter.class,
|
||||
"customKotlinSerializationJsonHttpMessageConverter"));
|
||||
.run((context) -> assertConverterIsRegistered(context, KotlinSerializationJsonHttpMessageConverter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void kotlinSerializationOrderedAheadOfJsonConverter() {
|
||||
allOptionsRunner().run((context) -> {
|
||||
assertConverterBeanExists(context, KotlinSerializationJsonHttpMessageConverter.class,
|
||||
"kotlinSerializationJsonHttpMessageConverter");
|
||||
assertConverterBeanRegisteredWithHttpMessageConverters(context,
|
||||
KotlinSerializationJsonHttpMessageConverter.class);
|
||||
assertConvertersBeanRegisteredWithHttpMessageConverters(context,
|
||||
assertConverterIsRegistered(context, KotlinSerializationJsonHttpMessageConverter.class);
|
||||
assertConvertersRegisteredWithHttpMessageConverters(context,
|
||||
List.of(KotlinSerializationJsonHttpMessageConverter.class, JacksonJsonHttpMessageConverter.class));
|
||||
});
|
||||
}
|
||||
@@ -233,30 +276,27 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
@Test
|
||||
void kotlinSerializationUsesLimitedPredicateWhenOtherJsonConverterIsAvailable() {
|
||||
allOptionsRunner().run((context) -> {
|
||||
KotlinSerializationJsonHttpMessageConverter converter = context
|
||||
.getBean(KotlinSerializationJsonHttpMessageConverter.class);
|
||||
KotlinSerializationJsonHttpMessageConverter converter = findConverter(getServerConverters(context),
|
||||
KotlinSerializationJsonHttpMessageConverter.class);
|
||||
assertThat(converter.canWrite(Map.class, MediaType.APPLICATION_JSON)).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void kotlinSerializationUsesUnrestrictedPredicateWhenNoOtherJsonConverterIsAvailable() {
|
||||
this.contextRunner.withBean(Json.class, () -> Json.Default).run((context) -> {
|
||||
KotlinSerializationJsonHttpMessageConverter converter = context
|
||||
.getBean(KotlinSerializationJsonHttpMessageConverter.class);
|
||||
assertThat(converter.canWrite(Map.class, MediaType.APPLICATION_JSON)).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void stringDefaultConverter() {
|
||||
this.contextRunner.run(assertConverter(StringHttpMessageConverter.class, "stringHttpMessageConverter"));
|
||||
this.contextRunner.run((context) -> assertConverterIsRegistered(context, StringHttpMessageConverter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stringCustomConverter() {
|
||||
this.contextRunner.withUserConfiguration(StringConverterConfig.class)
|
||||
.run(assertConverter(StringHttpMessageConverter.class, "customStringMessageConverter"));
|
||||
this.contextRunner.withUserConfiguration(StringConverterConfig.class).run((context) -> {
|
||||
assertThat(getClientConverters(context))
|
||||
.filteredOn((converter) -> converter instanceof StringHttpMessageConverter)
|
||||
.hasSize(2);
|
||||
assertThat(getServerConverters(context))
|
||||
.filteredOn((converter) -> converter instanceof StringHttpMessageConverter)
|
||||
.hasSize(2);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -264,10 +304,9 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(JacksonJsonMapperBuilderConfig.class, TypeConstrainedConverterConfiguration.class)
|
||||
.run((context) -> {
|
||||
BeanDefinition beanDefinition = ((GenericApplicationContext) context.getSourceApplicationContext())
|
||||
.getBeanDefinition("jacksonJsonHttpMessageConverter");
|
||||
assertThat(beanDefinition.getFactoryBeanName())
|
||||
.isEqualTo(JacksonJsonHttpMessageConverterConfiguration.class.getName());
|
||||
assertThat(context).hasSingleBean(JacksonJsonHttpMessageConvertersCustomizer.class);
|
||||
assertConvertersRegisteredWithHttpMessageConverters(context, List
|
||||
.of(TypeConstrainedJacksonJsonHttpMessageConverter.class, JacksonJsonHttpMessageConverter.class));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -276,21 +315,20 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
this.contextRunner
|
||||
.withUserConfiguration(JacksonJsonMapperBuilderConfig.class, RepositoryRestMvcConfiguration.class)
|
||||
.run((context) -> {
|
||||
BeanDefinition beanDefinition = ((GenericApplicationContext) context.getSourceApplicationContext())
|
||||
.getBeanDefinition("jacksonJsonHttpMessageConverter");
|
||||
assertThat(beanDefinition.getFactoryBeanName())
|
||||
.isEqualTo(JacksonJsonHttpMessageConverterConfiguration.class.getName());
|
||||
assertThat(context).hasSingleBean(JacksonJsonHttpMessageConvertersCustomizer.class);
|
||||
assertConvertersRegisteredWithHttpMessageConverters(context, List.of(HalFormsHttpMessageConverter.class,
|
||||
AlpsJacksonJsonHttpMessageConverter.class, JacksonJsonHttpMessageConverter.class));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void jacksonIsPreferredByDefault() {
|
||||
allOptionsRunner().run((context) -> {
|
||||
assertConverterBeanExists(context, JacksonJsonHttpMessageConverter.class,
|
||||
"jacksonJsonHttpMessageConverter");
|
||||
assertConverterBeanRegisteredWithHttpMessageConverters(context, JacksonJsonHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
|
||||
assertBeanExists(context, JacksonJsonHttpMessageConvertersCustomizer.class,
|
||||
"jacksonJsonHttpMessageConvertersCustomizer");
|
||||
assertConverterIsRegistered(context, JacksonJsonHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(GsonHttpConvertersCustomizer.class);
|
||||
assertThat(context).doesNotHaveBean(JsonbHttpMessageConvertersCustomizer.class);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -300,13 +338,10 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
allOptionsRunner().withClassLoader(new FilteredClassLoader(JsonMapper.class.getPackage().getName()))
|
||||
.withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
|
||||
.run((context) -> {
|
||||
assertConverterBeanExists(context,
|
||||
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class,
|
||||
"mappingJackson2HttpMessageConverter");
|
||||
assertConverterBeanRegisteredWithHttpMessageConverters(context,
|
||||
assertConverterIsRegistered(context,
|
||||
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
|
||||
assertConverterIsNotRegistered(context, GsonHttpMessageConverter.class);
|
||||
assertConverterIsNotRegistered(context, JsonbHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -316,9 +351,8 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
.withClassLoader(new FilteredClassLoader(JsonMapper.class.getPackage().getName(),
|
||||
ObjectMapper.class.getPackage().getName()))
|
||||
.run((context) -> {
|
||||
assertConverterBeanExists(context, GsonHttpMessageConverter.class, "gsonHttpMessageConverter");
|
||||
assertConverterBeanRegisteredWithHttpMessageConverters(context, GsonHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
|
||||
assertConverterIsRegistered(context, GsonHttpMessageConverter.class);
|
||||
assertConverterIsNotRegistered(context, JsonbHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -327,15 +361,15 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
allOptionsRunner()
|
||||
.withClassLoader(new FilteredClassLoader(JsonMapper.class.getPackage().getName(),
|
||||
ObjectMapper.class.getPackage().getName(), Gson.class.getPackage().getName()))
|
||||
.run(assertConverter(JsonbHttpMessageConverter.class, "jsonbHttpMessageConverter"));
|
||||
.run((context) -> assertConverterIsRegistered(context, JsonbHttpMessageConverter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void whenServletWebApplicationHttpMessageConvertersIsConfigured() {
|
||||
new WebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
|
||||
.run((context) -> assertThat(context).hasSingleBean(ServerHttpMessageConvertersCustomizer.class)
|
||||
.hasSingleBean(ClientHttpMessageConvertersCustomizer.class));
|
||||
.run((context) -> assertThat(context).hasSingleBean(DefaultClientHttpMessageConvertersCustomizer.class)
|
||||
.hasSingleBean(DefaultClientHttpMessageConvertersCustomizer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -351,9 +385,9 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
new WebApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(HttpMessageConvertersAutoConfiguration.class))
|
||||
.run((context) -> {
|
||||
assertThat(context).hasSingleBean(StringHttpMessageConverter.class);
|
||||
assertThat(context.getBean(StringHttpMessageConverter.class).getDefaultCharset())
|
||||
.isEqualTo(StandardCharsets.UTF_8);
|
||||
StringHttpMessageConverter converter = findConverter(getServerConverters(context),
|
||||
StringHttpMessageConverter.class);
|
||||
assertThat(converter.getDefaultCharset()).isEqualTo(StandardCharsets.UTF_8);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -363,9 +397,9 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
.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);
|
||||
StringHttpMessageConverter serverConverter = findConverter(getServerConverters(context),
|
||||
StringHttpMessageConverter.class);
|
||||
assertThat(serverConverter.getDefaultCharset()).isEqualTo(StandardCharsets.UTF_16);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -398,53 +432,65 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
.withBean(Json.class, () -> Json.Default);
|
||||
}
|
||||
|
||||
private ContextConsumer<AssertableApplicationContext> assertConverter(
|
||||
Class<? extends HttpMessageConverter<?>> converterType, String beanName) {
|
||||
return (context) -> {
|
||||
assertConverterBeanExists(context, converterType, beanName);
|
||||
assertConverterBeanRegisteredWithHttpMessageConverters(context, converterType);
|
||||
};
|
||||
private void assertConverterIsRegistered(AssertableApplicationContext context,
|
||||
Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
assertThat(getClientConverters(context)).filteredOn((c) -> converterType.isAssignableFrom(c.getClass()))
|
||||
.hasSize(1);
|
||||
assertThat(getServerConverters(context)).filteredOn((c) -> converterType.isAssignableFrom(c.getClass()))
|
||||
.hasSize(1);
|
||||
}
|
||||
|
||||
private void assertConverterBeanExists(AssertableApplicationContext context, Class<?> type, String beanName) {
|
||||
assertThat(context).hasSingleBean(type);
|
||||
private void assertConverterIsNotRegistered(AssertableApplicationContext context,
|
||||
Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
assertThat(getClientConverters(context)).filteredOn((c) -> converterType.isAssignableFrom(c.getClass()))
|
||||
.isEmpty();
|
||||
assertThat(getServerConverters(context)).filteredOn((c) -> converterType.isAssignableFrom(c.getClass()))
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
private void assertBeanExists(AssertableApplicationContext context, Class<?> type, String beanName) {
|
||||
assertThat(context).getBean(beanName).isInstanceOf(type);
|
||||
assertThat(context).hasBean(beanName);
|
||||
}
|
||||
|
||||
private void assertConverterBeanRegisteredWithHttpMessageConverters(AssertableApplicationContext context,
|
||||
Class<? extends HttpMessageConverter<?>> type) {
|
||||
HttpMessageConverter<?> converter = context.getBean(type);
|
||||
ClientHttpMessageConvertersCustomizer clientCustomizer = context
|
||||
.getBean(ClientHttpMessageConvertersCustomizer.class);
|
||||
private HttpMessageConverters getClientConverters(ApplicationContext context) {
|
||||
ClientBuilder clientBuilder = HttpMessageConverters.forClient().registerDefaults();
|
||||
clientCustomizer.customize(clientBuilder);
|
||||
HttpMessageConverters clientConverters = clientBuilder.build();
|
||||
assertThat(clientConverters).contains(converter);
|
||||
assertThat(clientConverters).filteredOn((c) -> type.isAssignableFrom(c.getClass())).hasSize(1);
|
||||
|
||||
ServerHttpMessageConvertersCustomizer serverCustomizer = context
|
||||
.getBean(ServerHttpMessageConvertersCustomizer.class);
|
||||
ServerBuilder serverBuilder = HttpMessageConverters.forServer().registerDefaults();
|
||||
serverCustomizer.customize(serverBuilder);
|
||||
HttpMessageConverters serverConverters = serverBuilder.build();
|
||||
assertThat(serverConverters).contains(converter);
|
||||
assertThat(serverConverters).filteredOn((c) -> type.isAssignableFrom(c.getClass())).hasSize(1);
|
||||
context.getBeansOfType(ClientHttpMessageConvertersCustomizer.class)
|
||||
.values()
|
||||
.forEach((customizer) -> customizer.customize(clientBuilder));
|
||||
return clientBuilder.build();
|
||||
}
|
||||
|
||||
private void assertConvertersBeanRegisteredWithHttpMessageConverters(AssertableApplicationContext context,
|
||||
List<Class<? extends HttpMessageConverter<?>>> types) {
|
||||
List<? extends HttpMessageConverter<?>> converterInstances = types.stream().map(context::getBean).toList();
|
||||
ClientHttpMessageConvertersCustomizer clientCustomizer = context
|
||||
.getBean(ClientHttpMessageConvertersCustomizer.class);
|
||||
ClientBuilder clientBuilder = HttpMessageConverters.forClient().registerDefaults();
|
||||
clientCustomizer.customize(clientBuilder);
|
||||
assertThat(clientBuilder.build()).containsSubsequence(converterInstances);
|
||||
|
||||
ServerHttpMessageConvertersCustomizer serverCustomizer = context
|
||||
.getBean(ServerHttpMessageConvertersCustomizer.class);
|
||||
private HttpMessageConverters getServerConverters(ApplicationContext context) {
|
||||
ServerBuilder serverBuilder = HttpMessageConverters.forServer().registerDefaults();
|
||||
serverCustomizer.customize(serverBuilder);
|
||||
assertThat(serverBuilder.build()).containsSubsequence(converterInstances);
|
||||
context.getBeansOfType(ServerHttpMessageConvertersCustomizer.class)
|
||||
.values()
|
||||
.forEach((customizer) -> customizer.customize(serverBuilder));
|
||||
return serverBuilder.build();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends HttpMessageConverter<?>> T findConverter(HttpMessageConverters converters,
|
||||
Class<? extends HttpMessageConverter<?>> type) {
|
||||
for (HttpMessageConverter<?> converter : converters) {
|
||||
if (type.isAssignableFrom(converter.getClass())) {
|
||||
return (T) converter;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Could not find converter of type " + type);
|
||||
}
|
||||
|
||||
private void assertConvertersRegisteredWithHttpMessageConverters(AssertableApplicationContext context,
|
||||
List<Class<? extends HttpMessageConverter<?>>> types) {
|
||||
HttpMessageConverters clientConverters = getClientConverters(context);
|
||||
List<Class<?>> clientConverterTypes = new ArrayList<>();
|
||||
clientConverters.forEach((converter) -> clientConverterTypes.add(converter.getClass()));
|
||||
assertThat(clientConverterTypes).containsSubsequence(types);
|
||||
|
||||
HttpMessageConverters serverConverters = getServerConverters(context);
|
||||
List<Class<?>> serverConverterTypes = new ArrayList<>();
|
||||
serverConverters.forEach((converter) -> serverConverterTypes.add(converter.getClass()));
|
||||
assertThat(serverConverterTypes).containsSubsequence(types);
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
||||
+2
-3
@@ -38,9 +38,8 @@ class HttpMessageConvertersAutoConfigurationWithoutJacksonTests {
|
||||
|
||||
@Test
|
||||
void autoConfigurationWorksWithSpringHateoasButWithoutJackson() {
|
||||
this.contextRunner
|
||||
.run((context) -> assertThat(context).hasSingleBean(ClientHttpMessageConvertersCustomizer.class)
|
||||
.hasSingleBean(ServerHttpMessageConvertersCustomizer.class));
|
||||
this.contextRunner.run((context) -> assertThat(context).hasBean("clientConvertersCustomizer")
|
||||
.hasBean("serverConvertersCustomizer"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user