diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/GsonHttpMessageConvertersConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/GsonHttpMessageConvertersConfiguration.java index 5d10366d2e..dc754cd694 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/GsonHttpMessageConvertersConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/GsonHttpMessageConvertersConfiguration.java @@ -43,7 +43,7 @@ class GsonHttpMessageConvertersConfiguration { @Configuration @ConditionalOnBean(Gson.class) - @Conditional(PreferGsonOrMissingJacksonAndJsonbCondition.class) + @Conditional(PreferGsonOrJacksonAndJsonbUnavailableCondition.class) protected static class GsonHttpMessageConverterConfiguration { @Bean @@ -56,27 +56,28 @@ class GsonHttpMessageConvertersConfiguration { } - private static class PreferGsonOrMissingJacksonAndJsonbCondition extends AnyNestedCondition { + private static class PreferGsonOrJacksonAndJsonbUnavailableCondition + extends AnyNestedCondition { - PreferGsonOrMissingJacksonAndJsonbCondition() { + PreferGsonOrJacksonAndJsonbUnavailableCondition() { super(ConfigurationPhase.REGISTER_BEAN); } - @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "gson", matchIfMissing = false) + @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "gson") static class GsonPreferred { } - @Conditional(JacksonAndJsonbMissing.class) - static class JacksonJsonbMissing { + @Conditional(JacksonAndJsonbUnavailable.class) + static class JacksonJsonbUnavailable { } } - private static class JacksonAndJsonbMissing extends NoneNestedConditions { + private static class JacksonAndJsonbUnavailable extends NoneNestedConditions { - JacksonAndJsonbMissing() { + JacksonAndJsonbUnavailable() { super(ConfigurationPhase.REGISTER_BEAN); } @@ -86,7 +87,7 @@ class GsonHttpMessageConvertersConfiguration { } @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "jsonb") - static class JsonbMissing { + static class JsonbPreferred { } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/JsonbHttpMessageConvertersConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/JsonbHttpMessageConvertersConfiguration.java index 9e19dab093..ef88290de2 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/JsonbHttpMessageConvertersConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/JsonbHttpMessageConvertersConfiguration.java @@ -23,10 +23,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.condition.NoneNestedConditions; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.json.GsonHttpMessageConverter; import org.springframework.http.converter.json.JsonbHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; @@ -55,40 +55,24 @@ class JsonbHttpMessageConvertersConfiguration { } - private static class PreferJsonbOrMissingJacksonAndGsonCondition extends AnyNestedCondition { + private static class PreferJsonbOrMissingJacksonAndGsonCondition + extends AnyNestedCondition { PreferJsonbOrMissingJacksonAndGsonCondition() { super(ConfigurationPhase.REGISTER_BEAN); } - @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "jsonb", matchIfMissing = false) + @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "jsonb") static class JsonbPreferred { } - @Conditional(JacksonAndGsonMissing.class) - static class JacksonGsonMissing { + @ConditionalOnMissingBean({ MappingJackson2HttpMessageConverter.class, GsonHttpMessageConverter.class }) + static class JacksonAndGsonMissing { } } - private static class JacksonAndGsonMissing extends NoneNestedConditions { - - JacksonAndGsonMissing() { - super(ConfigurationPhase.REGISTER_BEAN); - } - - @ConditionalOnBean(MappingJackson2HttpMessageConverter.class) - static class JacksonMissing { - - } - - @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "gson") - static class GsonMissing { - - } - - } } diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 97f37f303d..cdc88b8e07 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -200,7 +200,7 @@ { "name": "spring.http.converters.preferred-json-mapper", "type": "java.lang.String", - "description": "Preferred JSON mapper to use for HTTP message conversion. Set to \"gson\" to force the use of Gson when both it and Jackson are on the classpath." + "description": "Preferred JSON mapper to use for HTTP message conversion, auto-detected according to the environment by default." }, { "name": "spring.jersey.type", diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfigurationTests.java index 9ccfe0a29c..927dce7544 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfigurationTests.java @@ -16,28 +16,29 @@ package org.springframework.boot.autoconfigure.http; -import java.util.Arrays; -import java.util.List; - import javax.json.bind.Jsonb; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; -import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration; import org.springframework.boot.autoconfigure.http.JacksonHttpMessageConvertersConfiguration.MappingJackson2HttpMessageConverterConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration; -import org.springframework.boot.test.util.TestPropertyValues; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.test.context.HidePackagesClassLoader; +import org.springframework.boot.test.context.assertj.AssertableApplicationContext; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; +import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.GsonHttpMessageConverter; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; @@ -59,240 +60,216 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class HttpMessageConvertersAutoConfigurationTests { - private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + private ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of( + HttpMessageConvertersAutoConfiguration.class)); - @After - public void close() { - if (this.context != null) { - this.context.close(); - } + @Test + public void jacksonNotAvailable() { + this.contextRunner.run((context) -> { + assertThat(context).doesNotHaveBean(ObjectMapper.class); + assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class); + assertThat(context).doesNotHaveBean(MappingJackson2XmlHttpMessageConverter.class); + }); } @Test - public void noObjectMapperMeansNoConverter() throws Exception { - this.context.register(HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBeansOfType(ObjectMapper.class)).isEmpty(); - assertThat(this.context.getBeansOfType(MappingJackson2HttpMessageConverter.class)) - .isEmpty(); - assertThat( - this.context.getBeansOfType(MappingJackson2XmlHttpMessageConverter.class)) - .isEmpty(); + public void jacksonDefaultConverter() { + this.contextRunner.withUserConfiguration(JacksonObjectMapperConfig.class) + .run(assertConverter(MappingJackson2HttpMessageConverter.class, + "mappingJackson2HttpMessageConverter")); } @Test - public void defaultJacksonConverter() throws Exception { - this.context.register(JacksonObjectMapperConfig.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - - assertConverterBeanExists(MappingJackson2HttpMessageConverter.class, - "mappingJackson2HttpMessageConverter"); - - assertConverterBeanRegisteredWithHttpMessageConverters( - MappingJackson2HttpMessageConverter.class); + public void jacksonConverterWithBuilder() { + this.contextRunner.withUserConfiguration(JacksonObjectMapperBuilderConfig.class) + .run(assertConverter(MappingJackson2HttpMessageConverter.class, + "mappingJackson2HttpMessageConverter")); } @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); + public void jacksonXmlConverterWithBuilder() { + this.contextRunner.withUserConfiguration(JacksonObjectMapperBuilderConfig.class) + .run(assertConverter(MappingJackson2XmlHttpMessageConverter.class, + "mappingJackson2XmlHttpMessageConverter")); } @Test - public void customJacksonConverter() throws Exception { - this.context.register(JacksonObjectMapperConfig.class, - JacksonConverterConfig.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - - assertConverterBeanExists(MappingJackson2HttpMessageConverter.class, - "customJacksonMessageConverter"); + public void jacksonCustomConverter() { + this.contextRunner.withUserConfiguration(JacksonObjectMapperConfig.class, + JacksonConverterConfig.class + ).run(assertConverter(MappingJackson2HttpMessageConverter.class, + "customJacksonMessageConverter")); } @Test - public void noGson() throws Exception { - this.context.register(HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBeansOfType(Gson.class).isEmpty()).isTrue(); - assertThat(this.context.getBeansOfType(GsonHttpMessageConverter.class).isEmpty()) - .isTrue(); + public void gsonNotAvailable() { + this.contextRunner.run((context) -> { + assertThat(context).doesNotHaveBean(Gson.class); + assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class); + }); } @Test - public void defaultGsonConverter() throws Exception { - this.context.register(GsonAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertConverterBeanExists(GsonHttpMessageConverter.class, - "gsonHttpMessageConverter"); - - assertConverterBeanRegisteredWithHttpMessageConverters( - GsonHttpMessageConverter.class); + public void gsonDefaultConverter() { + this.contextRunner.withConfiguration(AutoConfigurations.of( + GsonAutoConfiguration.class) + ).run(assertConverter(GsonHttpMessageConverter.class, + "gsonHttpMessageConverter")); } @Test - public void jacksonIsPreferredByDefaultWhenBothGsonAndJacksonAreAvailable() { - this.context.register(GsonAutoConfiguration.class, JacksonAutoConfiguration.class, - JsonbAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertConverterBeanExists(MappingJackson2HttpMessageConverter.class, - "mappingJackson2HttpMessageConverter"); - assertConverterBeanRegisteredWithHttpMessageConverters( - MappingJackson2HttpMessageConverter.class); - assertThat(this.context.getBeansOfType(GsonHttpMessageConverter.class)).isEmpty(); - assertThat(this.context.getBeansOfType(JsonbHttpMessageConverter.class)).isEmpty(); + public void gsonCustomConverter() { + this.contextRunner.withUserConfiguration(GsonConverterConfig.class) + .withConfiguration(AutoConfigurations.of(GsonAutoConfiguration.class)) + .run(assertConverter(GsonHttpMessageConverter.class, + "customGsonMessageConverter")); } @Test - public void gsonCanBePreferredWhenBothGsonAndJacksonAreAvailable() { - this.context.register(GsonAutoConfiguration.class, JacksonAutoConfiguration.class, - JsonbAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class); - TestPropertyValues.of("spring.http.converters.preferred-json-mapper:gson") - .applyTo(this.context); - this.context.refresh(); - assertConverterBeanExists(GsonHttpMessageConverter.class, - "gsonHttpMessageConverter"); - assertConverterBeanRegisteredWithHttpMessageConverters( - GsonHttpMessageConverter.class); - assertThat(this.context.getBeansOfType(JsonbHttpMessageConverter.class)) - .isEmpty(); - assertThat(this.context.getBeansOfType(MappingJackson2HttpMessageConverter.class)) - .isEmpty(); + public void gsonCanBePreferred() { + allOptionsRunner().withPropertyValues( + "spring.http.converters.preferred-json-mapper:gson").run((context) -> { + assertConverterBeanExists(context, GsonHttpMessageConverter.class, + "gsonHttpMessageConverter"); + assertConverterBeanRegisteredWithHttpMessageConverters(context, + GsonHttpMessageConverter.class); + assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class); + assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class); + }); } @Test - public void customGsonConverter() throws Exception { - this.context.register(GsonAutoConfiguration.class, GsonConverterConfig.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertConverterBeanExists(GsonHttpMessageConverter.class, - "customGsonMessageConverter"); - - assertConverterBeanRegisteredWithHttpMessageConverters( - GsonHttpMessageConverter.class); + public void jsonbNotAvailable() { + this.contextRunner.run((context) -> { + assertThat(context).doesNotHaveBean(Jsonb.class); + assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class); + }); } @Test - public void noJsonb() throws Exception { - this.context.register(HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertThat(this.context.getBeansOfType(Jsonb.class).isEmpty()).isTrue(); - assertThat(this.context.getBeansOfType(JsonbHttpMessageConverter.class).isEmpty()) - .isTrue(); + public void jsonbDefaultConverter() { + this.contextRunner.withConfiguration(AutoConfigurations.of( + JsonbAutoConfiguration.class) + ).run(assertConverter(JsonbHttpMessageConverter.class, + "jsonbHttpMessageConverter")); } @Test - public void defaultJsonbConverter() throws Exception { - this.context.register(JsonbAutoConfiguration.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertConverterBeanExists(JsonbHttpMessageConverter.class, - "jsonbHttpMessageConverter"); - - assertConverterBeanRegisteredWithHttpMessageConverters( - JsonbHttpMessageConverter.class); + public void jsonbCustomConverter() { + this.contextRunner.withUserConfiguration(JsonbConverterConfig.class) + .withConfiguration(AutoConfigurations.of(JsonbAutoConfiguration.class)) + .run(assertConverter(JsonbHttpMessageConverter.class, + "customJsonbMessageConverter")); } @Test - public void jsonbCanBePreferredWhenBothGsonAndJacksonAreAvailable() { - this.context.register(GsonAutoConfiguration.class, JacksonAutoConfiguration.class, - JsonbAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class); - TestPropertyValues.of("spring.http.converters.preferred-json-mapper:jsonb") - .applyTo(this.context); - this.context.refresh(); - assertConverterBeanExists(JsonbHttpMessageConverter.class, - "jsonbHttpMessageConverter"); - assertConverterBeanRegisteredWithHttpMessageConverters( - JsonbHttpMessageConverter.class); - assertThat(this.context.getBeansOfType(GsonHttpMessageConverter.class)) - .isEmpty(); - assertThat(this.context.getBeansOfType(MappingJackson2HttpMessageConverter.class)) - .isEmpty(); + public void jsonbCanBePreferred() { + allOptionsRunner().withPropertyValues( + "spring.http.converters.preferred-json-mapper:jsonb").run((context) -> { + assertConverterBeanExists(context, JsonbHttpMessageConverter.class, + "jsonbHttpMessageConverter"); + assertConverterBeanRegisteredWithHttpMessageConverters(context, + JsonbHttpMessageConverter.class); + assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class); + assertThat(context).doesNotHaveBean(MappingJackson2HttpMessageConverter.class); + }); } @Test - public void customJsonbConverter() throws Exception { - this.context.register(JsonbAutoConfiguration.class, JsonbConverterConfig.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertConverterBeanExists(JsonbHttpMessageConverter.class, - "customJsonbMessageConverter"); - assertConverterBeanRegisteredWithHttpMessageConverters( - JsonbHttpMessageConverter.class); + public void stringDefaultConverter() { + this.contextRunner.run(assertConverter(StringHttpMessageConverter.class, + "stringHttpMessageConverter")); } @Test - public void defaultStringConverter() throws Exception { - this.context.register(HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertConverterBeanExists(StringHttpMessageConverter.class, - "stringHttpMessageConverter"); - assertConverterBeanRegisteredWithHttpMessageConverters( - StringHttpMessageConverter.class); + public void sringCustomConverter() { + this.contextRunner.withUserConfiguration(StringConverterConfig.class) + .run(assertConverter(StringHttpMessageConverter.class, + "customStringMessageConverter")); } @Test - public void customStringConverter() throws Exception { - this.context.register(StringConverterConfig.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - assertConverterBeanExists(StringHttpMessageConverter.class, - "customStringMessageConverter"); - - assertConverterBeanRegisteredWithHttpMessageConverters( - StringHttpMessageConverter.class); + public void typeConstrainedConverterDoesNotPreventAutoConfigurationOfJacksonConverter() { + this.contextRunner.withUserConfiguration(JacksonObjectMapperBuilderConfig.class, + TypeConstrainedConverterConfiguration.class).run((context) -> { + BeanDefinition beanDefinition = ((GenericApplicationContext) context.getSourceApplicationContext()) + .getBeanDefinition("mappingJackson2HttpMessageConverter"); + assertThat(beanDefinition.getFactoryBeanName()).isEqualTo( + MappingJackson2HttpMessageConverterConfiguration.class.getName()); + }); } @Test - public void typeConstrainedConverterDoesNotPreventAutoConfigurationOfJacksonConverter() - throws Exception { - this.context.register(JacksonObjectMapperBuilderConfig.class, - TypeConstrainedConverterConfiguration.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - - BeanDefinition beanDefinition = this.context - .getBeanDefinition("mappingJackson2HttpMessageConverter"); - assertThat(beanDefinition.getFactoryBeanName()).isEqualTo( - MappingJackson2HttpMessageConverterConfiguration.class.getName()); + public void typeConstrainedConverterFromSpringDataDoesNotPreventAutoConfigurationOfJacksonConverter() { + this.contextRunner.withUserConfiguration(JacksonObjectMapperBuilderConfig.class, + RepositoryRestMvcConfiguration.class).run((context) -> { + BeanDefinition beanDefinition = ((GenericApplicationContext) context.getSourceApplicationContext()) + .getBeanDefinition("mappingJackson2HttpMessageConverter"); + assertThat(beanDefinition.getFactoryBeanName()).isEqualTo( + MappingJackson2HttpMessageConverterConfiguration.class.getName()); + }); } @Test - public void typeConstrainedConverterFromSpringDataDoesNotPreventAutoConfigurationOfJacksonConverter() - throws Exception { - this.context.register(JacksonObjectMapperBuilderConfig.class, - RepositoryRestMvcConfiguration.class, - HttpMessageConvertersAutoConfiguration.class); - this.context.refresh(); - BeanDefinition beanDefinition = this.context - .getBeanDefinition("mappingJackson2HttpMessageConverter"); - assertThat(beanDefinition.getFactoryBeanName()).isEqualTo( - MappingJackson2HttpMessageConverterConfiguration.class.getName()); + public void jacksonIsPreferredByDefault() { + allOptionsRunner().run((context) -> { + assertConverterBeanExists(context, MappingJackson2HttpMessageConverter.class, + "mappingJackson2HttpMessageConverter"); + assertConverterBeanRegisteredWithHttpMessageConverters(context, + MappingJackson2HttpMessageConverter.class); + assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class); + assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class); + }); } - private void assertConverterBeanExists(Class type, String beanName) { - assertThat(this.context.getBeansOfType(type)).hasSize(1); - List beanNames = Arrays.asList(this.context.getBeanDefinitionNames()); - assertThat(beanNames).contains(beanName); + @Test + public void gsonIsPreferredIfJacksonIsNotAvailable() { + allOptionsRunner() + .withClassLoader(new HidePackagesClassLoader( + ObjectMapper.class.getPackage().getName())).run((context) -> { + assertConverterBeanExists(context, GsonHttpMessageConverter.class, + "gsonHttpMessageConverter"); + assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class); + }); } - private void assertConverterBeanRegisteredWithHttpMessageConverters(Class type) { - Object converter = this.context.getBean(type); - HttpMessageConverters converters = this.context - .getBean(HttpMessageConverters.class); - assertThat(converters.getConverters().contains(converter)).isTrue(); + @Test + public void jsonbIsPreferredIfJacksonAndGsonAreNotAvailable() { + allOptionsRunner() + .withClassLoader(new HidePackagesClassLoader( + ObjectMapper.class.getPackage().getName(), + Gson.class.getPackage().getName())) + .run(assertConverter(JsonbHttpMessageConverter.class, + "jsonbHttpMessageConverter")); + } + + private ApplicationContextRunner allOptionsRunner() { + return this.contextRunner.withConfiguration(AutoConfigurations.of( + GsonAutoConfiguration.class, JacksonAutoConfiguration.class, + JsonbAutoConfiguration.class)); + } + + + private ContextConsumer assertConverter( + Class converterType, String beanName) { + return context -> { + assertConverterBeanExists(context, converterType, beanName); + assertConverterBeanRegisteredWithHttpMessageConverters(context, converterType); + }; + } + + private void assertConverterBeanExists(AssertableApplicationContext context, + Class type, String beanName) { + assertThat(context).hasSingleBean(type); + assertThat(context).hasBean(beanName); + } + + private void assertConverterBeanRegisteredWithHttpMessageConverters( + AssertableApplicationContext context, Class type) { + HttpMessageConverter converter = context.getBean(type); + HttpMessageConverters converters = context.getBean(HttpMessageConverters.class); + assertThat(converters.getConverters()).contains(converter); } @Configuration diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jsonb/JsonbAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jsonb/JsonbAutoConfigurationTests.java index 0cdb73d542..3f6a55c4b4 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jsonb/JsonbAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jsonb/JsonbAutoConfigurationTests.java @@ -18,11 +18,10 @@ package org.springframework.boot.autoconfigure.jsonb; import javax.json.bind.Jsonb; -import org.junit.After; -import org.junit.Before; import org.junit.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -33,26 +32,16 @@ import static org.assertj.core.api.Assertions.assertThat; */ public class JsonbAutoConfigurationTests { - AnnotationConfigApplicationContext context; - - @Before - public void setUp() { - this.context = new AnnotationConfigApplicationContext(); - } - - @After - public void tearDown() { - if (this.context != null) { - this.context.close(); - } - } + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(JsonbAutoConfiguration.class)); @Test public void jsonbRegistration() { - this.context.register(JsonbAutoConfiguration.class); - this.context.refresh(); - Jsonb jsonb = this.context.getBean(Jsonb.class); - assertThat(jsonb.toJson(new DataObject())).isEqualTo("{\"data\":\"hello\"}"); + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(Jsonb.class); + Jsonb jsonb = context.getBean(Jsonb.class); + assertThat(jsonb.toJson(new DataObject())).isEqualTo("{\"data\":\"hello\"}"); + }); } public class DataObject { diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index ee4bc50147..111f722999 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -297,7 +297,7 @@ content into your application; rather pick only the properties that you need. spring.hateoas.use-hal-as-default-json-media-type=true # Specify if application/hal+json responses should be sent to requests that accept application/json. # HTTP message conversion - spring.http.converters.preferred-json-mapper=jackson # Preferred JSON mapper to use for HTTP message conversion. Set to "gson" to force the use of Gson when both it and Jackson are on the classpath. + spring.http.converters.preferred-json-mapper= # Preferred JSON mapper to use for HTTP message conversion, auto-detected according to the environment by default. # HTTP encoding ({sc-spring-boot-autoconfigure}/http/HttpEncodingProperties.{sc-ext}[HttpEncodingProperties]) spring.http.encoding.charset=UTF-8 # Charset of HTTP requests and responses. Added to the "Content-Type" header if not set explicitly. 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 82a3b61b7d..76c84040c4 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -5675,16 +5675,21 @@ TIP: It's also possible to use the `@AutoConfigure...` annotations with the stan [[boot-features-testing-spring-boot-applications-testing-autoconfigured-json-tests]] ==== Auto-configured JSON tests To test that Object JSON serialization and deserialization is working as expected you can -use the `@JsonTest` annotation. `@JsonTest` will auto-configure Jackson `ObjectMapper`, -any `@JsonComponent` beans and any Jackson `Modules`. It also configures `Gson` -if you happen to be using that instead of, or as well as, Jackson. If you need to -configure elements of the auto-configuration you can use the `@AutoConfigureJsonTesters` -annotation. +use the `@JsonTest` annotation. `@JsonTest` will auto-configure the available supported +json mapper: + +* Jackson `ObjectMapper`, any `@JsonComponent` beans and any Jackson `Modules` +* `Gson` +* `Jsonb` + +If you need to configure elements of the auto-configuration you can use the +`@AutoConfigureJsonTesters` annotation. Spring Boot includes AssertJ based helpers that work with the JSONassert and JsonPath -libraries to check that JSON is as expected. The `JacksonTester`, `GsonTester` and -`BasicJsonTester` classes can be used for Jackson, Gson and Strings respectively. Any -helper fields on the test class can be `@Autowired` when using `@JsonTest`. +libraries to check that JSON is as expected. The `JacksonTester`, `GsonTester`, +`JsonbTester` and `BasicJsonTester` classes can be used for Jackson, Gson, Jsonb and +Strings respectively. Any helper fields on the test class can be `@Autowired` when using +`@JsonTest`. [source,java,indent=0] ---- @@ -6237,9 +6242,9 @@ A list of the auto-configuration that is enabled by `@DataLdapTest` can be [[boot-features-testing-spring-boot-applications-testing-autoconfigured-rest-client]] ==== Auto-configured REST clients The `@RestClientTest` annotation can be used if you want to test REST clients. By default -it will auto-configure Jackson and GSON support, configure a `RestTemplateBuilder` and -add support for `MockRestServiceServer`. The specific beans that you want to test should -be specified using `value` or `components` attribute of `@RestClientTest`: +it will auto-configure Jackson, GSON and Jsonb support, configure a `RestTemplateBuilder` +and add support for `MockRestServiceServer`. The specific beans that you want to test +should be specified using `value` or `components` attribute of `@RestClientTest`: [source,java,indent=0] diff --git a/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories index 71c98e3c43..783c0b6039 100644 --- a/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories @@ -126,7 +126,7 @@ org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\ org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\ org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\ -org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration.\ +org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\ org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\