From e8d39a1539cef97dd88f68c90b76d625bbcd8833 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 13 May 2019 22:37:02 +0200 Subject: [PATCH] Add support for CBOR codecs in RSocket This commit auto-configures CBOR (see https://cbor.io/) codecs in the RSocketStrategies, using Jackson binary format support. The required dependency is added to the rsocket starter. Binary codecs are well suited for RSocket payloads, so this codec is added first to the list of codecs (before the JSON one already supported). Closes gh-16830 --- .../spring-boot-autoconfigure/pom.xml | 5 +++ .../RSocketStrategiesAutoConfiguration.java | 33 ++++++++++++++++--- ...ocketStrategiesAutoConfigurationTests.java | 26 +++++++++++---- spring-boot-project/spring-boot-docs/pom.xml | 5 +++ .../main/asciidoc/spring-boot-features.adoc | 16 +++++++++ .../spring-boot-starter-rsocket/pom.xml | 4 +++ 6 files changed, 79 insertions(+), 10 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/pom.xml b/spring-boot-project/spring-boot-autoconfigure/pom.xml index b794fcce9cc..8b671d16868 100755 --- a/spring-boot-project/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-project/spring-boot-autoconfigure/pom.xml @@ -42,6 +42,11 @@ jackson-databind true + + com.fasterxml.jackson.dataformat + jackson-dataformat-cbor + true + com.fasterxml.jackson.dataformat jackson-dataformat-xml diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketStrategiesAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketStrategiesAutoConfiguration.java index ce987ad7bb4..49e26c8804a 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketStrategiesAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/rsocket/RSocketStrategiesAutoConfiguration.java @@ -17,6 +17,7 @@ package org.springframework.boot.autoconfigure.rsocket; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.cbor.CBORFactory; import io.netty.buffer.PooledByteBufAllocator; import io.rsocket.RSocketFactory; @@ -34,8 +35,11 @@ import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.annotation.Order; import org.springframework.core.io.buffer.NettyDataBufferFactory; import org.springframework.http.MediaType; +import org.springframework.http.codec.cbor.Jackson2CborDecoder; +import org.springframework.http.codec.cbor.Jackson2CborEncoder; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.messaging.rsocket.RSocketStrategies; /** @@ -56,20 +60,41 @@ public class RSocketStrategiesAutoConfiguration { ObjectProvider customizers) { RSocketStrategies.Builder builder = RSocketStrategies.builder(); builder.reactiveAdapterStrategy(ReactiveAdapterRegistry.getSharedInstance()); - customizers.stream().forEach((customizer) -> customizer.customize(builder)); + customizers.orderedStream() + .forEach((customizer) -> customizer.customize(builder)); builder.dataBufferFactory( new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT)); return builder.build(); } @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(ObjectMapper.class) - protected static class JacksonStrategyConfiguration { + @ConditionalOnClass({ ObjectMapper.class, CBORFactory.class }) + protected static class JacksonCborStrategyConfiguration { @Bean @Order(0) + @ConditionalOnBean(Jackson2ObjectMapperBuilder.class) + public RSocketStrategiesCustomizer jacksonCborStrategyCustomizer( + Jackson2ObjectMapperBuilder builder) { + return (strategy) -> { + ObjectMapper objectMapper = builder.factory(new CBORFactory()).build(); + MediaType[] supportedTypes = new MediaType[] { + new MediaType("application", "cbor") }; + strategy.decoder(new Jackson2CborDecoder(objectMapper, supportedTypes)); + strategy.encoder(new Jackson2CborEncoder(objectMapper, supportedTypes)); + }; + } + + } + + @Configuration(proxyBeanMethods = false) + @ConditionalOnClass(ObjectMapper.class) + protected static class JacksonJsonStrategyConfiguration { + + @Bean + @Order(1) @ConditionalOnBean(ObjectMapper.class) - public RSocketStrategiesCustomizer jacksonStrategyCustomizer( + public RSocketStrategiesCustomizer jacksonJsonStrategyCustomizer( ObjectMapper objectMapper) { return (strategy) -> { MediaType[] supportedTypes = new MediaType[] { MediaType.APPLICATION_JSON, diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketStrategiesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketStrategiesAutoConfigurationTests.java index 55545b6d120..ea34cb74a75 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketStrategiesAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/rsocket/RSocketStrategiesAutoConfigurationTests.java @@ -26,8 +26,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.codec.CharSequenceEncoder; import org.springframework.core.codec.StringDecoder; +import org.springframework.http.codec.cbor.Jackson2CborDecoder; +import org.springframework.http.codec.cbor.Jackson2CborEncoder; import org.springframework.http.codec.json.Jackson2JsonDecoder; import org.springframework.http.codec.json.Jackson2JsonEncoder; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.messaging.rsocket.RSocketStrategies; import static org.assertj.core.api.Assertions.assertThat; @@ -48,10 +51,16 @@ public class RSocketStrategiesAutoConfigurationTests { this.contextRunner.run((context) -> { assertThat(context).getBeans(RSocketStrategies.class).hasSize(1); RSocketStrategies strategies = context.getBean(RSocketStrategies.class); - assertThat(strategies.decoders()).hasSize(1) - .hasOnlyElementsOfType(Jackson2JsonDecoder.class); - assertThat(strategies.encoders()).hasSize(1) - .hasOnlyElementsOfType(Jackson2JsonEncoder.class); + assertThat(strategies.decoders()).hasSize(2); + assertThat(strategies.decoders().get(0)) + .isInstanceOf(Jackson2CborDecoder.class); + assertThat(strategies.decoders().get(1)) + .isInstanceOf(Jackson2JsonDecoder.class); + assertThat(strategies.encoders()).hasSize(2); + assertThat(strategies.encoders().get(0)) + .isInstanceOf(Jackson2CborEncoder.class); + assertThat(strategies.encoders().get(1)) + .isInstanceOf(Jackson2JsonEncoder.class); }); } @@ -71,9 +80,9 @@ public class RSocketStrategiesAutoConfigurationTests { assertThat(context).getBeans(RSocketStrategies.class).hasSize(1); RSocketStrategies strategies = context .getBean(RSocketStrategies.class); - assertThat(strategies.decoders()).hasSize(2) + assertThat(strategies.decoders()).hasSize(3) .hasAtLeastOneElementOfType(StringDecoder.class); - assertThat(strategies.encoders()).hasSize(2) + assertThat(strategies.encoders()).hasSize(3) .hasAtLeastOneElementOfType(CharSequenceEncoder.class); }); } @@ -86,6 +95,11 @@ public class RSocketStrategiesAutoConfigurationTests { return new ObjectMapper(); } + @Bean + public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() { + return new Jackson2ObjectMapperBuilder(); + } + } @Configuration diff --git a/spring-boot-project/spring-boot-docs/pom.xml b/spring-boot-project/spring-boot-docs/pom.xml index 61dddbe0817..849c3ae164f 100644 --- a/spring-boot-project/spring-boot-docs/pom.xml +++ b/spring-boot-project/spring-boot-docs/pom.xml @@ -135,6 +135,11 @@ jackson-datatype-joda true + + com.fasterxml.jackson.dataformat + jackson-dataformat-cbor + true + com.fasterxml.jackson.dataformat jackson-dataformat-xml diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index b595d58d40e..15550b8e3c5 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -3483,6 +3483,22 @@ The following code shows a typical `@Controller`: } ---- +[[boot-features-rsocket-strategies-auto-configuration]] +=== RSocket Strategies Auto-configuration +Spring Boot auto-configures an `RSocketStrategies` bean that provides all the required +infrastructure for encoding and decoding RSocket payloads. By default, the +auto-configuration will try to configure the following (in order): + +1. https://cbor.io/[CBOR] codecs with Jackson +2. JSON codecs with Jackson + +The `spring-boot-starter-rsocket` Starter provides both dependencies. + +Developers can customize the `RSocketStrategies` component by creating beans that +implement the `RSocketStrategiesCustomizer` interface. Note that their `@Order` is +important, as it determines the order of codecs. + + [[boot-features-rsocket-server-auto-configuration]] === RSocket server Auto-configuration Spring Boot provides auto-configuration for RSocket servers. The required dependencies diff --git a/spring-boot-project/spring-boot-starters/spring-boot-starter-rsocket/pom.xml b/spring-boot-project/spring-boot-starters/spring-boot-starter-rsocket/pom.xml index 0d685878de2..700bcc541d1 100644 --- a/spring-boot-project/spring-boot-starters/spring-boot-starter-rsocket/pom.xml +++ b/spring-boot-project/spring-boot-starters/spring-boot-starter-rsocket/pom.xml @@ -54,5 +54,9 @@ org.springframework.boot spring-boot-starter-json + + com.fasterxml.jackson.dataformat + jackson-dataformat-cbor +