提交 fbd85925 编写于 作者: S Sebastien Deleuze

Use Jackson improved default configuration everywhere

With this commit, Jackson builder is now used in spring-websocket
to create the ObjectMapper instance.

It is not possible to use the builder for spring-messaging
and spring-jms since these modules don't have a dependency on
spring-web, thus they now just customize the same features:
 - MapperFeature#DEFAULT_VIEW_INCLUSION is disabled
 - DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES is disabled

Issue: SPR-12293
上级 87f1512e
......@@ -29,7 +29,9 @@ import javax.jms.Message;
import javax.jms.Session;
import javax.jms.TextMessage;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.BeanClassLoaderAware;
......@@ -42,6 +44,12 @@ import org.springframework.util.ClassUtils;
* {@link #setTargetType targetType} is set to {@link MessageType#TEXT}.
* Converts from a {@link TextMessage} or {@link BytesMessage} to an object.
*
* <p>It customizes Jackson's default properties with the following ones:
* <ul>
* <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li>
* <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li>
* </ul>
*
* <p>Tested against Jackson 2.2; compatible with Jackson 2.0 and higher.
*
* @author Mark Pollack
......@@ -57,7 +65,7 @@ public class MappingJackson2MessageConverter implements MessageConverter, BeanCl
public static final String DEFAULT_ENCODING = "UTF-8";
private ObjectMapper objectMapper = new ObjectMapper();
private ObjectMapper objectMapper;
private MessageType targetType = MessageType.BYTES;
......@@ -74,6 +82,12 @@ public class MappingJackson2MessageConverter implements MessageConverter, BeanCl
private ClassLoader beanClassLoader;
public MappingJackson2MessageConverter() {
this.objectMapper = new ObjectMapper();
this.objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
/**
* Specify the {@link ObjectMapper} to use instead of using the default.
*/
......
......@@ -115,6 +115,32 @@ public class MappingJackson2MessageConverterTests {
verify(textMessageMock).setStringProperty("__typeid__", HashMap.class.getName());
}
@Test
public void fromTextMessage() throws Exception {
TextMessage textMessageMock = mock(TextMessage.class);
MyBean unmarshalled = new MyBean("bar");
String text = "{\"foo\":\"bar\"}";
given(textMessageMock.getStringProperty("__typeid__")).willReturn(MyBean.class.getName());
given(textMessageMock.getText()).willReturn(text);
MyBean result = (MyBean)converter.fromMessage(textMessageMock);
assertEquals("Invalid result", result, unmarshalled);
}
@Test
public void fromTextMessageWithUnknownProperty() throws Exception {
TextMessage textMessageMock = mock(TextMessage.class);
MyBean unmarshalled = new MyBean("bar");
String text = "{\"foo\":\"bar\", \"unknownProperty\":\"value\"}";
given(textMessageMock.getStringProperty("__typeid__")).willReturn(MyBean.class.getName());
given(textMessageMock.getText()).willReturn(text);
MyBean result = (MyBean)converter.fromMessage(textMessageMock);
assertEquals("Invalid result", result, unmarshalled);
}
@Test
public void fromTextMessageAsObject() throws Exception {
TextMessage textMessageMock = mock(TextMessage.class);
......@@ -141,4 +167,47 @@ public class MappingJackson2MessageConverterTests {
assertEquals("Invalid result", result, unmarshalled);
}
public static class MyBean {
public MyBean() {
}
public MyBean(String foo) {
this.foo = foo;
}
private String foo;
public String getFoo() {
return foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MyBean bean = (MyBean) o;
if (foo != null ? !foo.equals(bean.foo) : bean.foo != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
return foo != null ? foo.hashCode() : 0;
}
}
}
......@@ -26,7 +26,9 @@ import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
......@@ -39,6 +41,12 @@ import org.springframework.util.MimeType;
/**
* A Jackson 2 based {@link MessageConverter} implementation.
*
* <p>It customizes Jackson's default properties with the following ones:
* <ul>
* <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li>
* <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li>
* </ul>
*
* <p>Compatible with Jackson 2.1 and higher.
*
* @author Rossen Stoyanchev
......@@ -52,16 +60,18 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
ClassUtils.hasMethod(ObjectMapper.class, "canDeserialize", JavaType.class, AtomicReference.class);
private ObjectMapper objectMapper = new ObjectMapper();
private ObjectMapper objectMapper;
private Boolean prettyPrint;
public MappingJackson2MessageConverter() {
super(new MimeType("application", "json", Charset.forName("UTF-8")));
this.objectMapper = new ObjectMapper();
this.objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
/**
* Set the {@code ObjectMapper} for this converter.
* If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper} is used.
......
......@@ -289,16 +289,20 @@ public abstract class AbstractMessageBrokerConfiguration implements ApplicationC
converters.add(new StringMessageConverter());
converters.add(new ByteArrayMessageConverter());
if (jackson2Present) {
DefaultContentTypeResolver resolver = new DefaultContentTypeResolver();
resolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON);
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setContentTypeResolver(resolver);
converters.add(converter);
converters.add(createJacksonConverter());
}
}
return new CompositeMessageConverter(converters);
}
protected MappingJackson2MessageConverter createJacksonConverter() {
DefaultContentTypeResolver resolver = new DefaultContentTypeResolver();
resolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON);
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setContentTypeResolver(resolver);
return converter;
}
/**
* Override this method to add custom message converters.
* @param messageConverters the list to add converters to, initially empty
......
......@@ -87,11 +87,12 @@ public class MappingJackson2MessageConverterTests {
this.converter.fromMessage(message, MyBean.class);
}
@Test(expected = MessageConversionException.class)
@Test
public void fromMessageValidJsonWithUnknownProperty() throws IOException {
String payload = "{\"string\":\"string\",\"unknownProperty\":\"value\"}";
Message<?> message = MessageBuilder.withPayload(payload.getBytes(UTF_8)).build();
this.converter.fromMessage(message, MyBean.class);
MyBean myBean = (MyBean)this.converter.fromMessage(message, MyBean.class);
assertEquals("string", myBean.getString());
}
@Test
......
......@@ -434,12 +434,7 @@ public class Jackson2ObjectMapperBuilder {
objectMapper.registerModule(module);
}
if (!this.features.containsKey(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
configureFeature(objectMapper, MapperFeature.DEFAULT_VIEW_INCLUSION, false);
}
if (!this.features.containsKey(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
configureFeature(objectMapper, DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
customizeDefaultFeatures(objectMapper);
for (Object feature : this.features.keySet()) {
configureFeature(objectMapper, feature, this.features.get(feature));
}
......@@ -475,6 +470,17 @@ public class Jackson2ObjectMapperBuilder {
}
}
// Any change to this method should be also applied to spring-jms and spring-messaging
// MappingJackson2MessageConverter default constructors
private void customizeDefaultFeatures(ObjectMapper objectMapper) {
if (!this.features.containsKey(MapperFeature.DEFAULT_VIEW_INCLUSION)) {
configureFeature(objectMapper, MapperFeature.DEFAULT_VIEW_INCLUSION, false);
}
if (!this.features.containsKey(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)) {
configureFeature(objectMapper, DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
}
@SuppressWarnings("unchecked")
private <T> void addSerializers(SimpleModule module) {
for (Class<?> type : this.serializers.keySet()) {
......
......@@ -21,6 +21,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.messaging.support.ImmutableMessageChannelInterceptor;
import org.w3c.dom.Element;
......@@ -385,6 +386,8 @@ class MessageBrokerBeanDefinitionParser implements BeanDefinitionParser {
RootBeanDefinition resolverDef = new RootBeanDefinition(DefaultContentTypeResolver.class);
resolverDef.getPropertyValues().add("defaultMimeType", MimeTypeUtils.APPLICATION_JSON);
jacksonConverterDef.getPropertyValues().add("contentTypeResolver", resolverDef);
// Use Jackson builder in order to have JSR-310 and Joda-Time modules registered automatically
jacksonConverterDef.getPropertyValues().add("objectMapper", Jackson2ObjectMapperBuilder.json().build());
converters.add(jacksonConverterDef);
}
}
......
......@@ -18,6 +18,8 @@ package org.springframework.web.socket.config.annotation;
import org.springframework.beans.factory.config.CustomScopeConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.simp.SimpSessionScope;
import org.springframework.messaging.simp.broker.AbstractBrokerMessageHandler;
import org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration;
......@@ -132,4 +134,12 @@ public abstract class WebSocketMessageBrokerConfigurationSupport extends Abstrac
return stats;
}
@Override
protected MappingJackson2MessageConverter createJacksonConverter() {
MappingJackson2MessageConverter messageConverter = super.createJacksonConverter();
// Use Jackson builder in order to have JSR-310 and Joda-Time modules registered automatically
messageConverter.setObjectMapper(Jackson2ObjectMapperBuilder.json().build());
return messageConverter;
}
}
......@@ -20,13 +20,25 @@ import java.io.IOException;
import java.io.InputStream;
import com.fasterxml.jackson.core.io.JsonStringEncoder;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.util.Assert;
/**
* A Jackson 2.x codec for encoding and decoding SockJS messages.
*
* <p>It customizes Jackson's default properties with the following ones:
* <ul>
* <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li>
* <li>{@link DeserializationFeature#FAIL_ON_UNKNOWN_PROPERTIES} is disabled</li>
* </ul>
*
* <p>Note that Jackson's JSR-310 and Joda-Time support modules will be registered automatically
* when available (and when Java 8 and Joda-Time themselves are available, respectively).
*
* @author Rossen Stoyanchev
* @since 4.0
*/
......@@ -36,7 +48,7 @@ public class Jackson2SockJsMessageCodec extends AbstractSockJsMessageCodec {
public Jackson2SockJsMessageCodec() {
this.objectMapper = new ObjectMapper();
this.objectMapper = Jackson2ObjectMapperBuilder.json().build();
}
public Jackson2SockJsMessageCodec(ObjectMapper objectMapper) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册