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 b794fcce9c..8b671d1686 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 ce987ad7bb..49e26c8804 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 55545b6d12..ea34cb74a7 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 61dddbe081..849c3ae164 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 b595d58d40..15550b8e3c 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 0d685878de..700bcc541d 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 +