Skip to content

Commit 6477720

Browse files
committed
Apply codecs auto-configuration to WebFlux
This commit introduces `CodecCustomizer`, a new callback-based interface for customizing the codecs configuration for WebFlux server and client. Instances of those customizers are applied to the `WebClient.Builder` and to the `WebFluxAutoConfiguration` (which deals with both WebFlux and WebFlux.fn). For now, only Jackson codecs are auto-configured, by getting the `ObjectMapper` instance created by Spring Boot. Other codecs can be configured as soon as WebFlux supports those. Closes gh-9166
1 parent 4ce726b commit 6477720

File tree

10 files changed

+245
-1
lines changed

10 files changed

+245
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.http.codec;
18+
19+
import com.fasterxml.jackson.databind.ObjectMapper;
20+
21+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
22+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
24+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
25+
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
26+
import org.springframework.boot.web.codec.CodecCustomizer;
27+
import org.springframework.context.annotation.Bean;
28+
import org.springframework.context.annotation.Configuration;
29+
import org.springframework.http.codec.CodecConfigurer;
30+
import org.springframework.http.codec.json.Jackson2JsonDecoder;
31+
import org.springframework.http.codec.json.Jackson2JsonEncoder;
32+
import org.springframework.util.MimeType;
33+
34+
/**
35+
* {@link EnableAutoConfiguration Auto-configuration}
36+
* for {@link org.springframework.core.codec.Encoder}s and {@link org.springframework.core.codec.Decoder}s.
37+
* @author Brian Clozel
38+
*/
39+
@Configuration
40+
@ConditionalOnClass(CodecConfigurer.class)
41+
@AutoConfigureAfter(JacksonAutoConfiguration.class)
42+
public class CodecsAutoConfiguration {
43+
44+
@Configuration
45+
@ConditionalOnClass(ObjectMapper.class)
46+
static class JacksonCodecConfiguration {
47+
48+
@Bean
49+
@ConditionalOnBean(ObjectMapper.class)
50+
public CodecCustomizer jacksonCodecCustomizer(ObjectMapper objectMapper) {
51+
return configurer -> {
52+
CodecConfigurer.DefaultCodecs defaults = configurer.defaultCodecs();
53+
defaults.jackson2Decoder(new Jackson2JsonDecoder(objectMapper, new MimeType[0]));
54+
defaults.jackson2Encoder(new Jackson2JsonEncoder(objectMapper, new MimeType[0]));
55+
};
56+
}
57+
58+
}
59+
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Auto-configuration for HTTP codecs.
19+
*/
20+
package org.springframework.boot.autoconfigure.http.codec;

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfiguration.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@
3232
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3333
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3434
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
35+
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
3536
import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
3637
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
3738
import org.springframework.boot.autoconfigure.web.ResourceProperties;
3839
import org.springframework.boot.context.properties.EnableConfigurationProperties;
40+
import org.springframework.boot.web.codec.CodecCustomizer;
3941
import org.springframework.context.annotation.Bean;
4042
import org.springframework.context.annotation.Configuration;
4143
import org.springframework.context.annotation.Import;
@@ -46,6 +48,7 @@
4648
import org.springframework.format.Formatter;
4749
import org.springframework.format.FormatterRegistry;
4850
import org.springframework.http.CacheControl;
51+
import org.springframework.http.codec.ServerCodecConfigurer;
4952
import org.springframework.util.ClassUtils;
5053
import org.springframework.validation.Validator;
5154
import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
@@ -79,7 +82,7 @@
7982
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
8083
@ConditionalOnClass(WebFluxConfigurer.class)
8184
@ConditionalOnMissingBean({ WebFluxConfigurationSupport.class })
82-
@AutoConfigureAfter(ReactiveWebServerAutoConfiguration.class)
85+
@AutoConfigureAfter({ ReactiveWebServerAutoConfiguration.class, CodecsAutoConfiguration.class })
8386
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
8487
public class WebFluxAutoConfiguration {
8588

@@ -98,19 +101,23 @@ public static class WebFluxConfig implements WebFluxConfigurer {
98101

99102
private final List<HandlerMethodArgumentResolver> argumentResolvers;
100103

104+
private final List<CodecCustomizer> codecCustomizers;
105+
101106
private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
102107

103108
private final List<ViewResolver> viewResolvers;
104109

105110
public WebFluxConfig(ResourceProperties resourceProperties,
106111
WebFluxProperties webFluxProperties, ListableBeanFactory beanFactory,
107112
ObjectProvider<List<HandlerMethodArgumentResolver>> resolvers,
113+
ObjectProvider<List<CodecCustomizer>> codecCustomizers,
108114
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizer,
109115
ObjectProvider<List<ViewResolver>> viewResolvers) {
110116
this.resourceProperties = resourceProperties;
111117
this.webFluxProperties = webFluxProperties;
112118
this.beanFactory = beanFactory;
113119
this.argumentResolvers = resolvers.getIfAvailable();
120+
this.codecCustomizers = codecCustomizers.getIfAvailable();
114121
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizer
115122
.getIfAvailable();
116123
this.viewResolvers = viewResolvers.getIfAvailable();
@@ -123,6 +130,13 @@ public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
123130
}
124131
}
125132

133+
@Override
134+
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
135+
if (this.codecCustomizers != null) {
136+
this.codecCustomizers.forEach(codecCustomizer -> codecCustomizer.customize(configurer));
137+
}
138+
}
139+
126140
@Override
127141
public void addResourceHandlers(ResourceHandlerRegistry registry) {
128142
if (!this.resourceProperties.isAddMappings()) {

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfiguration.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@
2020
import java.util.List;
2121

2222
import org.springframework.beans.factory.ObjectProvider;
23+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
2324
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
25+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2426
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2527
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
28+
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
29+
import org.springframework.boot.web.codec.CodecCustomizer;
2630
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
2731
import org.springframework.context.annotation.Bean;
2832
import org.springframework.context.annotation.Configuration;
2933
import org.springframework.context.annotation.Scope;
3034
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
35+
import org.springframework.core.annotation.Order;
3136
import org.springframework.util.CollectionUtils;
3237
import org.springframework.web.reactive.function.client.WebClient;
3338

@@ -41,6 +46,7 @@
4146
*/
4247
@Configuration
4348
@ConditionalOnClass(WebClient.class)
49+
@AutoConfigureAfter(CodecsAutoConfiguration.class)
4450
public class WebClientAutoConfiguration {
4551

4652
private final WebClient.Builder webClientBuilder;
@@ -62,4 +68,19 @@ public WebClientAutoConfiguration(ObjectProvider<List<WebClientCustomizer>> cust
6268
public WebClient.Builder webClientBuilder(List<WebClientCustomizer> customizers) {
6369
return this.webClientBuilder.clone();
6470
}
71+
72+
@Configuration
73+
@ConditionalOnBean(CodecCustomizer.class)
74+
protected static class WebClientCodecsConfiguration {
75+
76+
@Bean
77+
@ConditionalOnMissingBean
78+
@Order(0)
79+
public WebClientCodecCustomizer exchangeStrategiesCustomizer(
80+
List<CodecCustomizer> codecCustomizers) {
81+
return new WebClientCodecCustomizer(codecCustomizers);
82+
}
83+
84+
}
85+
6586
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2012-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.web.reactive.function.client;
18+
19+
import java.util.List;
20+
21+
import org.springframework.boot.web.codec.CodecCustomizer;
22+
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
23+
import org.springframework.web.reactive.function.client.ExchangeStrategies;
24+
import org.springframework.web.reactive.function.client.WebClient;
25+
26+
/**
27+
* {@link WebClientCustomizer} that configures codecs for the HTTP client.
28+
* @author Brian Clozel
29+
* @since 2.0.0
30+
*/
31+
public class WebClientCodecCustomizer implements WebClientCustomizer {
32+
33+
private final List<CodecCustomizer> codecCustomizers;
34+
35+
public WebClientCodecCustomizer(List<CodecCustomizer> codecCustomizers) {
36+
this.codecCustomizers = codecCustomizers;
37+
}
38+
39+
@Override
40+
public void customize(WebClient.Builder webClientBuilder) {
41+
webClientBuilder
42+
.exchangeStrategies(ExchangeStrategies.builder()
43+
.codecs(codecs -> {
44+
this.codecCustomizers.forEach(codecCustomizer -> codecCustomizer.customize(codecs));
45+
}).build());
46+
}
47+
}

spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
6262
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
6363
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
6464
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
65+
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
6566
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
6667
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
6768
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@
3232
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationTests.Config;
3333
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3434
import org.springframework.boot.test.util.TestPropertyValues;
35+
import org.springframework.boot.web.codec.CodecCustomizer;
3536
import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext;
3637
import org.springframework.context.annotation.Bean;
3738
import org.springframework.context.annotation.Configuration;
3839
import org.springframework.context.annotation.Import;
3940
import org.springframework.core.Ordered;
4041
import org.springframework.core.annotation.Order;
4142
import org.springframework.core.io.ClassPathResource;
43+
import org.springframework.http.codec.ServerCodecConfigurer;
4244
import org.springframework.http.server.reactive.HttpHandler;
4345
import org.springframework.test.util.ReflectionTestUtils;
4446
import org.springframework.util.ObjectUtils;
@@ -60,7 +62,9 @@
6062
import org.springframework.web.reactive.result.view.ViewResolver;
6163

6264
import static org.assertj.core.api.Assertions.assertThat;
65+
import static org.mockito.ArgumentMatchers.any;
6366
import static org.mockito.Mockito.mock;
67+
import static org.mockito.Mockito.verify;
6468

6569
/**
6670
* Tests for {@link WebFluxAutoConfiguration}.
@@ -112,6 +116,15 @@ public void shouldRegisterCustomHandlerMethodArgumentResolver() throws Exception
112116
HandlerMethodArgumentResolver.class));
113117
}
114118

119+
@Test
120+
public void shouldCustomizeCodecs() throws Exception {
121+
load(CustomCodecCustomizers.class);
122+
CodecCustomizer codecCustomizer =
123+
this.context.getBean("firstCodecCustomizer", CodecCustomizer.class);
124+
assertThat(codecCustomizer).isNotNull();
125+
verify(codecCustomizer).customize(any(ServerCodecConfigurer.class));
126+
}
127+
115128
@Test
116129
public void shouldRegisterResourceHandlerMapping() throws Exception {
117130
load();
@@ -316,6 +329,15 @@ public HandlerMethodArgumentResolver secondResolver() {
316329

317330
}
318331

332+
@Configuration
333+
protected static class CustomCodecCustomizers {
334+
335+
@Bean
336+
public CodecCustomizer firstCodecCustomizer() {
337+
return mock(CodecCustomizer.class);
338+
}
339+
}
340+
319341
@Configuration
320342
protected static class ViewResolvers {
321343

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfigurationTests.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@
2222
import org.junit.Test;
2323
import reactor.core.publisher.Mono;
2424

25+
import org.springframework.boot.web.codec.CodecCustomizer;
2526
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
2627
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
2728
import org.springframework.context.annotation.Bean;
2829
import org.springframework.context.annotation.Configuration;
2930
import org.springframework.http.HttpMethod;
3031
import org.springframework.http.client.reactive.ClientHttpConnector;
32+
import org.springframework.http.codec.CodecConfigurer;
3133
import org.springframework.web.reactive.function.client.WebClient;
3234

3335
import static org.assertj.core.api.Assertions.assertThat;
@@ -54,6 +56,17 @@ public void close() {
5456
}
5557
}
5658

59+
@Test
60+
public void shouldCustomizeClientCodecs() throws Exception {
61+
load(CodecConfiguration.class);
62+
WebClient.Builder builder = this.context.getBean(WebClient.Builder.class);
63+
CodecCustomizer codecCustomizer = this.context.getBean(CodecCustomizer.class);
64+
WebClientCodecCustomizer clientCustomizer = this.context.getBean(WebClientCodecCustomizer.class);
65+
builder.build();
66+
assertThat(clientCustomizer).isNotNull();
67+
verify(codecCustomizer).customize(any(CodecConfigurer.class));
68+
}
69+
5770
@Test
5871
public void webClientShouldApplyCustomizers() throws Exception {
5972
load(WebClientCustomizerConfig.class);
@@ -104,6 +117,15 @@ private void load(Class<?>... config) {
104117
this.context = ctx;
105118
}
106119

120+
@Configuration
121+
static class CodecConfiguration {
122+
123+
@Bean
124+
public CodecCustomizer myCodecCustomizer() {
125+
return mock(CodecCustomizer.class);
126+
}
127+
}
128+
107129
@Configuration
108130
static class WebClientCustomizerConfig {
109131

spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient=\
109109
org.springframework.boot.test.autoconfigure.web.client.WebClientRestTemplateAutoConfiguration,\
110110
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
111111
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
112+
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
112113
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
113114
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
114115
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration

0 commit comments

Comments
 (0)