diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/Jackson2ObjectMapperBuilderCustomizer.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/Jackson2ObjectMapperBuilderCustomizer.java index 79e5d577847..59b6a451f4b 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/Jackson2ObjectMapperBuilderCustomizer.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/Jackson2ObjectMapperBuilderCustomizer.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.jackson; import com.fasterxml.jackson.databind.ObjectMapper; + import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; /** @@ -29,9 +30,9 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; */ public interface Jackson2ObjectMapperBuilderCustomizer { - /** - * Customize the jacksonObjectMapperBuilder. - * @param jacksonObjectMapperBuilder the jacksonObjectMapperBuilder to customize - */ - void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder); + /** + * Customize the jacksonObjectMapperBuilder. + * @param jacksonObjectMapperBuilder the jacksonObjectMapperBuilder to customize + */ + void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder); } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java index 92c6b255cc4..f8eef994d36 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java @@ -20,8 +20,8 @@ import java.lang.reflect.Field; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Collection; -import java.util.Locale; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.TimeZone; @@ -42,7 +42,6 @@ import org.joda.time.format.DateTimeFormat; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.ListableBeanFactory; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnJava; import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.JavaVersion; @@ -53,7 +52,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.Ordered; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -88,30 +87,13 @@ public class JacksonAutoConfiguration { @ConditionalOnClass({ ObjectMapper.class, Jackson2ObjectMapperBuilder.class }) static class JacksonObjectMapperConfiguration { - private final List builderCustomizers; - - JacksonObjectMapperConfiguration( - ObjectProvider> builderCustomizersProvider) { - this.builderCustomizers = builderCustomizersProvider.getIfAvailable(); - } - @Bean @Primary @ConditionalOnMissingBean(ObjectMapper.class) public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { - customize(builder); return builder.createXmlMapper(false).build(); } - private void customize(Jackson2ObjectMapperBuilder builder) { - if (this.builderCustomizers != null) { - AnnotationAwareOrderComparator.sort(this.builderCustomizers); - for (Jackson2ObjectMapperBuilderCustomizer customizer : this.builderCustomizers) { - customizer.customize(builder); - } - } - } - } @Configuration @@ -180,137 +162,189 @@ public class JacksonAutoConfiguration { @Configuration @ConditionalOnClass({ ObjectMapper.class, Jackson2ObjectMapperBuilder.class }) - @EnableConfigurationProperties(JacksonProperties.class) static class JacksonObjectMapperBuilderConfiguration { private final ApplicationContext applicationContext; - private final JacksonProperties jacksonProperties; - JacksonObjectMapperBuilderConfiguration(ApplicationContext applicationContext, - JacksonProperties jacksonProperties) { + JacksonProperties jacksonProperties, + List customizers) { this.applicationContext = applicationContext; - this.jacksonProperties = jacksonProperties; } @Bean @ConditionalOnMissingBean(Jackson2ObjectMapperBuilder.class) - public Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder() { + public Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder( + List customizers) { Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); builder.applicationContext(this.applicationContext); - if (this.jacksonProperties.getDefaultPropertyInclusion() != null) { - builder.serializationInclusion( - this.jacksonProperties.getDefaultPropertyInclusion()); - } - if (this.jacksonProperties.getTimeZone() != null) { - builder.timeZone(this.jacksonProperties.getTimeZone()); - } - configureFeatures(builder, this.jacksonProperties.getDeserialization()); - configureFeatures(builder, this.jacksonProperties.getSerialization()); - configureFeatures(builder, this.jacksonProperties.getMapper()); - configureFeatures(builder, this.jacksonProperties.getParser()); - configureFeatures(builder, this.jacksonProperties.getGenerator()); - configureDateFormat(builder); - configurePropertyNamingStrategy(builder); - configureModules(builder); - configureLocale(builder); + customize(builder, customizers); return builder; } - private void configureFeatures(Jackson2ObjectMapperBuilder builder, - Map features) { - for (Entry entry : features.entrySet()) { - if (entry.getValue() != null && entry.getValue()) { - builder.featuresToEnable(entry.getKey()); - } - else { - builder.featuresToDisable(entry.getKey()); - } + private void customize(Jackson2ObjectMapperBuilder builder, + List customizers) { + for (Jackson2ObjectMapperBuilderCustomizer customizer : customizers) { + customizer.customize(builder); } } - private void configureDateFormat(Jackson2ObjectMapperBuilder builder) { - // We support a fully qualified class name extending DateFormat or a date - // pattern string value - String dateFormat = this.jacksonProperties.getDateFormat(); - if (dateFormat != null) { - try { - Class dateFormatClass = ClassUtils.forName(dateFormat, null); - builder.dateFormat( - (DateFormat) BeanUtils.instantiateClass(dateFormatClass)); + } + + @Configuration + @ConditionalOnClass({ ObjectMapper.class, Jackson2ObjectMapperBuilder.class }) + @EnableConfigurationProperties(JacksonProperties.class) + static class Jackson2ObjectMapperBuilderCustomizerConfiguration { + + @Bean + public StandardJackson2ObjectMapperBuilderCustomizer standardJacksonObjectMapperBuilderCustomizer( + ApplicationContext applicationContext, + JacksonProperties jacksonProperties) { + return new StandardJackson2ObjectMapperBuilderCustomizer(applicationContext, + jacksonProperties); + } + + private static final class StandardJackson2ObjectMapperBuilderCustomizer + implements Jackson2ObjectMapperBuilderCustomizer, Ordered { + + private final ApplicationContext applicationContext; + + private final JacksonProperties jacksonProperties; + + StandardJackson2ObjectMapperBuilderCustomizer( + ApplicationContext applicationContext, + JacksonProperties jacksonProperties) { + this.applicationContext = applicationContext; + this.jacksonProperties = jacksonProperties; + } + + @Override + public int getOrder() { + return 0; + } + + @Override + public void customize(Jackson2ObjectMapperBuilder builder) { + + if (this.jacksonProperties.getDefaultPropertyInclusion() != null) { + builder.serializationInclusion( + this.jacksonProperties.getDefaultPropertyInclusion()); } - catch (ClassNotFoundException ex) { - SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat); - // Since Jackson 2.6.3 we always need to set a TimeZone (see gh-4170) - // If none in our properties fallback to the Jackson's default - TimeZone timeZone = this.jacksonProperties.getTimeZone(); - if (timeZone == null) { - timeZone = new ObjectMapper().getSerializationConfig() - .getTimeZone(); + if (this.jacksonProperties.getTimeZone() != null) { + builder.timeZone(this.jacksonProperties.getTimeZone()); + } + configureFeatures(builder, this.jacksonProperties.getDeserialization()); + configureFeatures(builder, this.jacksonProperties.getSerialization()); + configureFeatures(builder, this.jacksonProperties.getMapper()); + configureFeatures(builder, this.jacksonProperties.getParser()); + configureFeatures(builder, this.jacksonProperties.getGenerator()); + configureDateFormat(builder); + configurePropertyNamingStrategy(builder); + configureModules(builder); + configureLocale(builder); + } + + private void configureFeatures(Jackson2ObjectMapperBuilder builder, + Map features) { + for (Entry entry : features.entrySet()) { + if (entry.getValue() != null && entry.getValue()) { + builder.featuresToEnable(entry.getKey()); + } + else { + builder.featuresToDisable(entry.getKey()); } - simpleDateFormat.setTimeZone(timeZone); - builder.dateFormat(simpleDateFormat); } } - } - private void configurePropertyNamingStrategy( - Jackson2ObjectMapperBuilder builder) { - // We support a fully qualified class name extending Jackson's - // PropertyNamingStrategy or a string value corresponding to the constant - // names in PropertyNamingStrategy which hold default provided implementations - String strategy = this.jacksonProperties.getPropertyNamingStrategy(); - if (strategy != null) { + private void configureDateFormat(Jackson2ObjectMapperBuilder builder) { + // We support a fully qualified class name extending DateFormat or a date + // pattern string value + String dateFormat = this.jacksonProperties.getDateFormat(); + if (dateFormat != null) { + try { + Class dateFormatClass = ClassUtils.forName(dateFormat, null); + builder.dateFormat( + (DateFormat) BeanUtils.instantiateClass(dateFormatClass)); + } + catch (ClassNotFoundException ex) { + SimpleDateFormat simpleDateFormat = new SimpleDateFormat( + dateFormat); + // Since Jackson 2.6.3 we always need to set a TimeZone (see + // gh-4170). If none in our properties fallback to the Jackson's + // default + TimeZone timeZone = this.jacksonProperties.getTimeZone(); + if (timeZone == null) { + timeZone = new ObjectMapper().getSerializationConfig() + .getTimeZone(); + } + simpleDateFormat.setTimeZone(timeZone); + builder.dateFormat(simpleDateFormat); + } + } + } + + private void configurePropertyNamingStrategy( + Jackson2ObjectMapperBuilder builder) { + // We support a fully qualified class name extending Jackson's + // PropertyNamingStrategy or a string value corresponding to the constant + // names in PropertyNamingStrategy which hold default provided + // implementations + String strategy = this.jacksonProperties.getPropertyNamingStrategy(); + if (strategy != null) { + try { + configurePropertyNamingStrategyClass(builder, + ClassUtils.forName(strategy, null)); + } + catch (ClassNotFoundException ex) { + configurePropertyNamingStrategyField(builder, strategy); + } + } + } + + private void configurePropertyNamingStrategyClass( + Jackson2ObjectMapperBuilder builder, + Class propertyNamingStrategyClass) { + builder.propertyNamingStrategy((PropertyNamingStrategy) BeanUtils + .instantiateClass(propertyNamingStrategyClass)); + } + + private void configurePropertyNamingStrategyField( + Jackson2ObjectMapperBuilder builder, String fieldName) { + // Find the field (this way we automatically support new constants + // that may be added by Jackson in the future) + Field field = ReflectionUtils.findField(PropertyNamingStrategy.class, + fieldName, PropertyNamingStrategy.class); + Assert.notNull(field, "Constant named '" + fieldName + "' not found on " + + PropertyNamingStrategy.class.getName()); try { - configurePropertyNamingStrategyClass(builder, - ClassUtils.forName(strategy, null)); + builder.propertyNamingStrategy( + (PropertyNamingStrategy) field.get(null)); } - catch (ClassNotFoundException ex) { - configurePropertyNamingStrategyField(builder, strategy); + catch (Exception ex) { + throw new IllegalStateException(ex); } } - } - private void configurePropertyNamingStrategyClass( - Jackson2ObjectMapperBuilder builder, - Class propertyNamingStrategyClass) { - builder.propertyNamingStrategy((PropertyNamingStrategy) BeanUtils - .instantiateClass(propertyNamingStrategyClass)); - } - - private void configurePropertyNamingStrategyField( - Jackson2ObjectMapperBuilder builder, String fieldName) { - // Find the field (this way we automatically support new constants - // that may be added by Jackson in the future) - Field field = ReflectionUtils.findField(PropertyNamingStrategy.class, - fieldName, PropertyNamingStrategy.class); - Assert.notNull(field, "Constant named '" + fieldName + "' not found on " - + PropertyNamingStrategy.class.getName()); - try { - builder.propertyNamingStrategy((PropertyNamingStrategy) field.get(null)); + private void configureModules(Jackson2ObjectMapperBuilder builder) { + Collection moduleBeans = getBeans(this.applicationContext, + Module.class); + builder.modulesToInstall( + moduleBeans.toArray(new Module[moduleBeans.size()])); } - catch (Exception ex) { - throw new IllegalStateException(ex); + + private void configureLocale(Jackson2ObjectMapperBuilder builder) { + Locale locale = this.jacksonProperties.getLocale(); + if (locale != null) { + builder.locale(locale); + } } - } - private void configureModules(Jackson2ObjectMapperBuilder builder) { - Collection moduleBeans = getBeans(this.applicationContext, - Module.class); - builder.modulesToInstall(moduleBeans.toArray(new Module[moduleBeans.size()])); - } - - private void configureLocale(Jackson2ObjectMapperBuilder builder) { - Locale locale = this.jacksonProperties.getLocale(); - if (locale != null) { - builder.locale(locale); + private static Collection getBeans(ListableBeanFactory beanFactory, + Class type) { + return BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, type) + .values(); } - } - private static Collection getBeans(ListableBeanFactory beanFactory, - Class type) { - return BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, type) - .values(); } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java index e873432ac83..9dc026d804e 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java @@ -527,7 +527,8 @@ public class JacksonAutoConfigurationTests { public Jackson2ObjectMapperBuilderCustomizer customDateFormat() { return new Jackson2ObjectMapperBuilderCustomizer() { @Override - public void customize(Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) { + public void customize( + Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) { jackson2ObjectMapperBuilder.dateFormat(new MyDateFormat()); } };