diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index d880067a3d3..07634036b62 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -40,6 +40,11 @@ jackson-databind true + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + true + com.fasterxml.jackson.datatype jackson-datatype-joda @@ -104,6 +109,12 @@ org.apache.solr solr-solrj true + + + org.codehaus.woodstox + wstx-asl + + org.apache.tomcat.embed diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java index bc86a82c676..6301babf85d 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfiguration.java @@ -20,6 +20,7 @@ import java.lang.reflect.Field; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Collection; +import java.util.Map; import java.util.Map.Entry; import javax.annotation.PostConstruct; @@ -29,41 +30,33 @@ import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnJava; -import org.springframework.boot.autoconfigure.condition.ConditionalOnJava.JavaVersion; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.web.HttpMapperProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationFeature; -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.datatype.joda.JodaModule; -import com.fasterxml.jackson.datatype.jsr310.JSR310Module; /** * Auto configuration for Jackson. The following auto-configuration will get applied: * * * @author Oliver Gierke * @author Andy Wilkinson + * @author Sebastien Deleuze * @author Marcel Overdijk * @since 1.1.0 */ @@ -88,10 +81,23 @@ public class JacksonAutoConfiguration { } @Configuration - @ConditionalOnClass(ObjectMapper.class) - @EnableConfigurationProperties({ HttpMapperProperties.class, JacksonProperties.class }) + @ConditionalOnClass({ ObjectMapper.class, Jackson2ObjectMapperBuilder.class }) static class JacksonObjectMapperAutoConfiguration { + @Bean + @Primary + @ConditionalOnMissingBean(ObjectMapper.class) + public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { + return builder.createXmlMapper(false).build(); + } + + } + + @Configuration + @ConditionalOnClass({ ObjectMapper.class, Jackson2ObjectMapperBuilder.class }) + @EnableConfigurationProperties({ HttpMapperProperties.class, JacksonProperties.class }) + static class JacksonObjectMapperBuilderAutoConfiguration { + @Autowired private HttpMapperProperties httpMapperProperties = new HttpMapperProperties(); @@ -99,29 +105,39 @@ public class JacksonAutoConfiguration { private JacksonProperties jacksonProperties = new JacksonProperties(); @Bean - @Primary - @ConditionalOnMissingBean - public ObjectMapper jacksonObjectMapper() { - ObjectMapper objectMapper = new ObjectMapper(); + @ConditionalOnMissingBean(Jackson2ObjectMapperBuilder.class) + public Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder() { + Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder(); if (this.httpMapperProperties.isJsonSortKeys()) { - objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, - true); + builder.featuresToEnable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS); } - configureDeserializationFeatures(objectMapper); - configureSerializationFeatures(objectMapper); - configureMapperFeatures(objectMapper); - configureParserFeatures(objectMapper); - configureGeneratorFeatures(objectMapper); + configureFeatures(builder, this.jacksonProperties.getDeserialization()); + configureFeatures(builder, this.jacksonProperties.getSerialization()); + configureFeatures(builder, this.jacksonProperties.getMapper()); + configureFeatures(builder, this.jacksonProperties.getParser()); + configureFeatures(builder, this.jacksonProperties.getGenerator()); + + configureDateFormat(builder); + configurePropertyNamingStrategy(builder); - configureDateFormat(objectMapper); - configurePropertyNamingStrategy(objectMapper); + return builder; + } - return objectMapper; + private void configureFeatures(Jackson2ObjectMapperBuilder builder, + Map features) { + for (Entry entry : features.entrySet()) { + if (entry.getValue() != null && entry.getValue()) { + builder.featuresToEnable(entry.getKey()); + } + else { + builder.featuresToDisable(entry.getKey()); + } + } } - private void configurePropertyNamingStrategy(ObjectMapper objectMapper) { + private void configurePropertyNamingStrategy(Jackson2ObjectMapperBuilder builder) { // We support a fully qualified class name extending Jackson's // PropertyNamingStrategy or a string value corresponding to the constant // names in PropertyNamingStrategy which hold default provided implementations @@ -130,9 +146,8 @@ public class JacksonAutoConfiguration { if (propertyNamingStrategy != null) { try { Class clazz = ClassUtils.forName(propertyNamingStrategy, null); - objectMapper - .setPropertyNamingStrategy((PropertyNamingStrategy) BeanUtils - .instantiateClass(clazz)); + builder.propertyNamingStrategy((PropertyNamingStrategy) BeanUtils + .instantiateClass(clazz)); } catch (ClassNotFoundException e) { // Find the field (this way we automatically support new constants @@ -141,9 +156,8 @@ public class JacksonAutoConfiguration { propertyNamingStrategy, PropertyNamingStrategy.class); if (field != null) { try { - objectMapper - .setPropertyNamingStrategy((PropertyNamingStrategy) field - .get(null)); + builder.propertyNamingStrategy((PropertyNamingStrategy) field + .get(null)); } catch (Exception ex) { throw new IllegalStateException(ex); @@ -158,85 +172,21 @@ public class JacksonAutoConfiguration { } } - private void configureDateFormat(ObjectMapper objectMapper) { + private void configureDateFormat(Jackson2ObjectMapperBuilder builder) { // We support a fully qualified class name extending DateFormat or a date // pattern string value String dateFormat = this.jacksonProperties.getDateFormat(); if (dateFormat != null) { try { Class clazz = ClassUtils.forName(dateFormat, null); - objectMapper.setDateFormat((DateFormat) BeanUtils - .instantiateClass(clazz)); + builder.dateFormat((DateFormat) BeanUtils.instantiateClass(clazz)); } catch (ClassNotFoundException e) { - objectMapper.setDateFormat(new SimpleDateFormat(dateFormat)); + builder.dateFormat(new SimpleDateFormat(dateFormat)); } } } - private void configureDeserializationFeatures(ObjectMapper objectMapper) { - for (Entry entry : this.jacksonProperties - .getDeserialization().entrySet()) { - objectMapper.configure(entry.getKey(), isFeatureEnabled(entry)); - } - } - - private void configureSerializationFeatures(ObjectMapper objectMapper) { - for (Entry entry : this.jacksonProperties - .getSerialization().entrySet()) { - objectMapper.configure(entry.getKey(), isFeatureEnabled(entry)); - } - } - - private void configureMapperFeatures(ObjectMapper objectMapper) { - for (Entry entry : this.jacksonProperties.getMapper() - .entrySet()) { - objectMapper.configure(entry.getKey(), isFeatureEnabled(entry)); - } - } - - private void configureParserFeatures(ObjectMapper objectMapper) { - for (Entry entry : this.jacksonProperties - .getParser().entrySet()) { - objectMapper.configure(entry.getKey(), isFeatureEnabled(entry)); - } - } - - private void configureGeneratorFeatures(ObjectMapper objectMapper) { - for (Entry entry : this.jacksonProperties - .getGenerator().entrySet()) { - objectMapper.configure(entry.getKey(), isFeatureEnabled(entry)); - } - } - - private boolean isFeatureEnabled(Entry entry) { - return entry.getValue() != null && entry.getValue(); - } - } - - @Configuration - @ConditionalOnClass(JodaModule.class) - static class JodaModuleAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public JodaModule jacksonJodaModule() { - return new JodaModule(); - } - - } - - @Configuration - @ConditionalOnJava(JavaVersion.EIGHT) - @ConditionalOnClass(JSR310Module.class) - static class Jsr310ModuleAutoConfiguration { - - @Bean - @ConditionalOnMissingBean - public JSR310Module jacksonJsr310Module() { - return new JSR310Module(); - } - } } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConverters.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConverters.java index 4b0290e1b1d..db752ddd0db 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConverters.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConverters.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-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. @@ -25,6 +25,7 @@ import java.util.List; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.xml.AbstractXmlHttpMessageConverter; +import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.util.ClassUtils; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; @@ -141,7 +142,8 @@ public class HttpMessageConverters implements Iterable> for (Iterator> iterator = converters.iterator(); iterator .hasNext();) { HttpMessageConverter converter = iterator.next(); - if (converter instanceof AbstractXmlHttpMessageConverter) { + if ((converter instanceof AbstractXmlHttpMessageConverter) + || (converter instanceof MappingJackson2XmlHttpMessageConverter)) { xml.add(converter); iterator.remove(); } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfiguration.java index 68b5b910d7c..23827a2a4fb 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfiguration.java @@ -29,9 +29,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.google.gson.Gson; /** @@ -42,6 +45,7 @@ import com.google.gson.Gson; * @author Piotr Maj * @author Oliver Gierke * @author David Liu + * @author Sebastien Deleuze * @author Andy Wilkinson */ @Configuration @@ -78,6 +82,27 @@ public class HttpMessageConvertersAutoConfiguration { } + @Configuration + @ConditionalOnClass(XmlMapper.class) + @ConditionalOnBean(Jackson2ObjectMapperBuilder.class) + @EnableConfigurationProperties(HttpMapperProperties.class) + protected static class XmlMappers { + + @Autowired + private HttpMapperProperties properties = new HttpMapperProperties(); + + @Bean + @ConditionalOnMissingBean + public MappingJackson2XmlHttpMessageConverter mappingJackson2XmlHttpMessageConverter( + Jackson2ObjectMapperBuilder builder) { + MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter(); + converter.setObjectMapper(builder.createXmlMapper(true).build()); + converter.setPrettyPrint(this.properties.isJsonPrettyPrint()); + return converter; + } + + } + @Configuration @ConditionalOnClass(Gson.class) @ConditionalOnBean(Gson.class) diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java index 4b5ba52888f..af34adacaf2 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jackson/JacksonAutoConfigurationTests.java @@ -19,7 +19,6 @@ package org.springframework.boot.autoconfigure.jackson; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; -import java.util.Map; import org.joda.time.DateTime; import org.joda.time.LocalDateTime; @@ -33,6 +32,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; @@ -45,11 +45,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.module.SimpleModule; -import com.fasterxml.jackson.datatype.joda.JodaModule; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -63,6 +60,7 @@ import static org.mockito.Mockito.verify; * * @author Dave Syer * @author Oliver Gierke + * @author Sebastien Deleuze * @author Andy Wilkinson * @author Marcel Overdijk */ @@ -86,9 +84,6 @@ public class JacksonAutoConfigurationTests { public void registersJodaModuleAutomatically() { this.context.register(JacksonAutoConfiguration.class); this.context.refresh(); - Map modules = this.context.getBeansOfType(Module.class); - assertThat(modules.size(), greaterThanOrEqualTo(1)); // Depends on the JDK - assertThat(modules.get("jacksonJodaModule"), is(instanceOf(JodaModule.class))); ObjectMapper objectMapper = this.context.getBean(ObjectMapper.class); assertThat(objectMapper.canSerialize(LocalDateTime.class), is(true)); } @@ -339,6 +334,26 @@ public class JacksonAutoConfigurationTests { .isEnabled(JsonGenerator.Feature.AUTO_CLOSE_TARGET)); } + @Test + public void defaultObjectMapperBuilder() throws Exception { + this.context.register(JacksonAutoConfiguration.class); + this.context.refresh(); + Jackson2ObjectMapperBuilder builder = this.context + .getBean(Jackson2ObjectMapperBuilder.class); + ObjectMapper mapper = builder.build(); + assertTrue(MapperFeature.DEFAULT_VIEW_INCLUSION.enabledByDefault()); + assertFalse(mapper.getDeserializationConfig().isEnabled( + MapperFeature.DEFAULT_VIEW_INCLUSION)); + assertTrue(MapperFeature.DEFAULT_VIEW_INCLUSION.enabledByDefault()); + assertFalse(mapper.getDeserializationConfig().isEnabled( + MapperFeature.DEFAULT_VIEW_INCLUSION)); + assertFalse(mapper.getSerializationConfig().isEnabled( + MapperFeature.DEFAULT_VIEW_INCLUSION)); + assertTrue(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES.enabledByDefault()); + assertFalse(mapper.getDeserializationConfig().isEnabled( + DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)); + } + @Configuration protected static class ModulesConfig { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java index 1c4368101cc..99cddea47f2 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersAutoConfigurationTests.java @@ -25,7 +25,9 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.GsonHttpMessageConverter; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; @@ -40,6 +42,7 @@ import static org.junit.Assert.assertTrue; * @author Oliver Gierke * @author David Liu * @author Andy Wilkinson + * @author Sebastien Deleuze */ public class HttpMessageConvertersAutoConfigurationTests { @@ -59,11 +62,13 @@ public class HttpMessageConvertersAutoConfigurationTests { assertTrue(this.context.getBeansOfType(ObjectMapper.class).isEmpty()); assertTrue(this.context.getBeansOfType(MappingJackson2HttpMessageConverter.class) .isEmpty()); + assertTrue(this.context.getBeansOfType( + MappingJackson2XmlHttpMessageConverter.class).isEmpty()); } @Test public void defaultJacksonConverter() throws Exception { - this.context.register(JacksonConfig.class, + this.context.register(JacksonObjectMapperConfig.class, HttpMessageConvertersAutoConfiguration.class); this.context.refresh(); @@ -73,9 +78,25 @@ public class HttpMessageConvertersAutoConfigurationTests { assertConverterBeanRegisteredWithHttpMessageConverters(MappingJackson2HttpMessageConverter.class); } + @Test + public void defaultJacksonConvertersWithBuilder() throws Exception { + this.context.register(JacksonObjectMapperBuilderConfig.class, + HttpMessageConvertersAutoConfiguration.class); + this.context.refresh(); + + assertConverterBeanExists(MappingJackson2HttpMessageConverter.class, + "mappingJackson2HttpMessageConverter"); + assertConverterBeanExists(MappingJackson2XmlHttpMessageConverter.class, + "mappingJackson2XmlHttpMessageConverter"); + + assertConverterBeanRegisteredWithHttpMessageConverters(MappingJackson2HttpMessageConverter.class); + assertConverterBeanRegisteredWithHttpMessageConverters(MappingJackson2XmlHttpMessageConverter.class); + } + @Test public void customJacksonConverter() throws Exception { - this.context.register(JacksonConfig.class, JacksonConverterConfig.class, + this.context.register(JacksonObjectMapperConfig.class, + JacksonConverterConfig.class, HttpMessageConvertersAutoConfiguration.class); this.context.refresh(); @@ -128,13 +149,27 @@ public class HttpMessageConvertersAutoConfigurationTests { } @Configuration - protected static class JacksonConfig { + protected static class JacksonObjectMapperConfig { @Bean public ObjectMapper objectMapper() { return new ObjectMapper(); } } + @Configuration + protected static class JacksonObjectMapperBuilderConfig { + + @Bean + public ObjectMapper objectMapper() { + return new ObjectMapper(); + } + + @Bean + public Jackson2ObjectMapperBuilder builder() { + return new Jackson2ObjectMapperBuilder(); + } + } + @Configuration protected static class JacksonConverterConfig { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersTests.java index 6363679f1be..f5fa9b517c7 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/HttpMessageConvertersTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2013 the original author or authors. + * Copyright 2012-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. @@ -30,7 +30,7 @@ import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; -import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; +import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.http.converter.xml.SourceHttpMessageConverter; import static org.hamcrest.Matchers.equalTo; @@ -63,7 +63,7 @@ public class HttpMessageConvertersTests { ResourceHttpMessageConverter.class, SourceHttpMessageConverter.class, AllEncompassingFormHttpMessageConverter.class, MappingJackson2HttpMessageConverter.class, - Jaxb2RootElementHttpMessageConverter.class))); + MappingJackson2XmlHttpMessageConverter.class))); } @Test @@ -106,7 +106,7 @@ public class HttpMessageConvertersTests { List> converters) { for (Iterator> iterator = converters.iterator(); iterator .hasNext();) { - if (iterator.next() instanceof Jaxb2RootElementHttpMessageConverter) { + if (iterator.next() instanceof MappingJackson2XmlHttpMessageConverter) { iterator.remove(); } } diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index c976e95db3d..05b56297087 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -440,6 +440,11 @@ jackson-databind ${jackson.version} + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + ${jackson.version} + com.fasterxml.jackson.datatype jackson-datatype-joda diff --git a/spring-boot-docs/src/main/asciidoc/howto.adoc b/spring-boot-docs/src/main/asciidoc/howto.adoc index 890008f3116..0c4a58da468 100644 --- a/spring-boot-docs/src/main/asciidoc/howto.adoc +++ b/spring-boot-docs/src/main/asciidoc/howto.adoc @@ -643,15 +643,40 @@ default as long as Jackson2 is on the classpath. For example: As long as `MyThing` can be serialized by Jackson2 (e.g. a normal POJO or Groovy object) then `http://localhost:8080/thing` will serve a JSON representation of it by default. -Sometimes in a browser you might see XML responses (but by default only if `MyThing` was -a JAXB object) because browsers tend to send accept headers that prefer XML. +Sometimes in a browser you might see XML responses because browsers tend to send accept +headers that prefer XML. [[howto-write-an-xml-rest-service]] === Write an XML REST service -Since JAXB is in the JDK the same example as we used for JSON would work, as long as the -`MyThing` was annotated as `@XmlRootElement`: +If you have the Jackson XML extension (`jackson-dataformat-xml`) on the classpath, it will +be used to render XML responses and the very same example as we used for JSON would work. +To use it, add the following dependency to your project: + +[source,xml,indent=0,subs="verbatim,quotes,attributes"] +---- + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + +---- + +You may also want to add a dependency on Woodstox. It's faster than the default Stax +implementation provided by the JDK and also adds pretty print support and improved +namespace handling: + +[source,xml,indent=0,subs="verbatim,quotes,attributes"] +---- + + org.codehaus.woodstox + woodstox-core-asl + +---- + +If Jackson's XML extension is not available, JAXB (provided by default in the JDK) will +be used, with the additional requirement to have `MyThing` annotated as +`@XmlRootElement`: [source,java,indent=0,subs="verbatim,quotes,attributes"] ---- @@ -670,14 +695,21 @@ To get the server to render XML instead of JSON you might have to send an [[howto-customize-the-jackson-objectmapper]] === Customize the Jackson ObjectMapper Spring MVC (client and server side) uses `HttpMessageConverters` to negotiate content -conversion in an HTTP exchange. If Jackson is on the classpath you already get a default -converter with a vanilla `ObjectMapper`. Spring Boot has some features to make it easier -to customize this behavior. +conversion in an HTTP exchange. If Jackson is on the classpath you already get the +default converter(s) provided by `Jackson2ObjectMapperBuilder`. -You can configure the vanilla `ObjectMapper` using the environment. Jackson provides an -extensive suite of simple on/off features that can be used to configure various aspects -of its processing. These features are described in five enums in Jackson which map onto -properties in the environment: +The `ObjectMapper` (or `XmlMapper` for Jackson XML converter) instance created by default +have the following customized properties: + +* MapperFeature.DEFAULT_VIEW_INCLUSION is disabled +* DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES is disabled + +Spring Boot has also some features to make it easier to customize this behavior. + +You can configure the `ObjectMapper` and `XmlMapper` instances using the environment. +Jackson provides an extensive suite of simple on/off features that can be used to +configure various aspects of its processing. These features are described in five enums in +Jackson which map onto properties in the environment: |=== |Jackson enum|Environment property @@ -698,14 +730,17 @@ properties in the environment: |`spring.jackson.serialization.=true\|false` |=== -For example, to allow deserialization to continue when an unknown property is encountered -during deserialization, set `spring.jackson.deserialization.fail_on_unknown_properties=false`. -Note that, thanks to the use of <>, -the case of `fail_on_unknown_properties` doesn't have to match the case of the corresponding -enum constant which is `FAIL_ON_UNKNOWN_PROPERTIES`. +For example, to enable pretty print, set `spring.jackson.serialization.indent_output=true`. +Note that, thanks to the use of <>, the case of `indent_output` doesn't have to match the case of the +corresponding enum constant which is `INDENT_OUTPUT`. + +If you want to replace the default `ObjectMapper` completely, define a `@Bean` of that +type and mark it as `@Primary`. -If you want to replace the default `ObjectMapper` completely, define a `@Bean` of that type -and mark it as `@Primary`. +Defining a `@Bean` of type `Jackson2ObjectMapperBuilder` will allow you to customize both +default `ObjectMapper` and `XmlMapper` (used in `MappingJackson2HttpMessageConverter` and +`MappingJackson2XmlHttpMessageConverter` respectively). Another way to customize Jackson is to add beans of type `com.fasterxml.jackson.databind.Module` to your context. They will be registered with every diff --git a/spring-boot-docs/src/main/asciidoc/index.adoc b/spring-boot-docs/src/main/asciidoc/index.adoc index e3386f7dad0..8055def48ed 100644 --- a/spring-boot-docs/src/main/asciidoc/index.adoc +++ b/spring-boot-docs/src/main/asciidoc/index.adoc @@ -1,5 +1,5 @@ = Spring Boot Reference Guide -Phillip Webb; Dave Syer; Josh Long; Stéphane Nicoll; Rob Winch; Andy Wilkinson; Marcel Overdijk; Christian Dupuis; +Phillip Webb; Dave Syer; Josh Long; Stéphane Nicoll; Rob Winch; Andy Wilkinson; Marcel Overdijk; Christian Dupuis; Sébastien Deleuze :doctype: book :toc: :toclevels: 4 diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index a6de6df80aa..21984975e06 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -885,7 +885,8 @@ formatters, view controllers etc.) you can add your own `@Bean` of type ==== HttpMessageConverters Spring MVC uses the `HttpMessageConverter` interface to convert HTTP requests and responses. Sensible defaults are included out of the box, for example Objects can be -automatically converted to JSON (using the Jackson library) or XML (using JAXB). +automatically converted to JSON (using the Jackson library) or XML (using the Jackson +XML extension if available, else using JAXB). If you need to add or customize converters you can use Spring Boot's `HttpMessageConverters` class: