Refine kotlinx.serialization support

This commit introduces the following changes:
 - Converters/codecs are now used based on generic type info.
 - On WebMvc and WebFlux, kotlinx.serialization is enabled along
   to Jackson because it only serializes Kotlin @Serializable classes
   which is not enough for error or actuator endpoints in Boot as
   described on spring-projects/spring-boot#24238.

TODO: leverage Kotlin/kotlinx.serialization#1164 when fixed.

Closes gh-26147
This commit is contained in:
Sébastien Deleuze
2020-11-26 12:15:23 +01:00
parent 1f701d9bad
commit 43faa439ab
13 changed files with 142 additions and 35 deletions

View File

@@ -69,10 +69,38 @@ public class KotlinSerializationJsonDecoder extends AbstractDecoder<Object> {
this.json = json;
}
/**
* Configure a limit on the number of bytes that can be buffered whenever
* the input stream needs to be aggregated. This can be a result of
* decoding to a single {@code DataBuffer},
* {@link java.nio.ByteBuffer ByteBuffer}, {@code byte[]},
* {@link org.springframework.core.io.Resource Resource}, {@code String}, etc.
* It can also occur when splitting the input stream, e.g. delimited text,
* in which case the limit applies to data buffered between delimiters.
* <p>By default this is set to 256K.
* @param byteCount the max number of bytes to buffer, or -1 for unlimited
*/
public void setMaxInMemorySize(int byteCount) {
this.stringDecoder.setMaxInMemorySize(byteCount);
}
/**
* Return the {@link #setMaxInMemorySize configured} byte count limit.
*/
public int getMaxInMemorySize() {
return this.stringDecoder.getMaxInMemorySize();
}
@Override
public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) {
return (super.canDecode(elementType, mimeType) && !CharSequence.class.isAssignableFrom(elementType.toClass()));
try {
serializer(elementType.getType());
return (super.canDecode(elementType, mimeType) && !CharSequence.class.isAssignableFrom(elementType.toClass()));
}
catch (Exception ex) {
return false;
}
}
@Override
@@ -95,6 +123,7 @@ public class KotlinSerializationJsonDecoder extends AbstractDecoder<Object> {
* Tries to find a serializer that can marshall or unmarshall instances of the given type
* using kotlinx.serialization. If no serializer can be found, an exception is thrown.
* <p>Resolved serializers are cached and cached results are returned on successive calls.
* TODO Avoid relying on throwing exception when https://github.com/Kotlin/kotlinx.serialization/pull/1164 is fixed
* @param type the type to find a serializer for
* @return a resolved serializer for the given type
* @throws RuntimeException if no serializer supporting the given type can be found

View File

@@ -71,8 +71,14 @@ public class KotlinSerializationJsonEncoder extends AbstractEncoder<Object> {
@Override
public boolean canEncode(ResolvableType elementType, @Nullable MimeType mimeType) {
return (super.canEncode(elementType, mimeType) && !String.class.isAssignableFrom(elementType.toClass()) &&
!ServerSentEvent.class.isAssignableFrom(elementType.toClass()));
try {
serializer(elementType.getType());
return (super.canEncode(elementType, mimeType) && !String.class.isAssignableFrom(elementType.toClass()) &&
!ServerSentEvent.class.isAssignableFrom(elementType.toClass()));
}
catch (Exception ex) {
return false;
}
}
@Override
@@ -105,6 +111,7 @@ public class KotlinSerializationJsonEncoder extends AbstractEncoder<Object> {
* Tries to find a serializer that can marshall or unmarshall instances of the given type
* using kotlinx.serialization. If no serializer can be found, an exception is thrown.
* <p>Resolved serializers are cached and cached results are returned on successive calls.
* TODO Avoid relying on throwing exception when https://github.com/Kotlin/kotlinx.serialization/pull/1164 is fixed
* @param type the type to find a serializer for
* @return a resolved serializer for the given type
* @throws RuntimeException if no serializer supporting the given type can be found

View File

@@ -312,6 +312,11 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
((ProtobufDecoder) codec).setMaxMessageSize(size);
}
}
if (kotlinSerializationJsonPresent) {
if (codec instanceof KotlinSerializationJsonDecoder) {
((KotlinSerializationJsonDecoder) codec).setMaxInMemorySize(size);
}
}
if (jackson2Present) {
if (codec instanceof AbstractJackson2Decoder) {
((AbstractJackson2Decoder) codec).setMaxInMemorySize(size);
@@ -385,12 +390,12 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
return Collections.emptyList();
}
List<HttpMessageReader<?>> readers = new ArrayList<>();
if (kotlinSerializationJsonPresent) {
addCodec(readers, new DecoderHttpMessageReader<>(getKotlinSerializationJsonDecoder()));
}
if (jackson2Present) {
addCodec(readers, new DecoderHttpMessageReader<>(getJackson2JsonDecoder()));
}
else if (kotlinSerializationJsonPresent) {
addCodec(readers, new DecoderHttpMessageReader<>(getKotlinSerializationJsonDecoder()));
}
if (jackson2SmilePresent) {
addCodec(readers, new DecoderHttpMessageReader<>(this.jackson2SmileDecoder != null ?
(Jackson2SmileDecoder) this.jackson2SmileDecoder : new Jackson2SmileDecoder()));
@@ -484,12 +489,12 @@ class BaseDefaultCodecs implements CodecConfigurer.DefaultCodecs, CodecConfigure
*/
final List<HttpMessageWriter<?>> getBaseObjectWriters() {
List<HttpMessageWriter<?>> writers = new ArrayList<>();
if (kotlinSerializationJsonPresent) {
writers.add(new EncoderHttpMessageWriter<>(getKotlinSerializationJsonEncoder()));
}
if (jackson2Present) {
writers.add(new EncoderHttpMessageWriter<>(getJackson2JsonEncoder()));
}
else if (kotlinSerializationJsonPresent) {
writers.add(new EncoderHttpMessageWriter<>(getKotlinSerializationJsonEncoder()));
}
if (jackson2SmilePresent) {
writers.add(new EncoderHttpMessageWriter<>(this.jackson2SmileEncoder != null ?
(Jackson2SmileEncoder) this.jackson2SmileEncoder : new Jackson2SmileEncoder()));

View File

@@ -88,6 +88,28 @@ public class KotlinSerializationJsonHttpMessageConverter extends AbstractGeneric
}
}
@Override
public boolean canRead(Type type, @Nullable Class<?> contextClass, @Nullable MediaType mediaType) {
try {
serializer(GenericTypeResolver.resolveType(type, contextClass));
return canRead(mediaType);
}
catch (Exception ex) {
return false;
}
}
@Override
public boolean canWrite(@Nullable Type type, @Nullable Class<?> clazz, @Nullable MediaType mediaType) {
try {
serializer(GenericTypeResolver.resolveType(type, clazz));
return canWrite(mediaType);
}
catch (Exception ex) {
return false;
}
}
@Override
public final Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
@@ -151,6 +173,7 @@ public class KotlinSerializationJsonHttpMessageConverter extends AbstractGeneric
* Tries to find a serializer that can marshall or unmarshall instances of the given type
* using kotlinx.serialization. If no serializer can be found, an exception is thrown.
* <p>Resolved serializers are cached and cached results are returned on successive calls.
* TODO Avoid relying on throwing exception when https://github.com/Kotlin/kotlinx.serialization/pull/1164 is fixed
* @param type the type to find a serializer for
* @return a resolved serializer for the given type
* @throws RuntimeException if no serializer supporting the given type can be found