Add support for autowiring Jackson handlers

This commit introduces the SpringHandlerInstantiator
class, a Jackson HandlerInstantiator that allows to autowire
Jackson handlers (JsonSerializer, JsonDeserializer, KeyDeserializer,
TypeResolverBuilder and TypeIdResolver) if needed.

SpringHandlerInstantiator is automatically used with
@EnableWebMvc and <mvc:annotation-driven />.

Issue: SPR-10768
This commit is contained in:
Sebastien Deleuze
2014-12-04 18:40:49 +01:00
parent ea7f787ad8
commit 2fccf3ff44
8 changed files with 459 additions and 9 deletions

View File

@@ -31,16 +31,20 @@ import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import javafx.application.Application;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.FatalBeanException;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -94,6 +98,10 @@ public class Jackson2ObjectMapperBuilder {
private ClassLoader moduleClassLoader = getClass().getClassLoader();
private HandlerInstantiator handlerInstantiator;
private ApplicationContext applicationContext;
/**
* If set to {@code true}, an {@link XmlMapper} will be created using its
@@ -379,6 +387,27 @@ public class Jackson2ObjectMapperBuilder {
return this;
}
/**
* Customize the construction of Jackson handlers ({@link JsonSerializer}, {@link JsonDeserializer},
* {@link KeyDeserializer}, {@code TypeResolverBuilder} and {@code TypeIdResolver}).
* @since 4.1.3
* @see Jackson2ObjectMapperBuilder#applicationContext(ApplicationContext)
*/
public Jackson2ObjectMapperBuilder handlerInstantiator(HandlerInstantiator handlerInstantiator) {
this.handlerInstantiator = handlerInstantiator;
return this;
}
/**
* Set the Spring {@link ApplicationContext} in order to autowire Jackson handlers ({@link JsonSerializer},
* {@link JsonDeserializer}, {@link KeyDeserializer}, {@code TypeResolverBuilder} and {@code TypeIdResolver}).
* @since 4.1.3
* @see SpringHandlerInstantiator
*/
public Jackson2ObjectMapperBuilder applicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
return this;
}
/**
* Build a new {@link ObjectMapper} instance.
@@ -468,6 +497,12 @@ public class Jackson2ObjectMapperBuilder {
for (Class<?> target : this.mixIns.keySet()) {
objectMapper.addMixInAnnotations(target, this.mixIns.get(target));
}
if (this.handlerInstantiator != null) {
objectMapper.setHandlerInstantiator(this.handlerInstantiator);
}
else if (this.applicationContext != null) {
objectMapper.setHandlerInstantiator(new SpringHandlerInstantiator(this.applicationContext.getAutowireCapableBeanFactory()));
}
}
// Any change to this method should be also applied to spring-jms and spring-messaging

View File

@@ -26,16 +26,20 @@ import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
* A {@link FactoryBean} for creating a Jackson 2.x {@link ObjectMapper} (default) or
@@ -119,9 +123,11 @@ import org.springframework.beans.factory.InitializingBean;
* @author Brian Clozel
* @author Juergen Hoeller
* @author Tadaya Tsuyukubo
* @author Sebastien Deleuze
* @since 3.2
*/
public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper>, BeanClassLoaderAware, InitializingBean {
public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper>, BeanClassLoaderAware,
ApplicationContextAware, InitializingBean {
private final Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
@@ -336,6 +342,16 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
this.builder.findModulesViaServiceLoader(findModules);
}
/**
* Customize the construction of Jackson handlers ({@link JsonSerializer}, {@link JsonDeserializer},
* {@link KeyDeserializer}, {@code TypeResolverBuilder} and {@code TypeIdResolver}).
* @since 4.1.3
* @see Jackson2ObjectMapperFactoryBean#setApplicationContext(ApplicationContext)
*/
public void setHandlerInstantiator(HandlerInstantiator handlerInstantiator) {
this.builder.handlerInstantiator(handlerInstantiator);
}
@Override
public void setBeanClassLoader(ClassLoader beanClassLoader) {
this.builder.moduleClassLoader(beanClassLoader);
@@ -353,6 +369,17 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
}
}
/**
* Set the builder {@link ApplicationContext} in order to autowire Jackson handlers ({@link JsonSerializer},
* {@link JsonDeserializer}, {@link KeyDeserializer}, {@code TypeResolverBuilder} and {@code TypeIdResolver}).
* @since 4.1.3
* @see Jackson2ObjectMapperBuilder#applicationContext(ApplicationContext)
* @see SpringHandlerInstantiator
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.builder.applicationContext(applicationContext);
}
/**
* Return the singleton ObjectMapper.

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2002-2014 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.converter.json;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.cfg.HandlerInstantiator;
import com.fasterxml.jackson.databind.cfg.MapperConfig;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
/**
* Eventually get Jackson handler ({@link JsonSerializer}, {@link JsonDeserializer},
* {@link KeyDeserializer}, {@link TypeResolverBuilder}, {@link TypeIdResolver}) beans by
* type from Spring {@link ApplicationContext}. If no bean is found, the default behavior
* happen (calling no-argument constructor via reflection).
*
* @since 4.1.3
* @author Sebastien Deleuze
* @see Jackson2ObjectMapperBuilder#handlerInstantiator(HandlerInstantiator)
* @see HandlerInstantiator
*/
public class SpringHandlerInstantiator extends HandlerInstantiator {
private final AutowireCapableBeanFactory beanFactory;
/**
* Create a new SpringHandlerInstantiator for the given BeanFactory.
* @param beanFactory the target BeanFactory
*/
public SpringHandlerInstantiator(AutowireCapableBeanFactory beanFactory) {
Assert.notNull(beanFactory, "BeanFactory must not be null");
this.beanFactory = beanFactory;
}
@Override
public JsonSerializer<?> serializerInstance(SerializationConfig config,
Annotated annotated, Class<?> keyDeserClass) {
return (JsonSerializer<?>) this.beanFactory.createBean(keyDeserClass);
}
@Override
public JsonDeserializer<?> deserializerInstance(DeserializationConfig config,
Annotated annotated, Class<?> deserClass) {
return (JsonDeserializer<?>) this.beanFactory.createBean(deserClass);
}
@Override
public KeyDeserializer keyDeserializerInstance(DeserializationConfig config,
Annotated annotated, Class<?> serClass) {
return (KeyDeserializer) this.beanFactory.createBean(serClass);
}
@Override
public TypeResolverBuilder<?> typeResolverBuilderInstance(MapperConfig<?> config,
Annotated annotated, Class<?> resolverClass) {
return (TypeResolverBuilder<?>) this.beanFactory.createBean(resolverClass);
}
@Override
public TypeIdResolver typeIdResolverInstance(MapperConfig<?> config,
Annotated annotated, Class<?> resolverClass) {
return (TypeIdResolver) this.beanFactory.createBean(resolverClass);
}
}