From 230048ae8b49442cdaa1d3ea6f1034ce518995af Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 5 Feb 2015 10:47:38 +0000 Subject: [PATCH] Prefer Jackson unless user explicity states preference for Gson This commit rewords the changes made in d718a80 so that simply adding Gson to the classpath isn't sufficient to stop Jackson being used for HTTP JSON mapping. To use Gson, the user must now either remove Jackson from the classpath (not an option if they also wish to use the Actuator) or configure the spring.http.converters.preferred-mapper property with a value gson. Closes gh-2247 --- ...sonHttpMessageConvertersConfiguration.java | 75 +++++++++++++++ ...ttpMessageConvertersAutoConfiguration.java | 80 +--------------- ...sonHttpMessageConvertersConfiguration.java | 94 +++++++++++++++++++ ...ssageConvertersAutoConfigurationTests.java | 17 +++- 4 files changed, 190 insertions(+), 76 deletions(-) create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/GsonHttpMessageConvertersConfiguration.java create mode 100644 spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/JacksonHttpMessageConvertersConfiguration.java diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/GsonHttpMessageConvertersConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/GsonHttpMessageConvertersConfiguration.java new file mode 100644 index 0000000000..eaf0eb237c --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/GsonHttpMessageConvertersConfiguration.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2015 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 + * + * http://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.autoconfigure.web; + +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.MappingJackson2HttpMessageConverter; + +import com.google.gson.Gson; + +/** + * Configuration for HTTP Message converters that use Gson + * + * @author awilkinson + * @since 1.2.2 + */ +@Configuration +class GsonHttpMessageConvertersConfiguration { + + @Configuration + @ConditionalOnClass(Gson.class) + @ConditionalOnBean(Gson.class) + @Conditional(PreferGsonOrMissingJacksonCondition.class) + protected static class GsonHttpMessageConverterConfiguration { + + @Bean + @ConditionalOnMissingBean + public GsonHttpMessageConverter gsonHttpMessageConverter(Gson gson) { + GsonHttpMessageConverter converter = new GsonHttpMessageConverter(); + converter.setGson(gson); + return converter; + } + + } + + private static class PreferGsonOrMissingJacksonCondition extends AnyNestedCondition { + + public PreferGsonOrMissingJacksonCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "gson", matchIfMissing = false) + static class GsonPreferred { + + } + + @ConditionalOnMissingBean(MappingJackson2HttpMessageConverter.class) + static class JacksonMissing { + + } + + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfiguration.java index 28ac65b490..2673f5ab77 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfiguration.java @@ -22,7 +22,6 @@ import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -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.gson.GsonAutoConfiguration; @@ -30,16 +29,9 @@ import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; 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.MappingJackson2HttpMessageConverter; -import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.xml.XmlMapper; -import com.google.gson.Gson; /** * {@link EnableAutoConfiguration Auto-configuration} for {@link HttpMessageConverter}s. @@ -56,8 +48,12 @@ import com.google.gson.Gson; @Configuration @ConditionalOnClass(HttpMessageConverter.class) @AutoConfigureAfter({ GsonAutoConfiguration.class, JacksonAutoConfiguration.class }) +@Import({ JacksonHttpMessageConvertersConfiguration.class, + GsonHttpMessageConvertersConfiguration.class }) public class HttpMessageConvertersAutoConfiguration { + static final String PREFERRED_MAPPER_PROPERTY = "spring.http.converters.preferred-mapper"; + @Autowired(required = false) private final List> converters = Collections.emptyList(); @@ -67,72 +63,6 @@ public class HttpMessageConvertersAutoConfiguration { return new HttpMessageConverters(this.converters); } - @Configuration - @ConditionalOnClass(ObjectMapper.class) - @ConditionalOnBean(ObjectMapper.class) - @ConditionalOnMissingBean(GsonHttpMessageConverter.class) - @EnableConfigurationProperties(HttpMapperProperties.class) - @SuppressWarnings("deprecation") - protected static class MappingJackson2HttpMessageConverterConfiguration { - - // This can be removed when the deprecated class is removed (the ObjectMapper will - // already have all the correct properties). - @Autowired - private HttpMapperProperties properties = new HttpMapperProperties(); - - @Bean - @ConditionalOnMissingBean - public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter( - ObjectMapper objectMapper) { - MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter( - objectMapper); - if (this.properties.isJsonPrettyPrint() != null) { - converter.setPrettyPrint(this.properties.isJsonPrettyPrint()); - } - return converter; - } - - } - - @Configuration - @ConditionalOnClass(XmlMapper.class) - @ConditionalOnBean(Jackson2ObjectMapperBuilder.class) - @EnableConfigurationProperties(HttpMapperProperties.class) - @SuppressWarnings("deprecation") - protected static class MappingJackson2XmlHttpMessageConverterConfiguration { - - @Autowired - private HttpMapperProperties properties = new HttpMapperProperties(); - - @Bean - @ConditionalOnMissingBean - public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter( - Jackson2ObjectMapperBuilder builder) { - MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter(); - converter.setObjectMapper(builder.createXmlMapper(true).build()); - if (this.properties.isJsonPrettyPrint() != null) { - converter.setPrettyPrint(this.properties.isJsonPrettyPrint()); - } - return converter; - } - - } - - @Configuration - @ConditionalOnClass(Gson.class) - @ConditionalOnBean(Gson.class) - protected static class GsonHttpMessageConverterConfiguration { - - @Bean - @ConditionalOnMissingBean - public GsonHttpMessageConverter gsonHttpMessageConverter(Gson gson) { - GsonHttpMessageConverter converter = new GsonHttpMessageConverter(); - converter.setGson(gson); - return converter; - } - - } - @Configuration @ConditionalOnClass(StringHttpMessageConverter.class) @EnableConfigurationProperties(HttpEncodingProperties.class) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/JacksonHttpMessageConvertersConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/JacksonHttpMessageConvertersConfiguration.java new file mode 100644 index 0000000000..9e315c6515 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/JacksonHttpMessageConvertersConfiguration.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012-2015 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 + * + * http://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.autoconfigure.web; + +import org.springframework.beans.factory.annotation.Autowired; +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.context.properties.EnableConfigurationProperties; +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; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; + +/** + * Configuration for HTTP message converters that use Jackson + * + * @author Andy Wilkinson + * @since 1.2.2 + */ +@Configuration +class JacksonHttpMessageConvertersConfiguration { + + @Configuration + @ConditionalOnClass(ObjectMapper.class) + @ConditionalOnBean(ObjectMapper.class) + @EnableConfigurationProperties(HttpMapperProperties.class) + @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "jackson", matchIfMissing = true) + @SuppressWarnings("deprecation") + protected static class MappingJackson2HttpMessageConverterConfiguration { + + // This can be removed when the deprecated class is removed (the ObjectMapper will + // already have all the correct properties). + @Autowired + private HttpMapperProperties properties = new HttpMapperProperties(); + + @Bean + @ConditionalOnMissingBean + public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter( + ObjectMapper objectMapper) { + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter( + objectMapper); + if (this.properties.isJsonPrettyPrint() != null) { + converter.setPrettyPrint(this.properties.isJsonPrettyPrint()); + } + return converter; + } + + } + + @Configuration + @ConditionalOnClass(XmlMapper.class) + @ConditionalOnBean(Jackson2ObjectMapperBuilder.class) + @EnableConfigurationProperties(HttpMapperProperties.class) + @SuppressWarnings("deprecation") + protected static class MappingJackson2XmlHttpMessageConverterConfiguration { + + @Autowired + private HttpMapperProperties properties = new HttpMapperProperties(); + + @Bean + @ConditionalOnMissingBean + public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter( + Jackson2ObjectMapperBuilder builder) { + MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter(); + converter.setObjectMapper(builder.createXmlMapper(true).build()); + if (this.properties.isJsonPrettyPrint() != null) { + converter.setPrettyPrint(this.properties.isJsonPrettyPrint()); + } + return converter; + } + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java index 4232d37f46..522fff05d3 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java @@ -130,11 +130,26 @@ public class HttpMessageConvertersAutoConfigurationTests { } @Test - public void gsonIsPreferredWhenBothGsonAndJacksonAreAvailable() { + public void jacksonIsPreferredByDefaultWhenBothGsonAndJacksonAreAvailable() { this.context.register(GsonAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class); this.context.refresh(); + assertConverterBeanExists(MappingJackson2HttpMessageConverter.class, + "mappingJackson2HttpMessageConverter"); + assertConverterBeanRegisteredWithHttpMessageConverters(MappingJackson2HttpMessageConverter.class); + assertEquals(0, this.context.getBeansOfType(GsonHttpMessageConverter.class) + .size()); + } + + @Test + public void gsonCanBePreferredWhenBothGsonAndJacksonAreAvailable() { + this.context.register(GsonAutoConfiguration.class, + JacksonAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.http.converters.preferred-mapper:gson"); + this.context.refresh(); assertConverterBeanExists(GsonHttpMessageConverter.class, "gsonHttpMessageConverter"); assertConverterBeanRegisteredWithHttpMessageConverters(GsonHttpMessageConverter.class);