Browse Source
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-10768pull/705/head
8 changed files with 459 additions and 9 deletions
@ -0,0 +1,88 @@
@@ -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); |
||||
} |
||||
} |
@ -0,0 +1,272 @@
@@ -0,0 +1,272 @@
|
||||
/* |
||||
* 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 java.io.IOException; |
||||
import java.util.Collection; |
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo; |
||||
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.DeserializationConfig; |
||||
import com.fasterxml.jackson.databind.DeserializationContext; |
||||
import com.fasterxml.jackson.databind.JavaType; |
||||
import com.fasterxml.jackson.databind.JsonDeserializer; |
||||
import com.fasterxml.jackson.databind.JsonNode; |
||||
import com.fasterxml.jackson.databind.JsonSerializer; |
||||
import com.fasterxml.jackson.databind.KeyDeserializer; |
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import com.fasterxml.jackson.databind.SerializationConfig; |
||||
import com.fasterxml.jackson.databind.SerializerProvider; |
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; |
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize; |
||||
import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; |
||||
import com.fasterxml.jackson.databind.annotation.JsonTypeResolver; |
||||
import com.fasterxml.jackson.databind.jsontype.NamedType; |
||||
import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; |
||||
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; |
||||
import com.fasterxml.jackson.databind.jsontype.TypeSerializer; |
||||
import com.fasterxml.jackson.databind.jsontype.impl.StdTypeResolverBuilder; |
||||
import com.fasterxml.jackson.databind.type.TypeFactory; |
||||
import static org.junit.Assert.assertEquals; |
||||
import static org.junit.Assert.assertFalse; |
||||
import static org.junit.Assert.assertTrue; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; |
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory; |
||||
import org.springframework.beans.factory.support.RootBeanDefinition; |
||||
|
||||
/** |
||||
* Test class for {@link SpringHandlerInstantiatorTests}. |
||||
* |
||||
* @author Sebastien Deleuze |
||||
*/ |
||||
public class SpringHandlerInstantiatorTests { |
||||
|
||||
private SpringHandlerInstantiator instantiator; |
||||
private ObjectMapper objectMapper; |
||||
|
||||
@Before |
||||
public void setup() { |
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); |
||||
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); |
||||
bpp.setBeanFactory(bf); |
||||
bf.addBeanPostProcessor(bpp); |
||||
bf.registerBeanDefinition("capitalizer", new RootBeanDefinition(Capitalizer.class)); |
||||
instantiator = new SpringHandlerInstantiator(bf); |
||||
objectMapper = Jackson2ObjectMapperBuilder.json().handlerInstantiator(instantiator).build(); |
||||
} |
||||
|
||||
@Test |
||||
public void autowiredSerializer() throws JsonProcessingException { |
||||
User user = new User("bob"); |
||||
String json = this.objectMapper.writeValueAsString(user); |
||||
assertEquals("{\"username\":\"BOB\"}", json); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void autowiredDeserializer() throws IOException { |
||||
String json = "{\"username\":\"bob\"}"; |
||||
User user = this.objectMapper.readValue(json, User.class); |
||||
assertEquals(user.getUsername(), "BOB"); |
||||
} |
||||
|
||||
@Test |
||||
public void autowiredKeyDeserializer() throws IOException { |
||||
String json = "{\"credentials\":{\"bob\":\"admin\"}}"; |
||||
SecurityRegistry registry = this.objectMapper.readValue(json, SecurityRegistry.class); |
||||
assertTrue(registry.getCredentials().keySet().contains("BOB")); |
||||
assertFalse(registry.getCredentials().keySet().contains("bob")); |
||||
} |
||||
|
||||
@Test |
||||
public void applicationContextAwaretypeResolverBuilder() throws JsonProcessingException { |
||||
this.objectMapper.writeValueAsString(new Group("authors")); |
||||
assertTrue(CustomTypeResolverBuilder.isAutowiredFiledInitialized); |
||||
} |
||||
|
||||
@Test |
||||
public void applicationContextAwareTypeIdResolver() throws JsonProcessingException { |
||||
this.objectMapper.writeValueAsString(new Group("authors")); |
||||
assertTrue(CustomTypeIdResolver.isAutowiredFiledInitialized); |
||||
} |
||||
|
||||
public static class UserDeserializer extends JsonDeserializer<User> { |
||||
|
||||
@Autowired |
||||
private Capitalizer capitalizer; |
||||
|
||||
@Override |
||||
public User deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { |
||||
ObjectCodec oc = jsonParser.getCodec(); |
||||
JsonNode node = oc.readTree(jsonParser); |
||||
return new User(this.capitalizer.capitalize(node.get("username").asText())); |
||||
} |
||||
|
||||
} |
||||
|
||||
public static class UserSerializer extends JsonSerializer<User> { |
||||
|
||||
@Autowired |
||||
private Capitalizer capitalizer; |
||||
|
||||
@Override |
||||
public void serialize(User user, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { |
||||
jsonGenerator.writeStartObject(); |
||||
jsonGenerator.writeStringField("username", this.capitalizer.capitalize(user.getUsername())); |
||||
jsonGenerator.writeEndObject(); |
||||
} |
||||
} |
||||
|
||||
public static class UpperCaseKeyDeserializer extends KeyDeserializer { |
||||
|
||||
@Autowired |
||||
private Capitalizer capitalizer; |
||||
|
||||
@Override |
||||
public Object deserializeKey(String key, DeserializationContext context) throws IOException, JsonProcessingException { |
||||
return this.capitalizer.capitalize(key); |
||||
} |
||||
} |
||||
|
||||
public static class CustomTypeResolverBuilder extends StdTypeResolverBuilder { |
||||
|
||||
@Autowired |
||||
private Capitalizer capitalizer; |
||||
|
||||
public static boolean isAutowiredFiledInitialized = false; |
||||
|
||||
@Override |
||||
public TypeSerializer buildTypeSerializer(SerializationConfig config, JavaType baseType, Collection<NamedType> subtypes) { |
||||
isAutowiredFiledInitialized = (this.capitalizer != null); |
||||
return super.buildTypeSerializer(config, baseType, subtypes); |
||||
} |
||||
|
||||
@Override |
||||
public TypeDeserializer buildTypeDeserializer(DeserializationConfig config, JavaType baseType, Collection<NamedType> subtypes) { |
||||
return super.buildTypeDeserializer(config, baseType, subtypes); |
||||
} |
||||
} |
||||
|
||||
public static class CustomTypeIdResolver implements TypeIdResolver { |
||||
|
||||
@Autowired |
||||
private Capitalizer capitalizer; |
||||
|
||||
public static boolean isAutowiredFiledInitialized = false; |
||||
|
||||
public CustomTypeIdResolver() { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public String idFromValueAndType(Object o, Class<?> type) { |
||||
return type.getClass().getName(); |
||||
} |
||||
|
||||
@Override |
||||
public JsonTypeInfo.Id getMechanism() { |
||||
return JsonTypeInfo.Id.CUSTOM; |
||||
} |
||||
|
||||
@Override |
||||
public JavaType typeFromId(String s) { |
||||
return TypeFactory.defaultInstance().constructFromCanonical(s); |
||||
} |
||||
|
||||
@Override |
||||
public String idFromValue(Object value) { |
||||
isAutowiredFiledInitialized = (this.capitalizer != null); |
||||
return value.getClass().getName(); |
||||
} |
||||
|
||||
@Override |
||||
public void init(JavaType type) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public String idFromBaseType() { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
@JsonDeserialize(using = UserDeserializer.class) |
||||
@JsonSerialize(using = UserSerializer.class) |
||||
public static class User { |
||||
|
||||
private String username; |
||||
|
||||
public User() { |
||||
} |
||||
|
||||
public User(String username) { |
||||
this.username = username; |
||||
} |
||||
|
||||
public String getUsername() { return this.username; } |
||||
} |
||||
|
||||
public static class SecurityRegistry { |
||||
|
||||
@JsonDeserialize(keyUsing = UpperCaseKeyDeserializer.class) |
||||
private Map<String, String> credentials = new HashMap<>(); |
||||
|
||||
public void addCredential(String username, String credential) { |
||||
this.credentials.put(username, credential); |
||||
} |
||||
|
||||
public Map<String, String> getCredentials() { |
||||
return credentials; |
||||
} |
||||
} |
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type") |
||||
@JsonTypeResolver(CustomTypeResolverBuilder.class) |
||||
@JsonTypeIdResolver(CustomTypeIdResolver.class) |
||||
public static class Group { |
||||
|
||||
private String name; |
||||
|
||||
public Group(String name) { |
||||
this.name = name; |
||||
} |
||||
|
||||
public Group() { |
||||
|
||||
} |
||||
|
||||
public String getType() { |
||||
return Group.class.getName(); |
||||
} |
||||
} |
||||
|
||||
public static class Capitalizer { |
||||
|
||||
public String capitalize(String text) { |
||||
return text.toUpperCase(); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue