diff --git a/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.java b/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.java index e42a6b575de..17528f4c61e 100644 --- a/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.java +++ b/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/GsonHttpMessageConvertersConfiguration.java @@ -42,7 +42,7 @@ class GsonHttpMessageConvertersConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnBean(Gson.class) - @Conditional(PreferGsonOrOtherJsonLibraryUnavailableCondition.class) + @Conditional(PreferGsonOrJacksonAndJsonbUnavailableCondition.class) static class GsonHttpMessageConverterConfiguration { @Bean @@ -55,9 +55,9 @@ class GsonHttpMessageConvertersConfiguration { } - private static class PreferGsonOrOtherJsonLibraryUnavailableCondition extends AnyNestedCondition { + private static class PreferGsonOrJacksonAndJsonbUnavailableCondition extends AnyNestedCondition { - PreferGsonOrOtherJsonLibraryUnavailableCondition() { + PreferGsonOrJacksonAndJsonbUnavailableCondition() { super(ConfigurationPhase.REGISTER_BEAN); } @@ -67,16 +67,16 @@ class GsonHttpMessageConvertersConfiguration { } - @Conditional(OtherJsonLibrariesUnavailableCondition.class) + @Conditional(JacksonAndJsonbUnavailableCondition.class) static class JacksonJsonbUnavailable { } } - private static class OtherJsonLibrariesUnavailableCondition extends NoneNestedConditions { + private static class JacksonAndJsonbUnavailableCondition extends NoneNestedConditions { - OtherJsonLibrariesUnavailableCondition() { + JacksonAndJsonbUnavailableCondition() { super(ConfigurationPhase.REGISTER_BEAN); } @@ -91,12 +91,6 @@ class GsonHttpMessageConvertersConfiguration { } - @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, - havingValue = "kotlin-serialization") - static class KotlinSerializationPreferred { - - } - } } diff --git a/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JsonbHttpMessageConvertersConfiguration.java b/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JsonbHttpMessageConvertersConfiguration.java index 96b6a4ce357..7669010ae60 100644 --- a/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JsonbHttpMessageConvertersConfiguration.java +++ b/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/JsonbHttpMessageConvertersConfiguration.java @@ -23,7 +23,6 @@ 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; @@ -42,7 +41,7 @@ class JsonbHttpMessageConvertersConfiguration { @Configuration(proxyBeanMethods = false) @ConditionalOnBean(Jsonb.class) - @Conditional(PreferJsonbOrOtherJsonLibrariesMissingCondition.class) + @Conditional(PreferJsonbOrMissingJacksonAndGsonCondition.class) static class JsonbHttpMessageConverterConfiguration { @Bean @@ -55,9 +54,9 @@ class JsonbHttpMessageConvertersConfiguration { } - private static class PreferJsonbOrOtherJsonLibrariesMissingCondition extends AnyNestedCondition { + private static class PreferJsonbOrMissingJacksonAndGsonCondition extends AnyNestedCondition { - PreferJsonbOrOtherJsonLibrariesMissingCondition() { + PreferJsonbOrMissingJacksonAndGsonCondition() { super(ConfigurationPhase.REGISTER_BEAN); } @@ -67,32 +66,8 @@ class JsonbHttpMessageConvertersConfiguration { } - @Conditional(OtherJsonLibrariesMissingMissingCondition.class) - static class OtherJsonLibrariesMissingMissing { - - } - - } - - private static class OtherJsonLibrariesMissingMissingCondition extends NoneNestedConditions { - - OtherJsonLibrariesMissingMissingCondition() { - super(ConfigurationPhase.REGISTER_BEAN); - } - - @ConditionalOnBean(JacksonJsonHttpMessageConverter.class) - static class JacksonAvailable { - - } - - @ConditionalOnBean(GsonHttpMessageConverter.class) - static class GsonAvailable { - - } - - @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, - havingValue = "kotlin-serialization") - static class KotlinPreferred { + @ConditionalOnMissingBean({ JacksonJsonHttpMessageConverter.class, GsonHttpMessageConverter.class }) + static class JacksonAndGsonMissing { } diff --git a/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/KotlinSerializationHttpMessageConvertersConfiguration.java b/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/KotlinSerializationHttpMessageConvertersConfiguration.java index d8f1edf3fc9..83cec0a5cef 100644 --- a/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/KotlinSerializationHttpMessageConvertersConfiguration.java +++ b/module/spring-boot-http-converter/src/main/java/org/springframework/boot/http/converter/autoconfigure/KotlinSerializationHttpMessageConvertersConfiguration.java @@ -16,61 +16,33 @@ package org.springframework.boot.http.converter.autoconfigure; +import kotlinx.serialization.Serializable; import kotlinx.serialization.json.Json; -import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; 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.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.JacksonJsonHttpMessageConverter; -import org.springframework.http.converter.json.JsonbHttpMessageConverter; +import org.springframework.core.annotation.Order; import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter; /** * Configuration for HTTP message converters that use Kotlin Serialization. * + * @author Brian Clozel * @author Dmitry Sulman */ @Configuration(proxyBeanMethods = false) -@ConditionalOnClass(Json.class) +@ConditionalOnClass({ Serializable.class, Json.class }) +@ConditionalOnBean(Json.class) class KotlinSerializationHttpMessageConvertersConfiguration { - @Configuration(proxyBeanMethods = false) - @ConditionalOnBean(Json.class) - @Conditional(PreferKotlinSerializationOrOtherJsonLibrariesUnavailableCondition.class) - static class KotlinSerializationHttpMessageConverterConfiguration { - - @Bean - @ConditionalOnMissingBean - KotlinSerializationJsonHttpMessageConverter kotlinSerializationJsonHttpMessageConverter(Json json) { - return new KotlinSerializationJsonHttpMessageConverter(json); - } - - } - - private static class PreferKotlinSerializationOrOtherJsonLibrariesUnavailableCondition extends AnyNestedCondition { - - PreferKotlinSerializationOrOtherJsonLibrariesUnavailableCondition() { - super(ConfigurationPhase.REGISTER_BEAN); - } - - @ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY, - havingValue = "kotlin-serialization") - static class KotlinSerializationPreferred { - - } - - @ConditionalOnMissingBean({ JacksonJsonHttpMessageConverter.class, JsonbHttpMessageConverter.class, - GsonHttpMessageConverter.class }) - static class OtherJsonLibrariesUnavailable { - - } - + @Bean + @ConditionalOnMissingBean + @Order(-10) // configured ahead of JSON mappers + KotlinSerializationJsonHttpMessageConverter kotlinSerializationJsonHttpMessageConverter(Json json) { + return new KotlinSerializationJsonHttpMessageConverter(json); } } diff --git a/module/spring-boot-http-converter/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/module/spring-boot-http-converter/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 6bf4ae4ff3b..d6d92477e76 100644 --- a/module/spring-boot-http-converter/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/module/spring-boot-http-converter/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -19,9 +19,6 @@ }, { "value": "jsonb" - }, - { - "value": "kotlin-serialization" } ], "providers": [ diff --git a/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfigurationTests.java b/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfigurationTests.java index dea61f29d7a..673b8b83f4a 100644 --- a/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfigurationTests.java +++ b/module/spring-boot-http-converter/src/test/java/org/springframework/boot/http/converter/autoconfigure/HttpMessageConvertersAutoConfigurationTests.java @@ -17,6 +17,7 @@ package org.springframework.boot.http.converter.autoconfigure; import java.nio.charset.StandardCharsets; +import java.util.List; import com.google.gson.Gson; import jakarta.json.bind.Jsonb; @@ -131,7 +132,6 @@ class HttpMessageConvertersAutoConfigurationTests { assertConverterBeanRegisteredWithHttpMessageConverters(context, GsonHttpMessageConverter.class); assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class); assertThat(context).doesNotHaveBean(JacksonJsonHttpMessageConverter.class); - assertThat(context).doesNotHaveBean(KotlinSerializationJsonHttpMessageConverter.class); }); } @@ -163,7 +163,6 @@ class HttpMessageConvertersAutoConfigurationTests { assertConverterBeanRegisteredWithHttpMessageConverters(context, JsonbHttpMessageConverter.class); assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class); assertThat(context).doesNotHaveBean(JacksonJsonHttpMessageConverter.class); - assertThat(context).doesNotHaveBean(KotlinSerializationJsonHttpMessageConverter.class); }); } @@ -184,17 +183,15 @@ class HttpMessageConvertersAutoConfigurationTests { } @Test - void kotlinSerializationCanBePreferred() { - allOptionsRunner().withPropertyValues("spring.http.converters.preferred-json-mapper:kotlin-serialization") - .run((context) -> { - assertConverterBeanExists(context, KotlinSerializationJsonHttpMessageConverter.class, - "kotlinSerializationJsonHttpMessageConverter"); - assertConverterBeanRegisteredWithHttpMessageConverters(context, - KotlinSerializationJsonHttpMessageConverter.class); - assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class); - assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class); - assertThat(context).doesNotHaveBean(JacksonJsonHttpMessageConverter.class); - }); + void kotlinSerializationOrderedAheadOfJsonConverter() { + allOptionsRunner().run((context) -> { + assertConverterBeanExists(context, KotlinSerializationJsonHttpMessageConverter.class, + "kotlinSerializationJsonHttpMessageConverter"); + assertConverterBeanRegisteredWithHttpMessageConverters(context, + KotlinSerializationJsonHttpMessageConverter.class); + assertConvertersBeanRegisteredWithHttpMessageConverters(context, + List.of(KotlinSerializationJsonHttpMessageConverter.class, JacksonJsonHttpMessageConverter.class)); + }); } @Test @@ -240,7 +237,6 @@ class HttpMessageConvertersAutoConfigurationTests { assertConverterBeanRegisteredWithHttpMessageConverters(context, JacksonJsonHttpMessageConverter.class); assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class); assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class); - assertThat(context).doesNotHaveBean(KotlinSerializationJsonHttpMessageConverter.class); }); } @@ -251,7 +247,6 @@ class HttpMessageConvertersAutoConfigurationTests { assertConverterBeanExists(context, GsonHttpMessageConverter.class, "gsonHttpMessageConverter"); assertConverterBeanRegisteredWithHttpMessageConverters(context, GsonHttpMessageConverter.class); assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class); - assertThat(context).doesNotHaveBean(KotlinSerializationJsonHttpMessageConverter.class); }); } @@ -327,6 +322,15 @@ class HttpMessageConvertersAutoConfigurationTests { assertThat(converters.getConverters()).contains(converter); } + private void assertConvertersBeanRegisteredWithHttpMessageConverters(AssertableApplicationContext context, + List>> types) { + + List> converterInstances = types.stream().map(context::getBean).toList(); + + HttpMessageConverters converters = context.getBean(HttpMessageConverters.class); + assertThat(converters.getConverters()).containsSubsequence(converterInstances); + } + @Configuration(proxyBeanMethods = false) static class JacksonJsonMapperConfig {