Browse Source

Consider Jackson 2 in RSocket auto-configuration

See gh-47688
pull/47694/head
Andy Wilkinson 2 months ago committed by Phillip Webb
parent
commit
dee32e0914
  1. 2
      module/spring-boot-rsocket/build.gradle
  2. 139
      module/spring-boot-rsocket/src/main/java/org/springframework/boot/rsocket/autoconfigure/RSocketStrategiesAutoConfiguration.java
  3. 26
      module/spring-boot-rsocket/src/main/resources/META-INF/additional-spring-configuration-metadata.json
  4. 39
      module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketStrategiesAutoConfigurationTests.java

2
module/spring-boot-rsocket/build.gradle

@ -33,10 +33,12 @@ dependencies { @@ -33,10 +33,12 @@ dependencies {
optional(project(":core:spring-boot-autoconfigure"))
optional(project(":module:spring-boot-jackson"))
optional(project(":module:spring-boot-jackson2"))
optional(project(":module:spring-boot-reactor-netty"))
optional("io.rsocket:rsocket-transport-netty")
optional("org.springframework:spring-web")
optional("tools.jackson.dataformat:jackson-dataformat-cbor")
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor")
testImplementation(project(":core:spring-boot-test"))
testImplementation(project(":test-support:spring-boot-test-support"))

139
module/spring-boot-rsocket/src/main/java/org/springframework/boot/rsocket/autoconfigure/RSocketStrategiesAutoConfiguration.java

@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
package org.springframework.boot.rsocket.autoconfigure;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
import io.netty.buffer.PooledByteBufAllocator;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.dataformat.cbor.CBORMapper;
@ -23,11 +25,15 @@ import tools.jackson.dataformat.cbor.CBORMapper; @@ -23,11 +25,15 @@ import tools.jackson.dataformat.cbor.CBORMapper;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.rsocket.messaging.RSocketStrategiesCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
@ -45,7 +51,8 @@ import org.springframework.web.util.pattern.PathPatternRouteMatcher; @@ -45,7 +51,8 @@ import org.springframework.web.util.pattern.PathPatternRouteMatcher;
* @author Brian Clozel
* @since 4.0.0
*/
@AutoConfiguration(afterName = "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration")
@AutoConfiguration(afterName = { "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration",
"org.springframework.boot.jackson2.autoconfigure.Jackson2AutoConfiguration" })
@ConditionalOnClass({ io.rsocket.RSocket.class, RSocketStrategies.class, PooledByteBufAllocator.class })
public final class RSocketStrategiesAutoConfiguration {
@ -63,38 +70,114 @@ public final class RSocketStrategiesAutoConfiguration { @@ -63,38 +70,114 @@ public final class RSocketStrategiesAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CBORMapper.class)
@ConditionalOnBean(CBORMapper.class)
protected static class JacksonCborStrategyConfiguration {
private static final MediaType[] SUPPORTED_TYPES = { MediaType.APPLICATION_CBOR };
@Bean
@Order(0)
RSocketStrategiesCustomizer jacksonCborRSocketStrategyCustomizer(CBORMapper cborMapper) {
return (strategy) -> {
strategy.decoder(new JacksonCborDecoder(cborMapper, SUPPORTED_TYPES));
strategy.encoder(new JacksonCborEncoder(cborMapper, SUPPORTED_TYPES));
};
@ConditionalOnProperty(name = "spring.rsocket.preferred-mapper", havingValue = "jackson", matchIfMissing = true)
static class JacksonStrategyConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(CBORMapper.class)
@ConditionalOnBean(CBORMapper.class)
static class JacksonCborStrategyConfiguration {
private static final MediaType[] SUPPORTED_TYPES = { MediaType.APPLICATION_CBOR };
@Bean
@Order(0)
RSocketStrategiesCustomizer jacksonCborRSocketStrategyCustomizer(CBORMapper cborMapper) {
return (strategy) -> {
strategy.decoder(new JacksonCborDecoder(cborMapper, SUPPORTED_TYPES));
strategy.encoder(new JacksonCborEncoder(cborMapper, SUPPORTED_TYPES));
};
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(JsonMapper.class)
static class JacksonJsonStrategyConfiguration {
private static final MediaType[] SUPPORTED_TYPES = { MediaType.APPLICATION_JSON,
new MediaType("application", "*+json") };
@Bean
@Order(1)
@ConditionalOnBean(JsonMapper.class)
RSocketStrategiesCustomizer jacksonJsonRSocketStrategyCustomizer(JsonMapper jsonMapper) {
return (strategy) -> {
strategy.decoder(new JacksonJsonDecoder(jsonMapper, SUPPORTED_TYPES));
strategy.encoder(new JacksonJsonEncoder(jsonMapper, SUPPORTED_TYPES));
};
}
}
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(JsonMapper.class)
@ConditionalOnBean(JsonMapper.class)
protected static class JacksonJsonStrategyConfiguration {
private static final MediaType[] SUPPORTED_TYPES = { MediaType.APPLICATION_JSON,
new MediaType("application", "*+json") };
@Bean
@Order(1)
RSocketStrategiesCustomizer jacksonJsonRSocketStrategyCustomizer(JsonMapper jsonMapper) {
return (strategy) -> {
strategy.decoder(new JacksonJsonDecoder(jsonMapper, SUPPORTED_TYPES));
strategy.encoder(new JacksonJsonEncoder(jsonMapper, SUPPORTED_TYPES));
};
@Conditional(NoJacksonOrJackson2Preferred.class)
@SuppressWarnings("removal")
@Deprecated(since = "4.0.0", forRemoval = true)
static class Jackson2StrategyConfiguration {
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ ObjectMapper.class, CBORFactory.class })
static class Jackson2CborStrategyConfiguration {
private static final MediaType[] SUPPORTED_TYPES = { MediaType.APPLICATION_CBOR };
@Bean
@Order(0)
@ConditionalOnBean(org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.class)
RSocketStrategiesCustomizer jackson2CborRSocketStrategyCustomizer(
org.springframework.http.converter.json.Jackson2ObjectMapperBuilder builder) {
return (strategy) -> {
ObjectMapper objectMapper = builder.createXmlMapper(false)
.factory(new com.fasterxml.jackson.dataformat.cbor.CBORFactory())
.build();
strategy.decoder(
new org.springframework.http.codec.cbor.Jackson2CborDecoder(objectMapper, SUPPORTED_TYPES));
strategy.encoder(
new org.springframework.http.codec.cbor.Jackson2CborEncoder(objectMapper, SUPPORTED_TYPES));
};
}
}
@ConditionalOnClass(ObjectMapper.class)
static class Jackson2JsonStrategyConfiguration {
private static final MediaType[] SUPPORTED_TYPES = { MediaType.APPLICATION_JSON,
new MediaType("application", "*+json") };
@Bean
@Order(1)
@ConditionalOnBean(ObjectMapper.class)
RSocketStrategiesCustomizer jackson2JsonRSocketStrategyCustomizer(ObjectMapper objectMapper) {
return (strategy) -> {
strategy.decoder(
new org.springframework.http.codec.json.Jackson2JsonDecoder(objectMapper, SUPPORTED_TYPES));
strategy.encoder(
new org.springframework.http.codec.json.Jackson2JsonEncoder(objectMapper, SUPPORTED_TYPES));
};
}
}
}
static class NoJacksonOrJackson2Preferred extends AnyNestedCondition {
NoJacksonOrJackson2Preferred() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
@ConditionalOnMissingClass("tools.jackson.databind.json.JsonMapper")
static class NoJackson {
}
@ConditionalOnProperty(name = "spring.rsocket.preferred-mapper", havingValue = "jackson2")
static class Jackson2Preferred {
}
}

26
module/spring-boot-rsocket/src/main/resources/META-INF/additional-spring-configuration-metadata.json

@ -1,4 +1,28 @@ @@ -1,4 +1,28 @@
{
"properties": [
{
"name": "spring.rsocket.preferred-mapper",
"type": "java.lang.String",
"defaultValue": "jackson",
"description": "Preferred JSON and CBOR mapper to use. By default, auto-detected according to the environment. Supported values are 'jackson' and 'jackson2' (deprecated)."
}
],
"hints": [
{
"name": "spring.rsocket.preferred-mapper",
"values": [
{
"value": "jackson"
},
{
"value": "jackson2"
}
],
"providers": [
{
"name": "any"
}
]
}
]
}
}

39
module/spring-boot-rsocket/src/test/java/org/springframework/boot/rsocket/autoconfigure/RSocketStrategiesAutoConfigurationTests.java

@ -22,6 +22,7 @@ import tools.jackson.dataformat.cbor.CBORMapper; @@ -22,6 +22,7 @@ import tools.jackson.dataformat.cbor.CBORMapper;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.rsocket.messaging.RSocketStrategiesCustomizer;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -82,6 +83,44 @@ class RSocketStrategiesAutoConfigurationTests { @@ -82,6 +83,44 @@ class RSocketStrategiesAutoConfigurationTests {
});
}
@Test
@Deprecated(since = "4.0.0", forRemoval = true)
@SuppressWarnings("removal")
void shouldUseJackson2WhenPreferred() {
this.contextRunner
.withConfiguration(AutoConfigurations
.of(org.springframework.boot.jackson2.autoconfigure.Jackson2AutoConfiguration.class))
.withPropertyValues("spring.rsocket.preferred-mapper=jackson2")
.run((context) -> {
RSocketStrategies strategies = context.getBean(RSocketStrategies.class);
assertThat(strategies.decoders())
.hasAtLeastOneElementOfType(org.springframework.http.codec.cbor.Jackson2CborDecoder.class)
.hasAtLeastOneElementOfType(org.springframework.http.codec.json.Jackson2JsonDecoder.class);
assertThat(strategies.encoders())
.hasAtLeastOneElementOfType(org.springframework.http.codec.cbor.Jackson2CborEncoder.class)
.hasAtLeastOneElementOfType(org.springframework.http.codec.json.Jackson2JsonEncoder.class);
});
}
@Test
@Deprecated(since = "4.0.0", forRemoval = true)
@SuppressWarnings("removal")
void shouldUseJackson2WhenJacksonIsAbsent() {
this.contextRunner
.withConfiguration(AutoConfigurations
.of(org.springframework.boot.jackson2.autoconfigure.Jackson2AutoConfiguration.class))
.withClassLoader(new FilteredClassLoader(JsonMapper.class, CBORMapper.class))
.run((context) -> {
RSocketStrategies strategies = context.getBean(RSocketStrategies.class);
assertThat(strategies.decoders())
.hasAtLeastOneElementOfType(org.springframework.http.codec.cbor.Jackson2CborDecoder.class)
.hasAtLeastOneElementOfType(org.springframework.http.codec.json.Jackson2JsonDecoder.class);
assertThat(strategies.encoders())
.hasAtLeastOneElementOfType(org.springframework.http.codec.cbor.Jackson2CborEncoder.class)
.hasAtLeastOneElementOfType(org.springframework.http.codec.json.Jackson2JsonEncoder.class);
});
}
@Configuration(proxyBeanMethods = false)
static class UserStrategies {

Loading…
Cancel
Save