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 +