From f11bcb94954be7f8d9a4946f571c22986d57f35b Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 12 Feb 2015 13:42:32 +0000 Subject: [PATCH] Register Module beans with Jackson2ObjectMapperBuilder Prior to this commit, Module beans were registered with all ObjectMapper beans, but were not registered with the auto-configured Jackson2ObjectMapperBuilder. This meant that any ObjectMapper created with the builder but not exposed as a bean would not have the Module beans registered with it. One such ObjectMapper is the one used by the auto-configured MappingJackson2XmlHttpMessageConverter. This caused XML (de)serialization to be different to JSON (de)serialization. This commit updates JacksonAutoConfiguration to register all of the application context's Module beans with the auto-configured Jackson2ObjectMapperBuilder. This ensures consistent configuration of any ObjectMapper that's created using the builder, irrespective of whether or not that ObjectMapper is also exposed as a bean, and also ensures that (de)serialization of JSON and XML is consistent. See gh-2327 --- .../jackson/JacksonAutoConfiguration.java | 19 +++++--- .../JacksonAutoConfigurationTests.java | 48 ++++++++++++++++--- 2 files changed, 54 insertions(+), 13 deletions(-) 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 24ee210f47a..94beabf9fc1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * 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. @@ -73,15 +73,15 @@ public class JacksonAutoConfiguration { @PostConstruct private void registerModulesWithObjectMappers() { - Collection modules = getBeans(Module.class); - for (ObjectMapper objectMapper : getBeans(ObjectMapper.class)) { + Collection modules = getBeans(this.beanFactory, Module.class); + for (ObjectMapper objectMapper : getBeans(this.beanFactory, ObjectMapper.class)) { objectMapper.registerModules(modules); } } - private Collection getBeans(Class type) { - return BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type) - .values(); + private static Collection getBeans(ListableBeanFactory beanFactory, + Class type) { + return BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, type).values(); } @Configuration @@ -131,6 +131,7 @@ public class JacksonAutoConfiguration { configureFeatures(builder, this.jacksonProperties.getGenerator()); configureDateFormat(builder); configurePropertyNamingStrategy(builder); + configureModules(builder); return builder; } @@ -200,6 +201,12 @@ public class JacksonAutoConfiguration { } } + private void configureModules(Jackson2ObjectMapperBuilder builder) { + Collection moduleBeans = getBeans(this.applicationContext, + Module.class); + builder.modulesToInstall(moduleBeans.toArray(new Module[moduleBeans.size()])); + } + @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; 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 897a7551c0a..6c94593adc1 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 @@ -19,6 +19,8 @@ package org.springframework.boot.autoconfigure.jackson; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.HashSet; +import java.util.Set; import org.joda.time.DateTime; import org.joda.time.LocalDateTime; @@ -36,6 +38,7 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.ObjectCodec; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.MapperFeature; @@ -90,7 +93,8 @@ public class JacksonAutoConfigurationTests { @Test public void customJacksonModules() throws Exception { - this.context.register(ModulesConfig.class, JacksonAutoConfiguration.class); + this.context.register(ModuleConfig.class, MockObjectMapperConfig.class, + JacksonAutoConfiguration.class); this.context.refresh(); ObjectMapper mapper = this.context.getBean(ObjectMapper.class); @SuppressWarnings({ "unchecked", "unused" }) @@ -376,13 +380,19 @@ public class JacksonAutoConfigurationTests { SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)); } - @Configuration - protected static class ModulesConfig { + @Test + public void moduleBeansAndWellKnownModulesAreRegisteredWithTheObjectMapperBuilder() { + this.context.register(ModuleConfig.class, JacksonAutoConfiguration.class); + this.context.refresh(); + ObjectMapper objectMapper = this.context.getBean( + Jackson2ObjectMapperBuilder.class).build(); + assertThat(this.context.getBean(CustomModule.class).getOwners(), + hasItem((ObjectCodec) objectMapper)); + assertThat(objectMapper.canSerialize(LocalDateTime.class), is(true)); + } - @Bean - public Module jacksonModule() { - return new SimpleModule(); - } + @Configuration + protected static class MockObjectMapperConfig { @Bean @Primary @@ -392,6 +402,15 @@ public class JacksonAutoConfigurationTests { } + @Configuration + protected static class ModuleConfig { + + @Bean + public CustomModule jacksonModule() { + return new CustomModule(); + } + } + @Configuration protected static class DoubleModulesConfig { @@ -455,4 +474,19 @@ public class JacksonAutoConfigurationTests { this.propertyName = propertyName; } } + + private static class CustomModule extends SimpleModule { + + private Set owners = new HashSet(); + + @Override + public void setupModule(SetupContext context) { + this.owners.add(context.getOwner()); + } + + Set getOwners() { + return this.owners; + } + + } }