Skip to content

Commit 9f2dafd

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 `WebClientBuilder` 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 e5989d5 commit 9f2dafd

File tree

12 files changed

+278
-1
lines changed

12 files changed

+278
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 org.springframework.http.codec.CodecConfigurer;
20+
21+
/**
22+
* Callback interface that can be used to customize codecs configuration
23+
* for an HTTP client and/or server with a {@link CodecConfigurer}.
24+
* @author Brian Clozel
25+
* @since 2.0
26+
*/
27+
@FunctionalInterface
28+
public interface CodecCustomizer {
29+
30+
/**
31+
* Callback to customize a {@link CodecConfigurer} instance.
32+
* @param configurer codec configurer to customize
33+
*/
34+
void customize(CodecConfigurer configurer);
35+
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 org.springframework.boot.autoconfigure.AutoConfigureAfter;
20+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
22+
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
23+
import org.springframework.context.annotation.Configuration;
24+
import org.springframework.context.annotation.Import;
25+
import org.springframework.http.codec.CodecConfigurer;
26+
27+
/**
28+
* {@link EnableAutoConfiguration Auto-configuration}
29+
* for {@link org.springframework.core.codec.Encoder}s and {@link org.springframework.core.codec.Decoder}s.
30+
* @author Brian Clozel
31+
*/
32+
@Configuration
33+
@ConditionalOnClass(CodecConfigurer.class)
34+
@AutoConfigureAfter(JacksonAutoConfiguration.class)
35+
@Import(JacksonCodecConfiguration.class)
36+
public class CodecsAutoConfiguration {
37+
38+
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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.condition.ConditionalOnBean;
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.http.codec.CodecConfigurer;
26+
import org.springframework.http.codec.json.Jackson2JsonDecoder;
27+
import org.springframework.http.codec.json.Jackson2JsonEncoder;
28+
import org.springframework.util.MimeType;
29+
30+
/**
31+
* Configuration for HTTP codecs that use Jackson.
32+
* @author Brian Clozel
33+
*/
34+
@Configuration
35+
@ConditionalOnClass(ObjectMapper.class)
36+
class JacksonCodecConfiguration {
37+
38+
@Configuration
39+
@ConditionalOnBean(ObjectMapper.class)
40+
protected static class JacksonJsonCodecConfiguration {
41+
42+
@Bean
43+
public CodecCustomizer jacksonCodecCustomizer(ObjectMapper objectMapper) {
44+
return configurer -> {
45+
CodecConfigurer.DefaultCodecs defaults = configurer.defaultCodecs();
46+
defaults.jackson2Decoder(new Jackson2JsonDecoder(objectMapper, new MimeType[0]));
47+
defaults.jackson2Encoder(new Jackson2JsonEncoder(objectMapper, new MimeType[0]));
48+
};
49+
}
50+
}
51+
52+
}
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,6 +32,8 @@
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.CodecCustomizer;
36+
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
3537
import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
3638
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
3739
import org.springframework.boot.autoconfigure.web.ResourceProperties;
@@ -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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@
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.CodecCustomizer;
29+
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
2630
import org.springframework.boot.web.reactive.function.client.WebClientBuilder;
2731
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
2832
import org.springframework.context.annotation.Bean;
@@ -38,6 +42,7 @@
3842
*/
3943
@Configuration
4044
@ConditionalOnClass(WebClient.class)
45+
@AutoConfigureAfter(CodecsAutoConfiguration.class)
4146
public class WebClientAutoConfiguration {
4247

4348
private final ObjectProvider<List<WebClientCustomizer>> customizers;
@@ -58,4 +63,18 @@ public WebClientBuilder webClientBuilder() {
5863
}
5964
return builder;
6065
}
66+
67+
@Configuration
68+
@ConditionalOnBean(CodecCustomizer.class)
69+
protected static class WebClientCodecsConfiguration {
70+
71+
@Bean
72+
@ConditionalOnMissingBean
73+
public WebClientCodecCustomizer exchangeStrategiesCustomizer(
74+
List<CodecCustomizer> codecCustomizers) {
75+
return new WebClientCodecCustomizer(codecCustomizers);
76+
}
77+
78+
}
79+
6180
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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.autoconfigure.http.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+
42+
webClientBuilder
43+
.exchangeStrategies(ExchangeStrategies.builder()
44+
.codecs(codecs -> {
45+
this.codecCustomizers.forEach(codecCustomizer -> codecCustomizer.customize(codecs));
46+
}).build());
47+
}
48+
}

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
@@ -27,6 +27,7 @@
2727
import org.junit.rules.ExpectedException;
2828

2929
import org.springframework.beans.DirectFieldAccessor;
30+
import org.springframework.boot.autoconfigure.http.codec.CodecCustomizer;
3031
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
3132
import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
3233
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationTests.Config;
@@ -39,6 +40,7 @@
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

0 commit comments

Comments
 (0)