Browse Source

Jackson2ObjectMapperFactoryBean builds on revised Jackson2ObjectMapperBuilder now

Issue: SPR-12243
pull/655/merge
Juergen Hoeller 11 years ago
parent
commit
d778037f40
  1. 187
      spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java
  2. 272
      spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java
  3. 14
      spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java
  4. 2
      spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java

187
spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java

@ -47,10 +47,10 @@ import org.springframework.util.ClassUtils;
/** /**
* A builder used to create {@link ObjectMapper} instances with a fluent API. * A builder used to create {@link ObjectMapper} instances with a fluent API.
* *
* <p>It customizes Jackson defaults properties with the following ones: * <p>It customizes Jackson's default properties with the following ones:
* <ul> * <ul>
* <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li> * <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li>
* <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li> * <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li>
* </ul> * </ul>
* *
* <p>Note that Jackson's JSR-310 and Joda-Time support modules will be registered automatically * <p>Note that Jackson's JSR-310 and Joda-Time support modules will be registered automatically
@ -59,28 +59,27 @@ import org.springframework.util.ClassUtils;
* <p>Tested against Jackson 2.2 and 2.3; compatible with Jackson 2.0 and higher. * <p>Tested against Jackson 2.2 and 2.3; compatible with Jackson 2.0 and higher.
* *
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Juergen Hoeller
* @since 4.1.1 * @since 4.1.1
* @see #build()
* @see #configure(ObjectMapper)
* @see Jackson2ObjectMapperFactoryBean
*/ */
public class Jackson2ObjectMapperBuilder { public class Jackson2ObjectMapperBuilder {
private static final boolean jackson2XmlPresent =
ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", getClassLoader());
private ObjectMapper objectMapper;
private boolean createXmlMapper = false; private boolean createXmlMapper = false;
private DateFormat dateFormat; private DateFormat dateFormat;
private JsonInclude.Include serializationInclusion;
private AnnotationIntrospector annotationIntrospector; private AnnotationIntrospector annotationIntrospector;
private final Map<Class<?>, JsonSerializer<?>> private PropertyNamingStrategy propertyNamingStrategy;
serializers = new LinkedHashMap<Class<?>, JsonSerializer<?>>();
private JsonInclude.Include serializationInclusion;
private final Map<Class<?>, JsonSerializer<?>> serializers = new LinkedHashMap<Class<?>, JsonSerializer<?>>();
private final Map<Class<?>, JsonDeserializer<?>> private final Map<Class<?>, JsonDeserializer<?>> deserializers = new LinkedHashMap<Class<?>, JsonDeserializer<?>>();
deserializers = new LinkedHashMap<Class<?>, JsonDeserializer<?>>();
private final Map<Object, Boolean> features = new HashMap<Object, Boolean>(); private final Map<Object, Boolean> features = new HashMap<Object, Boolean>();
@ -90,47 +89,35 @@ public class Jackson2ObjectMapperBuilder {
private boolean findModulesViaServiceLoader; private boolean findModulesViaServiceLoader;
private PropertyNamingStrategy propertyNamingStrategy; private ClassLoader moduleClassLoader = getClass().getClassLoader();
private Jackson2ObjectMapperBuilder() { private Jackson2ObjectMapperBuilder() {
} }
private Jackson2ObjectMapperBuilder(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
/** /**
* Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to build an {@link ObjectMapper} instance. * Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to
* build an {@link ObjectMapper} instance.
*/ */
public static Jackson2ObjectMapperBuilder json() { public static Jackson2ObjectMapperBuilder json() {
return new Jackson2ObjectMapperBuilder(); return new Jackson2ObjectMapperBuilder();
} }
/** /**
* Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to build a {@link XmlMapper} instance. * Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to
* build a {@link XmlMapper} instance.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Jackson2ObjectMapperBuilder xml() { public static Jackson2ObjectMapperBuilder xml() {
return new Jackson2ObjectMapperBuilder().createXmlMapper(true); return new Jackson2ObjectMapperBuilder().createXmlMapper(true);
} }
/**
* Obtain a {@link Jackson2ObjectMapperBuilder} in order to customize the {@link ObjectMapper} parameter.
*/
public static Jackson2ObjectMapperBuilder instance(ObjectMapper objectMapper) {
return new Jackson2ObjectMapperBuilder(objectMapper);
}
private static ClassLoader getClassLoader() {
ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
Assert.state(classLoader != null, "No classloader available");
return classLoader;
}
/** /**
* If set to true and no custom {@link ObjectMapper} has been set, a {@link XmlMapper} * If set to {@code true}, an {@link XmlMapper} will be created using its
* will be created using its default constructor. * default constructor. This is only applicable to {@link #build()} calls,
* not to {@link #configure} calls.
*/ */
public Jackson2ObjectMapperBuilder createXmlMapper(boolean createXmlMapper) { public Jackson2ObjectMapperBuilder createXmlMapper(boolean createXmlMapper) {
this.createXmlMapper = createXmlMapper; this.createXmlMapper = createXmlMapper;
@ -167,6 +154,15 @@ public class Jackson2ObjectMapperBuilder {
return this; return this;
} }
/**
* Specify a {@link com.fasterxml.jackson.databind.PropertyNamingStrategy} to
* configure the {@link ObjectMapper} with.
*/
public Jackson2ObjectMapperBuilder propertyNamingStrategy(PropertyNamingStrategy propertyNamingStrategy) {
this.propertyNamingStrategy = propertyNamingStrategy;
return this;
}
/** /**
* Set a custom inclusion strategy for serialization. * Set a custom inclusion strategy for serialization.
* @see com.fasterxml.jackson.annotation.JsonInclude.Include * @see com.fasterxml.jackson.annotation.JsonInclude.Include
@ -186,8 +182,9 @@ public class Jackson2ObjectMapperBuilder {
if (serializers != null) { if (serializers != null) {
for (JsonSerializer<?> serializer : serializers) { for (JsonSerializer<?> serializer : serializers) {
Class<?> handledType = serializer.handledType(); Class<?> handledType = serializer.handledType();
Assert.isTrue(handledType != null && handledType != Object.class, if (handledType == null || handledType == Object.class) {
"Unknown handled type in " + serializer.getClass().getName()); throw new IllegalArgumentException("Unknown handled type in " + serializer.getClass().getName());
}
this.serializers.put(serializer.handledType(), serializer); this.serializers.put(serializer.handledType(), serializer);
} }
} }
@ -234,18 +231,18 @@ public class Jackson2ObjectMapperBuilder {
} }
/** /**
* Shortcut for {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} option. * Shortcut for {@link MapperFeature#DEFAULT_VIEW_INCLUSION} option.
*/ */
public Jackson2ObjectMapperBuilder failOnUnknownProperties(boolean failOnUnknownProperties) { public Jackson2ObjectMapperBuilder defaultViewInclusion(boolean defaultViewInclusion) {
this.features.put(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownProperties); this.features.put(MapperFeature.DEFAULT_VIEW_INCLUSION, defaultViewInclusion);
return this; return this;
} }
/** /**
* Shortcut for {@link MapperFeature#DEFAULT_VIEW_INCLUSION} option. * Shortcut for {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} option.
*/ */
public Jackson2ObjectMapperBuilder defaultViewInclusion(boolean defaultViewInclusion) { public Jackson2ObjectMapperBuilder failOnUnknownProperties(boolean failOnUnknownProperties) {
this.features.put(MapperFeature.DEFAULT_VIEW_INCLUSION, defaultViewInclusion); this.features.put(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownProperties);
return this; return this;
} }
@ -341,93 +338,104 @@ public class Jackson2ObjectMapperBuilder {
} }
/** /**
* Specify a {@link com.fasterxml.jackson.databind.PropertyNamingStrategy} to * Set the ClassLoader to use for loading Jackson extension modules.
* configure the {@link ObjectMapper} with.
*/ */
public Jackson2ObjectMapperBuilder propertyNamingStrategy(PropertyNamingStrategy propertyNamingStrategy) { public Jackson2ObjectMapperBuilder moduleClassLoader(ClassLoader moduleClassLoader) {
this.propertyNamingStrategy = propertyNamingStrategy; this.moduleClassLoader = moduleClassLoader;
return this; return this;
} }
/** /**
* Build a new {@link T} instance. * Build a new {@link ObjectMapper} instance.
* <p>Each build operation produces an independent {@link ObjectMapper} instance.
* The builder's settings can get modified, with a subsequent build operation
* then producing a new {@link ObjectMapper} based on the most recent settings.
* @return the newly built ObjectMapper
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T extends ObjectMapper> T build() { public <T extends ObjectMapper> T build() {
if (this.objectMapper == null) { ObjectMapper objectMapper;
if(this.createXmlMapper) { if (this.createXmlMapper) {
ClassLoader cl = getClassLoader(); try {
try { Class<? extends ObjectMapper> xmlMapper = (Class<? extends ObjectMapper>)
Class<? extends ObjectMapper> xmlMapper = (Class<? extends ObjectMapper>) ClassUtils.forName("com.fasterxml.jackson.dataformat.xml.XmlMapper", this.moduleClassLoader);
cl.loadClass("com.fasterxml.jackson.dataformat.xml.XmlMapper"); objectMapper = BeanUtils.instantiate(xmlMapper);
this.objectMapper = BeanUtils.instantiate(xmlMapper);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Could not instantiate XmlMapper, it has not been found on the classpath");
}
} }
else { catch (ClassNotFoundException ex) {
this.objectMapper = new ObjectMapper(); throw new IllegalStateException("Could not instantiate XmlMapper - not found on classpath");
} }
} }
else {
objectMapper = new ObjectMapper();
}
configure(objectMapper);
return (T) objectMapper;
}
/**
* Configure an existing {@link ObjectMapper} instance with this builder's
* settings. This can be applied to any number of {@code ObjectMappers}.
* @param objectMapper the ObjectMapper to configure
*/
public void configure(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
if (this.dateFormat != null) { if (this.dateFormat != null) {
this.objectMapper.setDateFormat(this.dateFormat); objectMapper.setDateFormat(this.dateFormat);
} }
if (this.annotationIntrospector != null) { if (this.annotationIntrospector != null) {
this.objectMapper.setAnnotationIntrospector(this.annotationIntrospector); objectMapper.setAnnotationIntrospector(this.annotationIntrospector);
} }
if (this.serializationInclusion != null) { if (this.serializationInclusion != null) {
this.objectMapper.setSerializationInclusion(this.serializationInclusion); objectMapper.setSerializationInclusion(this.serializationInclusion);
} }
if (!this.serializers.isEmpty() || !this.deserializers.isEmpty()) { if (!this.serializers.isEmpty() || !this.deserializers.isEmpty()) {
SimpleModule module = new SimpleModule(); SimpleModule module = new SimpleModule();
addSerializers(module); addSerializers(module);
addDeserializers(module); addDeserializers(module);
this.objectMapper.registerModule(module); objectMapper.registerModule(module);
} }
if(!features.containsKey(MapperFeature.DEFAULT_VIEW_INCLUSION)) { if (!this.features.containsKey(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
configureFeature(MapperFeature.DEFAULT_VIEW_INCLUSION, false); configureFeature(objectMapper, MapperFeature.DEFAULT_VIEW_INCLUSION, false);
} }
if(!features.containsKey(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) { if (!this.features.containsKey(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
configureFeature(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); configureFeature(objectMapper, DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
} }
for (Object feature : this.features.keySet()) { for (Object feature : this.features.keySet()) {
configureFeature(feature, this.features.get(feature)); configureFeature(objectMapper, feature, this.features.get(feature));
} }
if (this.modules != null) { if (this.modules != null) {
// Complete list of modules given // Complete list of modules given
for (Module module : this.modules) { for (Module module : this.modules) {
// Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules // Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules
this.objectMapper.registerModule(module); objectMapper.registerModule(module);
} }
} }
else { else {
// Combination of modules by class names specified and class presence in the classpath // Combination of modules by class names specified and class presence in the classpath
if (this.modulesToInstall != null) { if (this.modulesToInstall != null) {
for (Class<? extends Module> module : this.modulesToInstall) { for (Class<? extends Module> module : this.modulesToInstall) {
this.objectMapper.registerModule(BeanUtils.instantiate(module)); objectMapper.registerModule(BeanUtils.instantiate(module));
} }
} }
if (this.findModulesViaServiceLoader) { if (this.findModulesViaServiceLoader) {
// Jackson 2.2+ // Jackson 2.2+
this.objectMapper.registerModules(ObjectMapper.findModules(getClassLoader())); objectMapper.registerModules(ObjectMapper.findModules(this.moduleClassLoader));
} }
else { else {
registerWellKnownModulesIfAvailable(); registerWellKnownModulesIfAvailable(objectMapper);
} }
} }
if (this.propertyNamingStrategy != null) { if (this.propertyNamingStrategy != null) {
this.objectMapper.setPropertyNamingStrategy(this.propertyNamingStrategy); objectMapper.setPropertyNamingStrategy(this.propertyNamingStrategy);
} }
return (T)this.objectMapper;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -444,21 +452,21 @@ public class Jackson2ObjectMapperBuilder {
} }
} }
private void configureFeature(Object feature, boolean enabled) { private void configureFeature(ObjectMapper objectMapper, Object feature, boolean enabled) {
if (feature instanceof JsonParser.Feature) { if (feature instanceof JsonParser.Feature) {
this.objectMapper.configure((JsonParser.Feature) feature, enabled); objectMapper.configure((JsonParser.Feature) feature, enabled);
} }
else if (feature instanceof JsonGenerator.Feature) { else if (feature instanceof JsonGenerator.Feature) {
this.objectMapper.configure((JsonGenerator.Feature) feature, enabled); objectMapper.configure((JsonGenerator.Feature) feature, enabled);
} }
else if (feature instanceof SerializationFeature) { else if (feature instanceof SerializationFeature) {
this.objectMapper.configure((SerializationFeature) feature, enabled); objectMapper.configure((SerializationFeature) feature, enabled);
} }
else if (feature instanceof DeserializationFeature) { else if (feature instanceof DeserializationFeature) {
this.objectMapper.configure((DeserializationFeature) feature, enabled); objectMapper.configure((DeserializationFeature) feature, enabled);
} }
else if (feature instanceof MapperFeature) { else if (feature instanceof MapperFeature) {
this.objectMapper.configure((MapperFeature) feature, enabled); objectMapper.configure((MapperFeature) feature, enabled);
} }
else { else {
throw new FatalBeanException("Unknown feature class: " + feature.getClass().getName()); throw new FatalBeanException("Unknown feature class: " + feature.getClass().getName());
@ -466,25 +474,24 @@ public class Jackson2ObjectMapperBuilder {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void registerWellKnownModulesIfAvailable() { private void registerWellKnownModulesIfAvailable(ObjectMapper objectMapper) {
ClassLoader cl = getClassLoader();
// Java 8 java.time package present? // Java 8 java.time package present?
if (ClassUtils.isPresent("java.time.LocalDate", cl)) { if (ClassUtils.isPresent("java.time.LocalDate", this.moduleClassLoader)) {
try { try {
Class<? extends Module> jsr310Module = (Class<? extends Module>) Class<? extends Module> jsr310Module = (Class<? extends Module>)
cl.loadClass("com.fasterxml.jackson.datatype.jsr310.JSR310Module"); ClassUtils.forName("com.fasterxml.jackson.datatype.jsr310.JSR310Module", this.moduleClassLoader);
this.objectMapper.registerModule(BeanUtils.instantiate(jsr310Module)); objectMapper.registerModule(BeanUtils.instantiate(jsr310Module));
} }
catch (ClassNotFoundException ex) { catch (ClassNotFoundException ex) {
// jackson-datatype-jsr310 not available // jackson-datatype-jsr310 not available
} }
} }
// Joda-Time present? // Joda-Time present?
if (ClassUtils.isPresent("org.joda.time.LocalDate", cl)) { if (ClassUtils.isPresent("org.joda.time.LocalDate", this.moduleClassLoader)) {
try { try {
Class<? extends Module> jodaModule = (Class<? extends Module>) Class<? extends Module> jodaModule = (Class<? extends Module>)
cl.loadClass("com.fasterxml.jackson.datatype.joda.JodaModule"); ClassUtils.forName("com.fasterxml.jackson.datatype.joda.JodaModule", this.moduleClassLoader);
this.objectMapper.registerModule(BeanUtils.instantiate(jodaModule)); objectMapper.registerModule(BeanUtils.instantiate(jodaModule));
} }
catch (ClassNotFoundException ex) { catch (ClassNotFoundException ex) {
// jackson-datatype-joda not available // jackson-datatype-joda not available

272
spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBean.java

@ -18,15 +18,10 @@ package org.springframework.http.converter.json;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonDeserializer;
@ -36,16 +31,11 @@ import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.FatalBeanException;
import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/** /**
* A {@link FactoryBean} for creating a Jackson 2.x {@link ObjectMapper} (default) or * A {@link FactoryBean} for creating a Jackson 2.x {@link ObjectMapper} (default) or
@ -54,8 +44,8 @@ import org.springframework.util.ClassUtils;
* *
* <p>It customizes Jackson defaults properties with the following ones: * <p>It customizes Jackson defaults properties with the following ones:
* <ul> * <ul>
* <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li> * <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li>
* <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li> * <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li>
* </ul> * </ul>
* *
* <p>Example usage with * <p>Example usage with
@ -132,31 +122,9 @@ import org.springframework.util.ClassUtils;
*/ */
public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper>, BeanClassLoaderAware, InitializingBean { public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper>, BeanClassLoaderAware, InitializingBean {
private ObjectMapper objectMapper; private final Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
private boolean createXmlMapper = false;
private DateFormat dateFormat;
private JsonInclude.Include serializationInclusion;
private AnnotationIntrospector annotationIntrospector;
private final Map<Class<?>, JsonSerializer<?>> serializers = new LinkedHashMap<Class<?>, JsonSerializer<?>>();
private final Map<Class<?>, JsonDeserializer<?>> deserializers = new LinkedHashMap<Class<?>, JsonDeserializer<?>>();
private final Map<Object, Boolean> features = new HashMap<Object, Boolean>();
private List<Module> modules;
private Class<? extends Module>[] modulesToInstall;
private boolean findModulesViaServiceLoader;
private PropertyNamingStrategy propertyNamingStrategy; private ObjectMapper objectMapper;
private ClassLoader beanClassLoader;
/** /**
@ -170,9 +138,10 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
/** /**
* If set to true and no custom {@link ObjectMapper} has been set, a {@link XmlMapper} * If set to true and no custom {@link ObjectMapper} has been set, a {@link XmlMapper}
* will be created using its default constructor. * will be created using its default constructor.
* @since 4.1
*/ */
public void setCreateXmlMapper(boolean createXmlMapper) { public void setCreateXmlMapper(boolean createXmlMapper) {
this.createXmlMapper = createXmlMapper; this.builder.createXmlMapper(createXmlMapper);
} }
/** /**
@ -182,7 +151,7 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
* @see #setSimpleDateFormat(String) * @see #setSimpleDateFormat(String)
*/ */
public void setDateFormat(DateFormat dateFormat) { public void setDateFormat(DateFormat dateFormat) {
this.dateFormat = dateFormat; this.builder.dateFormat(dateFormat);
} }
/** /**
@ -192,14 +161,23 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
* @see #setDateFormat(DateFormat) * @see #setDateFormat(DateFormat)
*/ */
public void setSimpleDateFormat(String format) { public void setSimpleDateFormat(String format) {
this.dateFormat = new SimpleDateFormat(format); this.builder.simpleDateFormat(format);
} }
/** /**
* Set an {@link AnnotationIntrospector} for both serialization and deserialization. * Set an {@link AnnotationIntrospector} for both serialization and deserialization.
*/ */
public void setAnnotationIntrospector(AnnotationIntrospector annotationIntrospector) { public void setAnnotationIntrospector(AnnotationIntrospector annotationIntrospector) {
this.annotationIntrospector = annotationIntrospector; this.builder.annotationIntrospector(annotationIntrospector);
}
/**
* Specify a {@link com.fasterxml.jackson.databind.PropertyNamingStrategy} to
* configure the {@link ObjectMapper} with.
* @since 4.0.2
*/
public void setPropertyNamingStrategy(PropertyNamingStrategy propertyNamingStrategy) {
this.builder.propertyNamingStrategy(propertyNamingStrategy);
} }
/** /**
@ -207,7 +185,7 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
* @see com.fasterxml.jackson.annotation.JsonInclude.Include * @see com.fasterxml.jackson.annotation.JsonInclude.Include
*/ */
public void setSerializationInclusion(JsonInclude.Include serializationInclusion) { public void setSerializationInclusion(JsonInclude.Include serializationInclusion) {
this.serializationInclusion = serializationInclusion; this.builder.serializationInclusion(serializationInclusion);
} }
/** /**
@ -217,14 +195,7 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
* @see #setSerializersByType(Map) * @see #setSerializersByType(Map)
*/ */
public void setSerializers(JsonSerializer<?>... serializers) { public void setSerializers(JsonSerializer<?>... serializers) {
if (serializers != null) { this.builder.serializers(serializers);
for (JsonSerializer<?> serializer : serializers) {
Class<?> handledType = serializer.handledType();
Assert.isTrue(handledType != null && handledType != Object.class,
"Unknown handled type in " + serializer.getClass().getName());
this.serializers.put(serializer.handledType(), serializer);
}
}
} }
/** /**
@ -232,25 +203,21 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
* @see #setSerializers(JsonSerializer...) * @see #setSerializers(JsonSerializer...)
*/ */
public void setSerializersByType(Map<Class<?>, JsonSerializer<?>> serializers) { public void setSerializersByType(Map<Class<?>, JsonSerializer<?>> serializers) {
if (serializers != null) { this.builder.serializersByType(serializers);
this.serializers.putAll(serializers);
}
} }
/** /**
* Configure custom deserializers for the given types. * Configure custom deserializers for the given types.
*/ */
public void setDeserializersByType(Map<Class<?>, JsonDeserializer<?>> deserializers) { public void setDeserializersByType(Map<Class<?>, JsonDeserializer<?>> deserializers) {
if (deserializers != null) { this.builder.deserializersByType(deserializers);
this.deserializers.putAll(deserializers);
}
} }
/** /**
* Shortcut for {@link MapperFeature#AUTO_DETECT_FIELDS} option. * Shortcut for {@link MapperFeature#AUTO_DETECT_FIELDS} option.
*/ */
public void setAutoDetectFields(boolean autoDetectFields) { public void setAutoDetectFields(boolean autoDetectFields) {
this.features.put(MapperFeature.AUTO_DETECT_FIELDS, autoDetectFields); this.builder.autoDetectFields(autoDetectFields);
} }
/** /**
@ -258,36 +225,37 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
* {@link MapperFeature#AUTO_DETECT_GETTERS} option. * {@link MapperFeature#AUTO_DETECT_GETTERS} option.
*/ */
public void setAutoDetectGettersSetters(boolean autoDetectGettersSetters) { public void setAutoDetectGettersSetters(boolean autoDetectGettersSetters) {
this.features.put(MapperFeature.AUTO_DETECT_GETTERS, autoDetectGettersSetters); this.builder.autoDetectGettersSetters(autoDetectGettersSetters);
this.features.put(MapperFeature.AUTO_DETECT_SETTERS, autoDetectGettersSetters);
} }
/** /**
* Shortcut for {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} option. * Shortcut for {@link MapperFeature#DEFAULT_VIEW_INCLUSION} option.
* @since 4.1
*/ */
public void setFailOnUnknownProperties(boolean failOnUnknownProperties) { public void setDefaultViewInclusion(boolean defaultViewInclusion) {
this.features.put(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownProperties); this.builder.defaultViewInclusion(defaultViewInclusion);
} }
/** /**
* Shortcut for {@link MapperFeature#DEFAULT_VIEW_INCLUSION} option. * Shortcut for {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} option.
* @since 4.1.1
*/ */
public void setDefaultViewInclusion(boolean defaultViewInclusion) { public void setFailOnUnknownProperties(boolean failOnUnknownProperties) {
this.features.put(MapperFeature.DEFAULT_VIEW_INCLUSION, defaultViewInclusion); this.builder.failOnUnknownProperties(failOnUnknownProperties);
} }
/** /**
* Shortcut for {@link SerializationFeature#FAIL_ON_EMPTY_BEANS} option. * Shortcut for {@link SerializationFeature#FAIL_ON_EMPTY_BEANS} option.
*/ */
public void setFailOnEmptyBeans(boolean failOnEmptyBeans) { public void setFailOnEmptyBeans(boolean failOnEmptyBeans) {
this.features.put(SerializationFeature.FAIL_ON_EMPTY_BEANS, failOnEmptyBeans); this.builder.failOnEmptyBeans(failOnEmptyBeans);
} }
/** /**
* Shortcut for {@link SerializationFeature#INDENT_OUTPUT} option. * Shortcut for {@link SerializationFeature#INDENT_OUTPUT} option.
*/ */
public void setIndentOutput(boolean indentOutput) { public void setIndentOutput(boolean indentOutput) {
this.features.put(SerializationFeature.INDENT_OUTPUT, indentOutput); this.builder.indentOutput(indentOutput);
} }
/** /**
@ -299,11 +267,7 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
* @see com.fasterxml.jackson.databind.MapperFeature * @see com.fasterxml.jackson.databind.MapperFeature
*/ */
public void setFeaturesToEnable(Object... featuresToEnable) { public void setFeaturesToEnable(Object... featuresToEnable) {
if (featuresToEnable != null) { this.builder.featuresToEnable(featuresToEnable);
for (Object feature : featuresToEnable) {
this.features.put(feature, Boolean.TRUE);
}
}
} }
/** /**
@ -315,11 +279,7 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
* @see com.fasterxml.jackson.databind.MapperFeature * @see com.fasterxml.jackson.databind.MapperFeature
*/ */
public void setFeaturesToDisable(Object... featuresToDisable) { public void setFeaturesToDisable(Object... featuresToDisable) {
if (featuresToDisable != null) { this.builder.featuresToDisable(featuresToDisable);
for (Object feature : featuresToDisable) {
this.features.put(feature, Boolean.FALSE);
}
}
} }
/** /**
@ -333,7 +293,7 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
* @see com.fasterxml.jackson.databind.Module * @see com.fasterxml.jackson.databind.Module
*/ */
public void setModules(List<Module> modules) { public void setModules(List<Module> modules) {
this.modules = new LinkedList<Module>(modules); this.builder.modules(modules);
} }
/** /**
@ -347,7 +307,7 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
* @see com.fasterxml.jackson.databind.Module * @see com.fasterxml.jackson.databind.Module
*/ */
public void setModulesToInstall(Class<? extends Module>... modules) { public void setModulesToInstall(Class<? extends Module>... modules) {
this.modulesToInstall = modules; this.builder.modulesToInstall(modules);
} }
/** /**
@ -360,165 +320,23 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
* @see com.fasterxml.jackson.databind.ObjectMapper#findModules() * @see com.fasterxml.jackson.databind.ObjectMapper#findModules()
*/ */
public void setFindModulesViaServiceLoader(boolean findModules) { public void setFindModulesViaServiceLoader(boolean findModules) {
this.findModulesViaServiceLoader = findModules; this.builder.findModulesViaServiceLoader(findModules);
}
/**
* Specify a {@link com.fasterxml.jackson.databind.PropertyNamingStrategy} to
* configure the {@link ObjectMapper} with.
* @since 4.0.2
*/
public void setPropertyNamingStrategy(PropertyNamingStrategy propertyNamingStrategy) {
this.propertyNamingStrategy = propertyNamingStrategy;
} }
@Override @Override
public void setBeanClassLoader(ClassLoader beanClassLoader) { public void setBeanClassLoader(ClassLoader beanClassLoader) {
this.beanClassLoader = beanClassLoader; this.builder.moduleClassLoader(beanClassLoader);
} }
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void afterPropertiesSet() { public void afterPropertiesSet() {
if (this.objectMapper == null) { if (this.objectMapper != null) {
if(this.createXmlMapper) { this.builder.configure(this.objectMapper);
ClassLoader cl = this.beanClassLoader;
if (cl == null) {
cl = getClass().getClassLoader();
}
try {
Class<? extends ObjectMapper> xmlMapper = (Class<? extends ObjectMapper>)
cl.loadClass("com.fasterxml.jackson.dataformat.xml.XmlMapper");
this.objectMapper = BeanUtils.instantiate(xmlMapper);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Could not instantiate XmlMapper", ex);
}
}
else {
this.objectMapper = new ObjectMapper();
}
}
if (this.dateFormat != null) {
this.objectMapper.setDateFormat(this.dateFormat);
}
if (this.annotationIntrospector != null) {
this.objectMapper.setAnnotationIntrospector(this.annotationIntrospector);
}
if (this.serializationInclusion != null) {
this.objectMapper.setSerializationInclusion(this.serializationInclusion);
}
if (!this.serializers.isEmpty() || !this.deserializers.isEmpty()) {
SimpleModule module = new SimpleModule();
addSerializers(module);
addDeserializers(module);
this.objectMapper.registerModule(module);
}
if(!features.containsKey(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
configureFeature(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
}
if(!features.containsKey(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
configureFeature(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
for (Object feature : this.features.keySet()) {
configureFeature(feature, this.features.get(feature));
}
if (this.modules != null) {
// Complete list of modules given
for (Module module : this.modules) {
// Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules
this.objectMapper.registerModule(module);
}
} }
else { else {
// Combination of modules by class names specified and class presence in the classpath this.objectMapper = this.builder.build();
if (this.modulesToInstall != null) {
for (Class<? extends Module> module : this.modulesToInstall) {
this.objectMapper.registerModule(BeanUtils.instantiate(module));
}
}
if (this.findModulesViaServiceLoader) {
// Jackson 2.2+
this.objectMapper.registerModules(ObjectMapper.findModules(this.beanClassLoader));
}
else {
registerWellKnownModulesIfAvailable();
}
}
if (this.propertyNamingStrategy != null) {
this.objectMapper.setPropertyNamingStrategy(this.propertyNamingStrategy);
}
}
@SuppressWarnings("unchecked")
private <T> void addSerializers(SimpleModule module) {
for (Class<?> type : this.serializers.keySet()) {
module.addSerializer((Class<? extends T>) type, (JsonSerializer<T>) this.serializers.get(type));
}
}
@SuppressWarnings("unchecked")
private <T> void addDeserializers(SimpleModule module) {
for (Class<?> type : this.deserializers.keySet()) {
module.addDeserializer((Class<T>) type, (JsonDeserializer<? extends T>) this.deserializers.get(type));
}
}
private void configureFeature(Object feature, boolean enabled) {
if (feature instanceof JsonParser.Feature) {
this.objectMapper.configure((JsonParser.Feature) feature, enabled);
}
else if (feature instanceof JsonGenerator.Feature) {
this.objectMapper.configure((JsonGenerator.Feature) feature, enabled);
}
else if (feature instanceof SerializationFeature) {
this.objectMapper.configure((SerializationFeature) feature, enabled);
}
else if (feature instanceof DeserializationFeature) {
this.objectMapper.configure((DeserializationFeature) feature, enabled);
}
else if (feature instanceof MapperFeature) {
this.objectMapper.configure((MapperFeature) feature, enabled);
}
else {
throw new FatalBeanException("Unknown feature class: " + feature.getClass().getName());
}
}
@SuppressWarnings("unchecked")
private void registerWellKnownModulesIfAvailable() {
ClassLoader cl = this.beanClassLoader;
if (cl == null) {
cl = getClass().getClassLoader();
}
// Java 8 java.time package present?
if (ClassUtils.isPresent("java.time.LocalDate", cl)) {
try {
Class<? extends Module> jsr310Module = (Class<? extends Module>)
cl.loadClass("com.fasterxml.jackson.datatype.jsr310.JSR310Module");
this.objectMapper.registerModule(BeanUtils.instantiate(jsr310Module));
}
catch (ClassNotFoundException ex) {
// jackson-datatype-jsr310 not available
}
}
// Joda-Time present?
if (ClassUtils.isPresent("org.joda.time.LocalDate", cl)) {
try {
Class<? extends Module> jodaModule = (Class<? extends Module>)
cl.loadClass("com.fasterxml.jackson.datatype.joda.JodaModule");
this.objectMapper.registerModule(BeanUtils.instantiate(jodaModule));
}
catch (ClassNotFoundException ex) {
// jackson-datatype-joda not available
}
} }
} }
@ -533,7 +351,7 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
@Override @Override
public Class<?> getObjectType() { public Class<?> getObjectType() {
return (this.objectMapper != null) ? this.objectMapper.getClass() : null; return (this.objectMapper != null ? this.objectMapper.getClass() : null);
} }
@Override @Override

14
spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilderTests.java

@ -51,7 +51,6 @@ import org.junit.Test;
import org.springframework.beans.FatalBeanException; import org.springframework.beans.FatalBeanException;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
/** /**
* Test class for {@link Jackson2ObjectMapperBuilder}. * Test class for {@link Jackson2ObjectMapperBuilder}.
@ -195,18 +194,16 @@ public class Jackson2ObjectMapperBuilderTests {
public void completeSetup() { public void completeSetup() {
NopAnnotationIntrospector annotationIntrospector = NopAnnotationIntrospector.instance; NopAnnotationIntrospector annotationIntrospector = NopAnnotationIntrospector.instance;
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.instance(new ObjectMapper()); Map<Class<?>, JsonDeserializer<?>> deserializers = new HashMap<Class<?>, JsonDeserializer<?>>();
Map<Class<?>, JsonDeserializer<?>>
deserializers = new HashMap<Class<?>, JsonDeserializer<?>>();
deserializers.put(Date.class, new DateDeserializers.DateDeserializer()); deserializers.put(Date.class, new DateDeserializers.DateDeserializer());
JsonSerializer<Class<?>> serializer1 = new ClassSerializer(); JsonSerializer<Class<?>> serializer1 = new ClassSerializer();
JsonSerializer<Number> serializer2 = new NumberSerializer(); JsonSerializer<Number> serializer2 = new NumberSerializer();
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
builder.serializers(serializer1); builder.serializers(serializer1);
builder.serializersByType(Collections builder.serializersByType(Collections.<Class<?>, JsonSerializer<?>>singletonMap(Boolean.class, serializer2));
.<Class<?>, JsonSerializer<?>>singletonMap(Boolean.class, serializer2));
builder.deserializersByType(deserializers); builder.deserializersByType(deserializers);
builder.annotationIntrospector(annotationIntrospector); builder.annotationIntrospector(annotationIntrospector);
@ -221,7 +218,8 @@ public class Jackson2ObjectMapperBuilderTests {
builder.serializationInclusion(JsonInclude.Include.NON_NULL); builder.serializationInclusion(JsonInclude.Include.NON_NULL);
ObjectMapper objectMapper = builder.build(); ObjectMapper objectMapper = new ObjectMapper();
builder.configure(objectMapper);
assertTrue(getSerializerFactoryConfig(objectMapper).hasSerializers()); assertTrue(getSerializerFactoryConfig(objectMapper).hasSerializers());
assertTrue(getDeserializerFactoryConfig(objectMapper).hasDeserializers()); assertTrue(getDeserializerFactoryConfig(objectMapper).hasDeserializers());

2
spring-web/src/test/java/org/springframework/http/converter/json/Jackson2ObjectMapperFactoryBeanTests.java

@ -65,11 +65,13 @@ public class Jackson2ObjectMapperFactoryBeanTests {
private Jackson2ObjectMapperFactoryBean factory; private Jackson2ObjectMapperFactoryBean factory;
@Before @Before
public void setUp() { public void setUp() {
factory = new Jackson2ObjectMapperFactoryBean(); factory = new Jackson2ObjectMapperFactoryBean();
} }
@Test @Test
public void settersWithNullValues() { public void settersWithNullValues() {
// Should not crash: // Should not crash:

Loading…
Cancel
Save