mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-10-06 01:09:02 +00:00
Introduce Kotlin Serialization auto-configuration
See gh-46546 Signed-off-by: Dmitry Sulman <dmitry.sulman@gmail.com>
This commit is contained in:
committed by
Brian Clozel
parent
4b0b9e5e36
commit
c7621bb6be
+6
@@ -91,6 +91,12 @@ class GsonHttpMessageConvertersConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
|
||||
havingValue = "kotlin-serialization")
|
||||
static class KotlinxSerialization {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-2
@@ -71,9 +71,11 @@ public class HttpMessageConverters implements Iterable<HttpMessageConverter<?>>
|
||||
MultiValueMap<Class<?>, Class<?>> equivalentConverters = new LinkedMultiValueMap<>();
|
||||
putIfExists(equivalentConverters, "org.springframework.http.converter.json.JacksonJsonHttpMessageConverter",
|
||||
"org.springframework.http.converter.json.MappingJackson2HttpMessageConverter",
|
||||
"org.springframework.http.converter.json.GsonHttpMessageConverter");
|
||||
"org.springframework.http.converter.json.GsonHttpMessageConverter",
|
||||
"org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter");
|
||||
putIfExists(equivalentConverters, "org.springframework.http.converter.json.MappingJackson2HttpMessageConverter",
|
||||
"org.springframework.http.converter.json.GsonHttpMessageConverter");
|
||||
"org.springframework.http.converter.json.GsonHttpMessageConverter",
|
||||
"org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter");
|
||||
EQUIVALENT_CONVERTERS = CollectionUtils.unmodifiableMultiValueMap(equivalentConverters);
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -45,15 +45,17 @@ import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
* @author Sebastien Deleuze
|
||||
* @author Stephane Nicoll
|
||||
* @author Eddú Meléndez
|
||||
* @author Dmitry Sulman
|
||||
* @since 4.0.0
|
||||
*/
|
||||
@AutoConfiguration(afterName = { "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration",
|
||||
"org.springframework.boot.jsonb.autoconfigure.JsonbAutoConfiguration",
|
||||
"org.springframework.boot.gson.autoconfigure.GsonAutoConfiguration" })
|
||||
"org.springframework.boot.gson.autoconfigure.GsonAutoConfiguration",
|
||||
"org.springframework.boot.kotlin.serialization.autoconfigure.KotlinSerializationAutoConfiguration" })
|
||||
@ConditionalOnClass(HttpMessageConverter.class)
|
||||
@Conditional(NotReactiveWebApplicationCondition.class)
|
||||
@Import({ JacksonHttpMessageConvertersConfiguration.class, GsonHttpMessageConvertersConfiguration.class,
|
||||
JsonbHttpMessageConvertersConfiguration.class })
|
||||
JsonbHttpMessageConvertersConfiguration.class, KotlinSerializationHttpMessageConvertersConfiguration.class })
|
||||
public final class HttpMessageConvertersAutoConfiguration {
|
||||
|
||||
static final String PREFERRED_MAPPER_PROPERTY = "spring.http.converters.preferred-json-mapper";
|
||||
|
||||
+27
-2
@@ -23,6 +23,7 @@ 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;
|
||||
@@ -66,8 +67,32 @@ class JsonbHttpMessageConvertersConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnMissingBean({ JacksonJsonHttpMessageConverter.class, GsonHttpMessageConverter.class })
|
||||
static class JacksonAndGsonMissing {
|
||||
@Conditional(JacksonAndGsonAndKotlinSerializationMissingCondition.class)
|
||||
static class JacksonAndGsonAndKotlinSerializationMissing {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class JacksonAndGsonAndKotlinSerializationMissingCondition extends NoneNestedConditions {
|
||||
|
||||
JacksonAndGsonAndKotlinSerializationMissingCondition() {
|
||||
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 KotlinxPreferred {
|
||||
|
||||
}
|
||||
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2012-present 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
|
||||
*
|
||||
* https://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.boot.http.converter.autoconfigure;
|
||||
|
||||
import kotlinx.serialization.json.Json;
|
||||
|
||||
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.Configuration;
|
||||
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
|
||||
|
||||
/**
|
||||
* Configuration for HTTP message converters that use Kotlin Serialization.
|
||||
*
|
||||
* @author Dmitry Sulman
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(Json.class)
|
||||
class KotlinSerializationHttpMessageConvertersConfiguration {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnBean(Json.class)
|
||||
@ConditionalOnProperty(name = HttpMessageConvertersAutoConfiguration.PREFERRED_MAPPER_PROPERTY,
|
||||
havingValue = "kotlin-serialization")
|
||||
static class KotlinSerializationHttpMessageConverterConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
KotlinSerializationJsonHttpMessageConverter kotlinSerializationJsonHttpMessageConverter(Json json) {
|
||||
return new KotlinSerializationJsonHttpMessageConverter(json);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+4
-1
@@ -4,7 +4,7 @@
|
||||
"name": "spring.http.converters.preferred-json-mapper",
|
||||
"type": "java.lang.String",
|
||||
"defaultValue": "jackson",
|
||||
"description": "Preferred JSON mapper to use for HTTP message conversion. By default, auto-detected according to the environment. Supported values are 'jackson', 'gson', and 'jsonb'. When other json mapping libraries (such as kotlinx.serialization) are present, use a custom HttpMessageConverters bean to control the preferred mapper."
|
||||
"description": "Preferred JSON mapper to use for HTTP message conversion. By default, auto-detected according to the environment. Supported values are 'jackson', 'gson', 'jsonb' and 'kotlin-serialization'. When other json mapping libraries are present, use a custom HttpMessageConverters bean to control the preferred mapper."
|
||||
}
|
||||
],
|
||||
"hints": [
|
||||
@@ -19,6 +19,9 @@
|
||||
},
|
||||
{
|
||||
"value": "jsonb"
|
||||
},
|
||||
{
|
||||
"value": "kotlin-serialization"
|
||||
}
|
||||
],
|
||||
"providers": [
|
||||
|
||||
+49
-1
@@ -21,6 +21,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import com.google.gson.Gson;
|
||||
import jakarta.json.bind.Jsonb;
|
||||
import jakarta.json.bind.JsonbBuilder;
|
||||
import kotlinx.serialization.json.Json;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import tools.jackson.databind.ObjectMapper;
|
||||
import tools.jackson.databind.json.JsonMapper;
|
||||
@@ -46,6 +47,7 @@ import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.GsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.JsonbHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -61,6 +63,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Eddú Meléndez
|
||||
* @author Moritz Halbritter
|
||||
* @author Sebastien Deleuze
|
||||
* @author Dmitry Sulman
|
||||
*/
|
||||
class HttpMessageConvertersAutoConfigurationTests {
|
||||
|
||||
@@ -128,6 +131,7 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
assertConverterBeanRegisteredWithHttpMessageConverters(context, GsonHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(JacksonJsonHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(KotlinSerializationJsonHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -159,9 +163,40 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
assertConverterBeanRegisteredWithHttpMessageConverters(context, JsonbHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(JacksonJsonHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(KotlinSerializationJsonHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void kotlinSerializationNotAvailable() {
|
||||
this.contextRunner.run((context) -> {
|
||||
assertThat(context).doesNotHaveBean(Json.class);
|
||||
assertThat(context).doesNotHaveBean(KotlinSerializationJsonHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void kotlinSerializationCustomConverter() {
|
||||
this.contextRunner.withUserConfiguration(KotlinSerializationConverterConfig.class)
|
||||
.withBean(Json.class, () -> Json.Default)
|
||||
.run(assertConverter(KotlinSerializationJsonHttpMessageConverter.class,
|
||||
"customKotlinSerializationJsonHttpMessageConverter"));
|
||||
}
|
||||
|
||||
@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);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void stringDefaultConverter() {
|
||||
this.contextRunner.run(assertConverter(StringHttpMessageConverter.class, "stringHttpMessageConverter"));
|
||||
@@ -205,6 +240,7 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
assertConverterBeanRegisteredWithHttpMessageConverters(context, JacksonJsonHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(GsonHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(KotlinSerializationJsonHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -215,6 +251,7 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
assertConverterBeanExists(context, GsonHttpMessageConverter.class, "gsonHttpMessageConverter");
|
||||
assertConverterBeanRegisteredWithHttpMessageConverters(context, GsonHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(JsonbHttpMessageConverter.class);
|
||||
assertThat(context).doesNotHaveBean(KotlinSerializationJsonHttpMessageConverter.class);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -266,7 +303,8 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
private ApplicationContextRunner allOptionsRunner() {
|
||||
return this.contextRunner.withBean(Gson.class)
|
||||
.withBean(JsonMapper.class)
|
||||
.withBean(Jsonb.class, JsonbBuilder::create);
|
||||
.withBean(Jsonb.class, JsonbBuilder::create)
|
||||
.withBean(Json.class, () -> Json.Default);
|
||||
}
|
||||
|
||||
private ContextConsumer<AssertableApplicationContext> assertConverter(
|
||||
@@ -364,6 +402,16 @@ class HttpMessageConvertersAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class KotlinSerializationConverterConfig {
|
||||
|
||||
@Bean
|
||||
KotlinSerializationJsonHttpMessageConverter customKotlinSerializationJsonHttpMessageConverter(Json json) {
|
||||
return new KotlinSerializationJsonHttpMessageConverter(json);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class StringConverterConfig {
|
||||
|
||||
|
||||
+20
@@ -31,6 +31,7 @@ import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.http.converter.cbor.JacksonCborHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.GsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter;
|
||||
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.JacksonXmlHttpMessageConverter;
|
||||
|
||||
@@ -88,6 +89,25 @@ class HttpMessageConvertersTests {
|
||||
JacksonJsonHttpMessageConverter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addBeforeExistingAnotherEquivalentConverter() {
|
||||
KotlinSerializationJsonHttpMessageConverter converter1 = new KotlinSerializationJsonHttpMessageConverter();
|
||||
HttpMessageConverters converters = new HttpMessageConverters(converter1);
|
||||
Stream<Class<?>> converterClasses = converters.getConverters().stream().map(HttpMessageConverter::getClass);
|
||||
assertThat(converterClasses).containsSequence(KotlinSerializationJsonHttpMessageConverter.class,
|
||||
JacksonJsonHttpMessageConverter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addBeforeExistingMultipleEquivalentConverters() {
|
||||
GsonHttpMessageConverter converter1 = new GsonHttpMessageConverter();
|
||||
KotlinSerializationJsonHttpMessageConverter converter2 = new KotlinSerializationJsonHttpMessageConverter();
|
||||
HttpMessageConverters converters = new HttpMessageConverters(converter1, converter2);
|
||||
Stream<Class<?>> converterClasses = converters.getConverters().stream().map(HttpMessageConverter::getClass);
|
||||
assertThat(converterClasses).containsSequence(GsonHttpMessageConverter.class,
|
||||
KotlinSerializationJsonHttpMessageConverter.class, JacksonJsonHttpMessageConverter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addNewConverters() {
|
||||
HttpMessageConverter<?> converter1 = mock(HttpMessageConverter.class);
|
||||
|
||||
Reference in New Issue
Block a user