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
pull/2150/merge
Andy Wilkinson 10 years ago
parent 0899ffdadf
commit f11bcb9495

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -73,15 +73,15 @@ public class JacksonAutoConfiguration {
@PostConstruct @PostConstruct
private void registerModulesWithObjectMappers() { private void registerModulesWithObjectMappers() {
Collection<Module> modules = getBeans(Module.class); Collection<Module> modules = getBeans(this.beanFactory, Module.class);
for (ObjectMapper objectMapper : getBeans(ObjectMapper.class)) { for (ObjectMapper objectMapper : getBeans(this.beanFactory, ObjectMapper.class)) {
objectMapper.registerModules(modules); objectMapper.registerModules(modules);
} }
} }
private <T> Collection<T> getBeans(Class<T> type) { private static <T> Collection<T> getBeans(ListableBeanFactory beanFactory,
return BeanFactoryUtils.beansOfTypeIncludingAncestors(this.beanFactory, type) Class<T> type) {
.values(); return BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, type).values();
} }
@Configuration @Configuration
@ -131,6 +131,7 @@ public class JacksonAutoConfiguration {
configureFeatures(builder, this.jacksonProperties.getGenerator()); configureFeatures(builder, this.jacksonProperties.getGenerator());
configureDateFormat(builder); configureDateFormat(builder);
configurePropertyNamingStrategy(builder); configurePropertyNamingStrategy(builder);
configureModules(builder);
return builder; return builder;
} }
@ -200,6 +201,12 @@ public class JacksonAutoConfiguration {
} }
} }
private void configureModules(Jackson2ObjectMapperBuilder builder) {
Collection<Module> moduleBeans = getBeans(this.applicationContext,
Module.class);
builder.modulesToInstall(moduleBeans.toArray(new Module[moduleBeans.size()]));
}
@Override @Override
public void setApplicationContext(ApplicationContext applicationContext) { public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext; this.applicationContext = applicationContext;

@ -19,6 +19,8 @@ package org.springframework.boot.autoconfigure.jackson;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.LocalDateTime; 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.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.MapperFeature;
@ -90,7 +93,8 @@ public class JacksonAutoConfigurationTests {
@Test @Test
public void customJacksonModules() throws Exception { public void customJacksonModules() throws Exception {
this.context.register(ModulesConfig.class, JacksonAutoConfiguration.class); this.context.register(ModuleConfig.class, MockObjectMapperConfig.class,
JacksonAutoConfiguration.class);
this.context.refresh(); this.context.refresh();
ObjectMapper mapper = this.context.getBean(ObjectMapper.class); ObjectMapper mapper = this.context.getBean(ObjectMapper.class);
@SuppressWarnings({ "unchecked", "unused" }) @SuppressWarnings({ "unchecked", "unused" })
@ -376,13 +380,19 @@ public class JacksonAutoConfigurationTests {
SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)); SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS));
} }
@Configuration @Test
protected static class ModulesConfig { 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 @Configuration
public Module jacksonModule() { protected static class MockObjectMapperConfig {
return new SimpleModule();
}
@Bean @Bean
@Primary @Primary
@ -392,6 +402,15 @@ public class JacksonAutoConfigurationTests {
} }
@Configuration
protected static class ModuleConfig {
@Bean
public CustomModule jacksonModule() {
return new CustomModule();
}
}
@Configuration @Configuration
protected static class DoubleModulesConfig { protected static class DoubleModulesConfig {
@ -455,4 +474,19 @@ public class JacksonAutoConfigurationTests {
this.propertyName = propertyName; this.propertyName = propertyName;
} }
} }
private static class CustomModule extends SimpleModule {
private Set<ObjectCodec> owners = new HashSet<ObjectCodec>();
@Override
public void setupModule(SetupContext context) {
this.owners.add(context.getOwner());
}
Set<ObjectCodec> getOwners() {
return this.owners;
}
}
} }

Loading…
Cancel
Save