Skip to content

Commit d778037

Browse files
committed
Jackson2ObjectMapperFactoryBean builds on revised Jackson2ObjectMapperBuilder now
Issue: SPR-12243
1 parent 69998e3 commit d778037

File tree

4 files changed

+150
-325
lines changed

4 files changed

+150
-325
lines changed

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

Lines changed: 97 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@
4747
/**
4848
* A builder used to create {@link ObjectMapper} instances with a fluent API.
4949
*
50-
* <p>It customizes Jackson defaults properties with the following ones:
50+
* <p>It customizes Jackson's default properties with the following ones:
5151
* <ul>
52-
* <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li>
53-
* <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li>
52+
* <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li>
53+
* <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li>
5454
* </ul>
5555
*
5656
* <p>Note that Jackson's JSR-310 and Joda-Time support modules will be registered automatically
@@ -59,28 +59,27 @@
5959
* <p>Tested against Jackson 2.2 and 2.3; compatible with Jackson 2.0 and higher.
6060
*
6161
* @author Sebastien Deleuze
62+
* @author Juergen Hoeller
6263
* @since 4.1.1
64+
* @see #build()
65+
* @see #configure(ObjectMapper)
66+
* @see Jackson2ObjectMapperFactoryBean
6367
*/
6468
public class Jackson2ObjectMapperBuilder {
6569

66-
private static final boolean jackson2XmlPresent =
67-
ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", getClassLoader());
68-
69-
private ObjectMapper objectMapper;
70-
7170
private boolean createXmlMapper = false;
7271

7372
private DateFormat dateFormat;
7473

75-
private JsonInclude.Include serializationInclusion;
76-
7774
private AnnotationIntrospector annotationIntrospector;
7875

79-
private final Map<Class<?>, JsonSerializer<?>>
80-
serializers = new LinkedHashMap<Class<?>, JsonSerializer<?>>();
76+
private PropertyNamingStrategy propertyNamingStrategy;
77+
78+
private JsonInclude.Include serializationInclusion;
79+
80+
private final Map<Class<?>, JsonSerializer<?>> serializers = new LinkedHashMap<Class<?>, JsonSerializer<?>>();
8181

82-
private final Map<Class<?>, JsonDeserializer<?>>
83-
deserializers = new LinkedHashMap<Class<?>, JsonDeserializer<?>>();
82+
private final Map<Class<?>, JsonDeserializer<?>> deserializers = new LinkedHashMap<Class<?>, JsonDeserializer<?>>();
8483

8584
private final Map<Object, Boolean> features = new HashMap<Object, Boolean>();
8685

@@ -90,47 +89,35 @@ public class Jackson2ObjectMapperBuilder {
9089

9190
private boolean findModulesViaServiceLoader;
9291

93-
private PropertyNamingStrategy propertyNamingStrategy;
92+
private ClassLoader moduleClassLoader = getClass().getClassLoader();
9493

9594

9695
private Jackson2ObjectMapperBuilder() {
9796
}
9897

99-
private Jackson2ObjectMapperBuilder(ObjectMapper objectMapper) {
100-
this.objectMapper = objectMapper;
101-
}
10298

10399
/**
104-
* Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to build an {@link ObjectMapper} instance.
100+
* Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to
101+
* build an {@link ObjectMapper} instance.
105102
*/
106103
public static Jackson2ObjectMapperBuilder json() {
107104
return new Jackson2ObjectMapperBuilder();
108105
}
109106

110107
/**
111-
* Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to build a {@link XmlMapper} instance.
108+
* Obtain a {@link Jackson2ObjectMapperBuilder} instance in order to
109+
* build a {@link XmlMapper} instance.
112110
*/
113111
@SuppressWarnings("unchecked")
114112
public static Jackson2ObjectMapperBuilder xml() {
115113
return new Jackson2ObjectMapperBuilder().createXmlMapper(true);
116114
}
117115

118-
/**
119-
* Obtain a {@link Jackson2ObjectMapperBuilder} in order to customize the {@link ObjectMapper} parameter.
120-
*/
121-
public static Jackson2ObjectMapperBuilder instance(ObjectMapper objectMapper) {
122-
return new Jackson2ObjectMapperBuilder(objectMapper);
123-
}
124-
125-
private static ClassLoader getClassLoader() {
126-
ClassLoader classLoader = ClassUtils.getDefaultClassLoader();
127-
Assert.state(classLoader != null, "No classloader available");
128-
return classLoader;
129-
}
130116

131117
/**
132-
* If set to true and no custom {@link ObjectMapper} has been set, a {@link XmlMapper}
133-
* will be created using its default constructor.
118+
* If set to {@code true}, an {@link XmlMapper} will be created using its
119+
* default constructor. This is only applicable to {@link #build()} calls,
120+
* not to {@link #configure} calls.
134121
*/
135122
public Jackson2ObjectMapperBuilder createXmlMapper(boolean createXmlMapper) {
136123
this.createXmlMapper = createXmlMapper;
@@ -167,6 +154,15 @@ public Jackson2ObjectMapperBuilder annotationIntrospector(AnnotationIntrospector
167154
return this;
168155
}
169156

157+
/**
158+
* Specify a {@link com.fasterxml.jackson.databind.PropertyNamingStrategy} to
159+
* configure the {@link ObjectMapper} with.
160+
*/
161+
public Jackson2ObjectMapperBuilder propertyNamingStrategy(PropertyNamingStrategy propertyNamingStrategy) {
162+
this.propertyNamingStrategy = propertyNamingStrategy;
163+
return this;
164+
}
165+
170166
/**
171167
* Set a custom inclusion strategy for serialization.
172168
* @see com.fasterxml.jackson.annotation.JsonInclude.Include
@@ -186,8 +182,9 @@ public Jackson2ObjectMapperBuilder serializers(JsonSerializer<?>... serializers)
186182
if (serializers != null) {
187183
for (JsonSerializer<?> serializer : serializers) {
188184
Class<?> handledType = serializer.handledType();
189-
Assert.isTrue(handledType != null && handledType != Object.class,
190-
"Unknown handled type in " + serializer.getClass().getName());
185+
if (handledType == null || handledType == Object.class) {
186+
throw new IllegalArgumentException("Unknown handled type in " + serializer.getClass().getName());
187+
}
191188
this.serializers.put(serializer.handledType(), serializer);
192189
}
193190
}
@@ -234,18 +231,18 @@ public Jackson2ObjectMapperBuilder autoDetectGettersSetters(boolean autoDetectGe
234231
}
235232

236233
/**
237-
* Shortcut for {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} option.
234+
* Shortcut for {@link MapperFeature#DEFAULT_VIEW_INCLUSION} option.
238235
*/
239-
public Jackson2ObjectMapperBuilder failOnUnknownProperties(boolean failOnUnknownProperties) {
240-
this.features.put(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownProperties);
236+
public Jackson2ObjectMapperBuilder defaultViewInclusion(boolean defaultViewInclusion) {
237+
this.features.put(MapperFeature.DEFAULT_VIEW_INCLUSION, defaultViewInclusion);
241238
return this;
242239
}
243240

244241
/**
245-
* Shortcut for {@link MapperFeature#DEFAULT_VIEW_INCLUSION} option.
242+
* Shortcut for {@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} option.
246243
*/
247-
public Jackson2ObjectMapperBuilder defaultViewInclusion(boolean defaultViewInclusion) {
248-
this.features.put(MapperFeature.DEFAULT_VIEW_INCLUSION, defaultViewInclusion);
244+
public Jackson2ObjectMapperBuilder failOnUnknownProperties(boolean failOnUnknownProperties) {
245+
this.features.put(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, failOnUnknownProperties);
249246
return this;
250247
}
251248

@@ -341,93 +338,104 @@ public Jackson2ObjectMapperBuilder findModulesViaServiceLoader(boolean findModul
341338
}
342339

343340
/**
344-
* Specify a {@link com.fasterxml.jackson.databind.PropertyNamingStrategy} to
345-
* configure the {@link ObjectMapper} with.
341+
* Set the ClassLoader to use for loading Jackson extension modules.
346342
*/
347-
public Jackson2ObjectMapperBuilder propertyNamingStrategy(PropertyNamingStrategy propertyNamingStrategy) {
348-
this.propertyNamingStrategy = propertyNamingStrategy;
343+
public Jackson2ObjectMapperBuilder moduleClassLoader(ClassLoader moduleClassLoader) {
344+
this.moduleClassLoader = moduleClassLoader;
349345
return this;
350346
}
351347

348+
352349
/**
353-
* Build a new {@link T} instance.
350+
* Build a new {@link ObjectMapper} instance.
351+
* <p>Each build operation produces an independent {@link ObjectMapper} instance.
352+
* The builder's settings can get modified, with a subsequent build operation
353+
* then producing a new {@link ObjectMapper} based on the most recent settings.
354+
* @return the newly built ObjectMapper
354355
*/
355356
@SuppressWarnings("unchecked")
356357
public <T extends ObjectMapper> T build() {
357-
if (this.objectMapper == null) {
358-
if(this.createXmlMapper) {
359-
ClassLoader cl = getClassLoader();
360-
try {
361-
Class<? extends ObjectMapper> xmlMapper = (Class<? extends ObjectMapper>)
362-
cl.loadClass("com.fasterxml.jackson.dataformat.xml.XmlMapper");
363-
this.objectMapper = BeanUtils.instantiate(xmlMapper);
364-
}
365-
catch (ClassNotFoundException ex) {
366-
throw new IllegalStateException("Could not instantiate XmlMapper, it has not been found on the classpath");
367-
}
358+
ObjectMapper objectMapper;
359+
if (this.createXmlMapper) {
360+
try {
361+
Class<? extends ObjectMapper> xmlMapper = (Class<? extends ObjectMapper>)
362+
ClassUtils.forName("com.fasterxml.jackson.dataformat.xml.XmlMapper", this.moduleClassLoader);
363+
objectMapper = BeanUtils.instantiate(xmlMapper);
368364
}
369-
else {
370-
this.objectMapper = new ObjectMapper();
365+
catch (ClassNotFoundException ex) {
366+
throw new IllegalStateException("Could not instantiate XmlMapper - not found on classpath");
371367
}
372368
}
369+
else {
370+
objectMapper = new ObjectMapper();
371+
}
372+
configure(objectMapper);
373+
return (T) objectMapper;
374+
}
375+
376+
/**
377+
* Configure an existing {@link ObjectMapper} instance with this builder's
378+
* settings. This can be applied to any number of {@code ObjectMappers}.
379+
* @param objectMapper the ObjectMapper to configure
380+
*/
381+
public void configure(ObjectMapper objectMapper) {
382+
Assert.notNull(objectMapper, "ObjectMapper must not be null");
373383

374384
if (this.dateFormat != null) {
375-
this.objectMapper.setDateFormat(this.dateFormat);
385+
objectMapper.setDateFormat(this.dateFormat);
376386
}
377387

378388
if (this.annotationIntrospector != null) {
379-
this.objectMapper.setAnnotationIntrospector(this.annotationIntrospector);
389+
objectMapper.setAnnotationIntrospector(this.annotationIntrospector);
380390
}
381391

382392
if (this.serializationInclusion != null) {
383-
this.objectMapper.setSerializationInclusion(this.serializationInclusion);
393+
objectMapper.setSerializationInclusion(this.serializationInclusion);
384394
}
385395

386396
if (!this.serializers.isEmpty() || !this.deserializers.isEmpty()) {
387397
SimpleModule module = new SimpleModule();
388398
addSerializers(module);
389399
addDeserializers(module);
390-
this.objectMapper.registerModule(module);
400+
objectMapper.registerModule(module);
391401
}
392402

393-
if(!features.containsKey(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
394-
configureFeature(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
403+
if (!this.features.containsKey(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
404+
configureFeature(objectMapper, MapperFeature.DEFAULT_VIEW_INCLUSION, false);
395405
}
396-
if(!features.containsKey(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
397-
configureFeature(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
406+
if (!this.features.containsKey(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
407+
configureFeature(objectMapper, DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
398408
}
399409
for (Object feature : this.features.keySet()) {
400-
configureFeature(feature, this.features.get(feature));
410+
configureFeature(objectMapper, feature, this.features.get(feature));
401411
}
402412

403413
if (this.modules != null) {
404414
// Complete list of modules given
405415
for (Module module : this.modules) {
406416
// Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules
407-
this.objectMapper.registerModule(module);
417+
objectMapper.registerModule(module);
408418
}
409419
}
410420
else {
411421
// Combination of modules by class names specified and class presence in the classpath
412422
if (this.modulesToInstall != null) {
413423
for (Class<? extends Module> module : this.modulesToInstall) {
414-
this.objectMapper.registerModule(BeanUtils.instantiate(module));
424+
objectMapper.registerModule(BeanUtils.instantiate(module));
415425
}
416426
}
417427
if (this.findModulesViaServiceLoader) {
418428
// Jackson 2.2+
419-
this.objectMapper.registerModules(ObjectMapper.findModules(getClassLoader()));
429+
objectMapper.registerModules(ObjectMapper.findModules(this.moduleClassLoader));
420430
}
421431
else {
422-
registerWellKnownModulesIfAvailable();
432+
registerWellKnownModulesIfAvailable(objectMapper);
423433
}
424434
}
425435

426436
if (this.propertyNamingStrategy != null) {
427-
this.objectMapper.setPropertyNamingStrategy(this.propertyNamingStrategy);
437+
objectMapper.setPropertyNamingStrategy(this.propertyNamingStrategy);
428438
}
429-
430-
return (T)this.objectMapper;
431439
}
432440

433441
@SuppressWarnings("unchecked")
@@ -444,47 +452,46 @@ private <T> void addDeserializers(SimpleModule module) {
444452
}
445453
}
446454

447-
private void configureFeature(Object feature, boolean enabled) {
455+
private void configureFeature(ObjectMapper objectMapper, Object feature, boolean enabled) {
448456
if (feature instanceof JsonParser.Feature) {
449-
this.objectMapper.configure((JsonParser.Feature) feature, enabled);
457+
objectMapper.configure((JsonParser.Feature) feature, enabled);
450458
}
451459
else if (feature instanceof JsonGenerator.Feature) {
452-
this.objectMapper.configure((JsonGenerator.Feature) feature, enabled);
460+
objectMapper.configure((JsonGenerator.Feature) feature, enabled);
453461
}
454462
else if (feature instanceof SerializationFeature) {
455-
this.objectMapper.configure((SerializationFeature) feature, enabled);
463+
objectMapper.configure((SerializationFeature) feature, enabled);
456464
}
457465
else if (feature instanceof DeserializationFeature) {
458-
this.objectMapper.configure((DeserializationFeature) feature, enabled);
466+
objectMapper.configure((DeserializationFeature) feature, enabled);
459467
}
460468
else if (feature instanceof MapperFeature) {
461-
this.objectMapper.configure((MapperFeature) feature, enabled);
469+
objectMapper.configure((MapperFeature) feature, enabled);
462470
}
463471
else {
464472
throw new FatalBeanException("Unknown feature class: " + feature.getClass().getName());
465473
}
466474
}
467475

468476
@SuppressWarnings("unchecked")
469-
private void registerWellKnownModulesIfAvailable() {
470-
ClassLoader cl = getClassLoader();
477+
private void registerWellKnownModulesIfAvailable(ObjectMapper objectMapper) {
471478
// Java 8 java.time package present?
472-
if (ClassUtils.isPresent("java.time.LocalDate", cl)) {
479+
if (ClassUtils.isPresent("java.time.LocalDate", this.moduleClassLoader)) {
473480
try {
474481
Class<? extends Module> jsr310Module = (Class<? extends Module>)
475-
cl.loadClass("com.fasterxml.jackson.datatype.jsr310.JSR310Module");
476-
this.objectMapper.registerModule(BeanUtils.instantiate(jsr310Module));
482+
ClassUtils.forName("com.fasterxml.jackson.datatype.jsr310.JSR310Module", this.moduleClassLoader);
483+
objectMapper.registerModule(BeanUtils.instantiate(jsr310Module));
477484
}
478485
catch (ClassNotFoundException ex) {
479486
// jackson-datatype-jsr310 not available
480487
}
481488
}
482489
// Joda-Time present?
483-
if (ClassUtils.isPresent("org.joda.time.LocalDate", cl)) {
490+
if (ClassUtils.isPresent("org.joda.time.LocalDate", this.moduleClassLoader)) {
484491
try {
485492
Class<? extends Module> jodaModule = (Class<? extends Module>)
486-
cl.loadClass("com.fasterxml.jackson.datatype.joda.JodaModule");
487-
this.objectMapper.registerModule(BeanUtils.instantiate(jodaModule));
493+
ClassUtils.forName("com.fasterxml.jackson.datatype.joda.JodaModule", this.moduleClassLoader);
494+
objectMapper.registerModule(BeanUtils.instantiate(jodaModule));
488495
}
489496
catch (ClassNotFoundException ex) {
490497
// jackson-datatype-joda not available

0 commit comments

Comments
 (0)