-
Notifications
You must be signed in to change notification settings - Fork 38.8k
Description
Affects: Spring 5.3.21
This is somewhat related to #26321 - Spring using kotlin serialization over jackson (but not the same, as that's for WebMVC).
In Reactive Web, Kotlin classes that are tagged as @Serializable use Kotlin Serializers, not Jackson.
This is a reasonable default, but changing the behaviour is very difficult, and has a few surprising side effects.
In Spring WebMVC, we could re-order the HttpMessageConverter<>, and put Jackson first:
// The approach we've used for WebMVC - there's no analogus support in WebFlux.
@Configuration
class WebConfig : WebMvcConfigurationSupport() {
override fun configureMessageConverters(converters: MutableList<HttpMessageConverter<*>?>) {
super.addDefaultHttpMessageConverters(converters)
converters.sortBy { converter -> if (converter is KotlinSerializationJsonHttpMessageConverter) 1000 else 0 }
}
}The contract of WebFluxConfigurer doesn't allow modification of the list - because the BaseCodecConfigurer returns a new list each time:
@Override
public List<HttpMessageWriter<?>> getWriters() {
this.defaultCodecs.applyDefaultConfig(this.customCodecs);
List<HttpMessageWriter<?>> result = new ArrayList<>();
result.addAll(this.customCodecs.getTypedWriters().keySet());
result.addAll(this.defaultCodecs.getTypedWriters());
result.addAll(this.customCodecs.getObjectWriters().keySet());
result.addAll(this.defaultCodecs.getObjectWriters());
result.addAll(this.defaultCodecs.getCatchAllWriters());
return result;
}
Therefore, adding any sort like in WebMVC has no effect.
Changing the Kotlin encoder to null (to try to disable), doesn't work, as BaseDefaultCodecs simply adds it back:
@Override
public void kotlinSerializationJsonEncoder(Encoder<?> encoder) {
this.kotlinSerializationJsonEncoder = encoder;
initObjectWriters(); // triggers a call to getBaseObjectWriters()
}
final List<HttpMessageWriter<?>> getBaseObjectWriters() {
List<HttpMessageWriter<?>> writers = new ArrayList<>();
if (kotlinSerializationJsonPresent) {
addCodec(writers, new EncoderHttpMessageWriter<>(getKotlinSerializationJsonEncoder()));
}
...snip...
return writers;
}
The workaround I've used is to put a decorator around the configurer to re-order every single time. However, this seems awkward.
@Configuration
class CustomerWebFluxConfigSupport : WebFluxConfigurationSupport() {
override fun serverCodecConfigurer(): ServerCodecConfigurer {
return ReOrderingServerCodecConfigurer(super.serverCodecConfigurer())
}
class ReOrderingServerCodecConfigurer(private val configurer: ServerCodecConfigurer) :
ServerCodecConfigurer by configurer {
override fun getWriters(): MutableList<HttpMessageWriter<*>> {
val writers = configurer.writers
val jacksonWriterIndex =
configurer.writers.indexOfFirst { it is EncoderHttpMessageWriter && it.encoder is Jackson2JsonEncoder }
val kotlinSerializationWriterIndex =
configurer.writers.indexOfFirst { it is EncoderHttpMessageWriter && it.encoder is KotlinSerializationJsonEncoder }
if (kotlinSerializationWriterIndex == -1 || jacksonWriterIndex == -1) {
return writers
}
if (kotlinSerializationWriterIndex < jacksonWriterIndex) {
Collections.swap(writers, jacksonWriterIndex, kotlinSerializationWriterIndex)
}
return writers
}
}
}Expected / Desired Behaviour
It'd be nice if there was an easier way to configure this.
At the very least, where BaseDefaultCodecs overwrites the changed Kotlin serializer feels like a bug.