Skip to content

Commit dee32e0

Browse files
wilkinsonaphilwebb
authored andcommitted
Consider Jackson 2 in RSocket auto-configuration
See gh-47688
1 parent 2264b77 commit dee32e0

File tree

4 files changed

+177
-29
lines changed

4 files changed

+177
-29
lines changed

module/spring-boot-rsocket/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ dependencies {
3333

3434
optional(project(":core:spring-boot-autoconfigure"))
3535
optional(project(":module:spring-boot-jackson"))
36+
optional(project(":module:spring-boot-jackson2"))
3637
optional(project(":module:spring-boot-reactor-netty"))
3738
optional("io.rsocket:rsocket-transport-netty")
3839
optional("org.springframework:spring-web")
3940
optional("tools.jackson.dataformat:jackson-dataformat-cbor")
41+
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor")
4042

4143
testImplementation(project(":core:spring-boot-test"))
4244
testImplementation(project(":test-support:spring-boot-test-support"))

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

Lines changed: 111 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,24 @@
1616

1717
package org.springframework.boot.rsocket.autoconfigure;
1818

19+
import com.fasterxml.jackson.databind.ObjectMapper;
20+
import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
1921
import io.netty.buffer.PooledByteBufAllocator;
2022
import tools.jackson.databind.json.JsonMapper;
2123
import tools.jackson.dataformat.cbor.CBORMapper;
2224

2325
import org.springframework.beans.factory.ObjectProvider;
2426
import org.springframework.boot.autoconfigure.AutoConfiguration;
2527
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
28+
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
2629
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2730
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2831
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
32+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
33+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2934
import org.springframework.boot.rsocket.messaging.RSocketStrategiesCustomizer;
3035
import org.springframework.context.annotation.Bean;
36+
import org.springframework.context.annotation.Conditional;
3137
import org.springframework.context.annotation.Configuration;
3238
import org.springframework.core.annotation.Order;
3339
import org.springframework.http.MediaType;
@@ -45,7 +51,8 @@
4551
* @author Brian Clozel
4652
* @since 4.0.0
4753
*/
48-
@AutoConfiguration(afterName = "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration")
54+
@AutoConfiguration(afterName = { "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration",
55+
"org.springframework.boot.jackson2.autoconfigure.Jackson2AutoConfiguration" })
4956
@ConditionalOnClass({ io.rsocket.RSocket.class, RSocketStrategies.class, PooledByteBufAllocator.class })
5057
public final class RSocketStrategiesAutoConfiguration {
5158

@@ -63,38 +70,114 @@ RSocketStrategies rSocketStrategies(ObjectProvider<RSocketStrategiesCustomizer>
6370
}
6471

6572
@Configuration(proxyBeanMethods = false)
66-
@ConditionalOnClass(CBORMapper.class)
67-
@ConditionalOnBean(CBORMapper.class)
68-
protected static class JacksonCborStrategyConfiguration {
69-
70-
private static final MediaType[] SUPPORTED_TYPES = { MediaType.APPLICATION_CBOR };
71-
72-
@Bean
73-
@Order(0)
74-
RSocketStrategiesCustomizer jacksonCborRSocketStrategyCustomizer(CBORMapper cborMapper) {
75-
return (strategy) -> {
76-
strategy.decoder(new JacksonCborDecoder(cborMapper, SUPPORTED_TYPES));
77-
strategy.encoder(new JacksonCborEncoder(cborMapper, SUPPORTED_TYPES));
78-
};
73+
@ConditionalOnProperty(name = "spring.rsocket.preferred-mapper", havingValue = "jackson", matchIfMissing = true)
74+
static class JacksonStrategyConfiguration {
75+
76+
@Configuration(proxyBeanMethods = false)
77+
@ConditionalOnClass(CBORMapper.class)
78+
@ConditionalOnBean(CBORMapper.class)
79+
static class JacksonCborStrategyConfiguration {
80+
81+
private static final MediaType[] SUPPORTED_TYPES = { MediaType.APPLICATION_CBOR };
82+
83+
@Bean
84+
@Order(0)
85+
RSocketStrategiesCustomizer jacksonCborRSocketStrategyCustomizer(CBORMapper cborMapper) {
86+
return (strategy) -> {
87+
strategy.decoder(new JacksonCborDecoder(cborMapper, SUPPORTED_TYPES));
88+
strategy.encoder(new JacksonCborEncoder(cborMapper, SUPPORTED_TYPES));
89+
};
90+
}
91+
92+
}
93+
94+
@Configuration(proxyBeanMethods = false)
95+
@ConditionalOnClass(JsonMapper.class)
96+
static class JacksonJsonStrategyConfiguration {
97+
98+
private static final MediaType[] SUPPORTED_TYPES = { MediaType.APPLICATION_JSON,
99+
new MediaType("application", "*+json") };
100+
101+
@Bean
102+
@Order(1)
103+
@ConditionalOnBean(JsonMapper.class)
104+
RSocketStrategiesCustomizer jacksonJsonRSocketStrategyCustomizer(JsonMapper jsonMapper) {
105+
return (strategy) -> {
106+
strategy.decoder(new JacksonJsonDecoder(jsonMapper, SUPPORTED_TYPES));
107+
strategy.encoder(new JacksonJsonEncoder(jsonMapper, SUPPORTED_TYPES));
108+
};
109+
}
110+
79111
}
80112

81113
}
82114

83115
@Configuration(proxyBeanMethods = false)
84-
@ConditionalOnClass(JsonMapper.class)
85-
@ConditionalOnBean(JsonMapper.class)
86-
protected static class JacksonJsonStrategyConfiguration {
87-
88-
private static final MediaType[] SUPPORTED_TYPES = { MediaType.APPLICATION_JSON,
89-
new MediaType("application", "*+json") };
90-
91-
@Bean
92-
@Order(1)
93-
RSocketStrategiesCustomizer jacksonJsonRSocketStrategyCustomizer(JsonMapper jsonMapper) {
94-
return (strategy) -> {
95-
strategy.decoder(new JacksonJsonDecoder(jsonMapper, SUPPORTED_TYPES));
96-
strategy.encoder(new JacksonJsonEncoder(jsonMapper, SUPPORTED_TYPES));
97-
};
116+
@Conditional(NoJacksonOrJackson2Preferred.class)
117+
@SuppressWarnings("removal")
118+
@Deprecated(since = "4.0.0", forRemoval = true)
119+
static class Jackson2StrategyConfiguration {
120+
121+
@Configuration(proxyBeanMethods = false)
122+
@ConditionalOnClass({ ObjectMapper.class, CBORFactory.class })
123+
static class Jackson2CborStrategyConfiguration {
124+
125+
private static final MediaType[] SUPPORTED_TYPES = { MediaType.APPLICATION_CBOR };
126+
127+
@Bean
128+
@Order(0)
129+
@ConditionalOnBean(org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.class)
130+
RSocketStrategiesCustomizer jackson2CborRSocketStrategyCustomizer(
131+
org.springframework.http.converter.json.Jackson2ObjectMapperBuilder builder) {
132+
return (strategy) -> {
133+
ObjectMapper objectMapper = builder.createXmlMapper(false)
134+
.factory(new com.fasterxml.jackson.dataformat.cbor.CBORFactory())
135+
.build();
136+
strategy.decoder(
137+
new org.springframework.http.codec.cbor.Jackson2CborDecoder(objectMapper, SUPPORTED_TYPES));
138+
strategy.encoder(
139+
new org.springframework.http.codec.cbor.Jackson2CborEncoder(objectMapper, SUPPORTED_TYPES));
140+
};
141+
}
142+
143+
}
144+
145+
@ConditionalOnClass(ObjectMapper.class)
146+
static class Jackson2JsonStrategyConfiguration {
147+
148+
private static final MediaType[] SUPPORTED_TYPES = { MediaType.APPLICATION_JSON,
149+
new MediaType("application", "*+json") };
150+
151+
@Bean
152+
@Order(1)
153+
@ConditionalOnBean(ObjectMapper.class)
154+
RSocketStrategiesCustomizer jackson2JsonRSocketStrategyCustomizer(ObjectMapper objectMapper) {
155+
return (strategy) -> {
156+
strategy.decoder(
157+
new org.springframework.http.codec.json.Jackson2JsonDecoder(objectMapper, SUPPORTED_TYPES));
158+
strategy.encoder(
159+
new org.springframework.http.codec.json.Jackson2JsonEncoder(objectMapper, SUPPORTED_TYPES));
160+
};
161+
}
162+
163+
}
164+
165+
}
166+
167+
static class NoJacksonOrJackson2Preferred extends AnyNestedCondition {
168+
169+
NoJacksonOrJackson2Preferred() {
170+
super(ConfigurationPhase.PARSE_CONFIGURATION);
171+
}
172+
173+
@ConditionalOnMissingClass("tools.jackson.databind.json.JsonMapper")
174+
static class NoJackson {
175+
176+
}
177+
178+
@ConditionalOnProperty(name = "spring.rsocket.preferred-mapper", havingValue = "jackson2")
179+
static class Jackson2Preferred {
180+
98181
}
99182

100183
}
Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,28 @@
11
{
22
"properties": [
3+
{
4+
"name": "spring.rsocket.preferred-mapper",
5+
"type": "java.lang.String",
6+
"defaultValue": "jackson",
7+
"description": "Preferred JSON and CBOR mapper to use. By default, auto-detected according to the environment. Supported values are 'jackson' and 'jackson2' (deprecated)."
8+
}
9+
],
10+
"hints": [
11+
{
12+
"name": "spring.rsocket.preferred-mapper",
13+
"values": [
14+
{
15+
"value": "jackson"
16+
},
17+
{
18+
"value": "jackson2"
19+
}
20+
],
21+
"providers": [
22+
{
23+
"name": "any"
24+
}
25+
]
26+
}
327
]
4-
}
28+
}

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import org.springframework.boot.autoconfigure.AutoConfigurations;
2424
import org.springframework.boot.rsocket.messaging.RSocketStrategiesCustomizer;
25+
import org.springframework.boot.test.context.FilteredClassLoader;
2526
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
2627
import org.springframework.context.annotation.Bean;
2728
import org.springframework.context.annotation.Configuration;
@@ -82,6 +83,44 @@ void shouldUseStrategiesCustomizer() {
8283
});
8384
}
8485

86+
@Test
87+
@Deprecated(since = "4.0.0", forRemoval = true)
88+
@SuppressWarnings("removal")
89+
void shouldUseJackson2WhenPreferred() {
90+
this.contextRunner
91+
.withConfiguration(AutoConfigurations
92+
.of(org.springframework.boot.jackson2.autoconfigure.Jackson2AutoConfiguration.class))
93+
.withPropertyValues("spring.rsocket.preferred-mapper=jackson2")
94+
.run((context) -> {
95+
RSocketStrategies strategies = context.getBean(RSocketStrategies.class);
96+
assertThat(strategies.decoders())
97+
.hasAtLeastOneElementOfType(org.springframework.http.codec.cbor.Jackson2CborDecoder.class)
98+
.hasAtLeastOneElementOfType(org.springframework.http.codec.json.Jackson2JsonDecoder.class);
99+
assertThat(strategies.encoders())
100+
.hasAtLeastOneElementOfType(org.springframework.http.codec.cbor.Jackson2CborEncoder.class)
101+
.hasAtLeastOneElementOfType(org.springframework.http.codec.json.Jackson2JsonEncoder.class);
102+
});
103+
}
104+
105+
@Test
106+
@Deprecated(since = "4.0.0", forRemoval = true)
107+
@SuppressWarnings("removal")
108+
void shouldUseJackson2WhenJacksonIsAbsent() {
109+
this.contextRunner
110+
.withConfiguration(AutoConfigurations
111+
.of(org.springframework.boot.jackson2.autoconfigure.Jackson2AutoConfiguration.class))
112+
.withClassLoader(new FilteredClassLoader(JsonMapper.class, CBORMapper.class))
113+
.run((context) -> {
114+
RSocketStrategies strategies = context.getBean(RSocketStrategies.class);
115+
assertThat(strategies.decoders())
116+
.hasAtLeastOneElementOfType(org.springframework.http.codec.cbor.Jackson2CborDecoder.class)
117+
.hasAtLeastOneElementOfType(org.springframework.http.codec.json.Jackson2JsonDecoder.class);
118+
assertThat(strategies.encoders())
119+
.hasAtLeastOneElementOfType(org.springframework.http.codec.cbor.Jackson2CborEncoder.class)
120+
.hasAtLeastOneElementOfType(org.springframework.http.codec.json.Jackson2JsonEncoder.class);
121+
});
122+
}
123+
85124
@Configuration(proxyBeanMethods = false)
86125
static class UserStrategies {
87126

0 commit comments

Comments
 (0)