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
This commit is contained in:
Andy Wilkinson 2015-02-05 10:47:38 +00:00
parent e5d653dbd4
commit 230048ae8b
4 changed files with 190 additions and 76 deletions

View File

@ -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 {
}
}
}

View File

@ -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<HttpMessageConverter<?>> 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)

View File

@ -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;
}
}
}

View File

@ -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);