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
pull/9558/head
Brian Clozel 8 years ago
parent 4ce726b1a0
commit 64777204d8

@ -0,0 +1,60 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.http.codec;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.codec.CodecConfigurer;
import org.springframework.http.codec.json.Jackson2JsonDecoder;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.util.MimeType;
/**
* {@link EnableAutoConfiguration Auto-configuration}
* for {@link org.springframework.core.codec.Encoder}s and {@link org.springframework.core.codec.Decoder}s.
* @author Brian Clozel
*/
@Configuration
@ConditionalOnClass(CodecConfigurer.class)
@AutoConfigureAfter(JacksonAutoConfiguration.class)
public class CodecsAutoConfiguration {
@Configuration
@ConditionalOnClass(ObjectMapper.class)
static class JacksonCodecConfiguration {
@Bean
@ConditionalOnBean(ObjectMapper.class)
public CodecCustomizer jacksonCodecCustomizer(ObjectMapper objectMapper) {
return configurer -> {
CodecConfigurer.DefaultCodecs defaults = configurer.defaultCodecs();
defaults.jackson2Decoder(new Jackson2JsonDecoder(objectMapper, new MimeType[0]));
defaults.jackson2Encoder(new Jackson2JsonEncoder(objectMapper, new MimeType[0]));
};
}
}
}

@ -0,0 +1,20 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Auto-configuration for HTTP codecs.
*/
package org.springframework.boot.autoconfigure.http.codec;

@ -32,10 +32,12 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@ -46,6 +48,7 @@ import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.format.Formatter;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.CacheControl;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.util.ClassUtils;
import org.springframework.validation.Validator;
import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration;
@ -79,7 +82,7 @@ import org.springframework.web.reactive.result.view.ViewResolver;
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@ConditionalOnMissingBean({ WebFluxConfigurationSupport.class })
@AutoConfigureAfter(ReactiveWebServerAutoConfiguration.class)
@AutoConfigureAfter({ ReactiveWebServerAutoConfiguration.class, CodecsAutoConfiguration.class })
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
public class WebFluxAutoConfiguration {
@ -98,6 +101,8 @@ public class WebFluxAutoConfiguration {
private final List<HandlerMethodArgumentResolver> argumentResolvers;
private final List<CodecCustomizer> codecCustomizers;
private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
private final List<ViewResolver> viewResolvers;
@ -105,12 +110,14 @@ public class WebFluxAutoConfiguration {
public WebFluxConfig(ResourceProperties resourceProperties,
WebFluxProperties webFluxProperties, ListableBeanFactory beanFactory,
ObjectProvider<List<HandlerMethodArgumentResolver>> resolvers,
ObjectProvider<List<CodecCustomizer>> codecCustomizers,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizer,
ObjectProvider<List<ViewResolver>> viewResolvers) {
this.resourceProperties = resourceProperties;
this.webFluxProperties = webFluxProperties;
this.beanFactory = beanFactory;
this.argumentResolvers = resolvers.getIfAvailable();
this.codecCustomizers = codecCustomizers.getIfAvailable();
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizer
.getIfAvailable();
this.viewResolvers = viewResolvers.getIfAvailable();
@ -123,6 +130,13 @@ public class WebFluxAutoConfiguration {
}
}
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
if (this.codecCustomizers != null) {
this.codecCustomizers.forEach(codecCustomizer -> codecCustomizer.customize(configurer));
}
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {

@ -20,14 +20,19 @@ import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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.http.codec.CodecsAutoConfiguration;
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.Order;
import org.springframework.util.CollectionUtils;
import org.springframework.web.reactive.function.client.WebClient;
@ -41,6 +46,7 @@ import org.springframework.web.reactive.function.client.WebClient;
*/
@Configuration
@ConditionalOnClass(WebClient.class)
@AutoConfigureAfter(CodecsAutoConfiguration.class)
public class WebClientAutoConfiguration {
private final WebClient.Builder webClientBuilder;
@ -62,4 +68,19 @@ public class WebClientAutoConfiguration {
public WebClient.Builder webClientBuilder(List<WebClientCustomizer> customizers) {
return this.webClientBuilder.clone();
}
@Configuration
@ConditionalOnBean(CodecCustomizer.class)
protected static class WebClientCodecsConfiguration {
@Bean
@ConditionalOnMissingBean
@Order(0)
public WebClientCodecCustomizer exchangeStrategiesCustomizer(
List<CodecCustomizer> codecCustomizers) {
return new WebClientCodecCustomizer(codecCustomizers);
}
}
}

@ -0,0 +1,47 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.reactive.function.client;
import java.util.List;
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
/**
* {@link WebClientCustomizer} that configures codecs for the HTTP client.
* @author Brian Clozel
* @since 2.0.0
*/
public class WebClientCodecCustomizer implements WebClientCustomizer {
private final List<CodecCustomizer> codecCustomizers;
public WebClientCodecCustomizer(List<CodecCustomizer> codecCustomizers) {
this.codecCustomizers = codecCustomizers;
}
@Override
public void customize(WebClient.Builder webClientBuilder) {
webClientBuilder
.exchangeStrategies(ExchangeStrategies.builder()
.codecs(codecs -> {
this.codecCustomizers.forEach(codecCustomizer -> codecCustomizer.customize(codecs));
}).build());
}
}

@ -62,6 +62,7 @@ org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\

@ -32,6 +32,7 @@ import org.springframework.boot.autoconfigure.validation.ValidatorAdapter;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfigurationTests.Config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.boot.web.reactive.context.GenericReactiveWebApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -39,6 +40,7 @@ import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ObjectUtils;
@ -60,7 +62,9 @@ import org.springframework.web.reactive.result.view.ViewResolutionResultHandler;
import org.springframework.web.reactive.result.view.ViewResolver;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link WebFluxAutoConfiguration}.
@ -112,6 +116,15 @@ public class WebFluxAutoConfigurationTests {
HandlerMethodArgumentResolver.class));
}
@Test
public void shouldCustomizeCodecs() throws Exception {
load(CustomCodecCustomizers.class);
CodecCustomizer codecCustomizer =
this.context.getBean("firstCodecCustomizer", CodecCustomizer.class);
assertThat(codecCustomizer).isNotNull();
verify(codecCustomizer).customize(any(ServerCodecConfigurer.class));
}
@Test
public void shouldRegisterResourceHandlerMapping() throws Exception {
load();
@ -316,6 +329,15 @@ public class WebFluxAutoConfigurationTests {
}
@Configuration
protected static class CustomCodecCustomizers {
@Bean
public CodecCustomizer firstCodecCustomizer() {
return mock(CodecCustomizer.class);
}
}
@Configuration
protected static class ViewResolvers {

@ -22,12 +22,14 @@ import org.junit.After;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.boot.web.codec.CodecCustomizer;
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.codec.CodecConfigurer;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
@ -54,6 +56,17 @@ public class WebClientAutoConfigurationTests {
}
}
@Test
public void shouldCustomizeClientCodecs() throws Exception {
load(CodecConfiguration.class);
WebClient.Builder builder = this.context.getBean(WebClient.Builder.class);
CodecCustomizer codecCustomizer = this.context.getBean(CodecCustomizer.class);
WebClientCodecCustomizer clientCustomizer = this.context.getBean(WebClientCodecCustomizer.class);
builder.build();
assertThat(clientCustomizer).isNotNull();
verify(codecCustomizer).customize(any(CodecConfigurer.class));
}
@Test
public void webClientShouldApplyCustomizers() throws Exception {
load(WebClientCustomizerConfig.class);
@ -104,6 +117,15 @@ public class WebClientAutoConfigurationTests {
this.context = ctx;
}
@Configuration
static class CodecConfiguration {
@Bean
public CodecCustomizer myCodecCustomizer() {
return mock(CodecCustomizer.class);
}
}
@Configuration
static class WebClientCustomizerConfig {

@ -109,6 +109,7 @@ org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient=\
org.springframework.boot.test.autoconfigure.web.client.WebClientRestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration

@ -0,0 +1,36 @@
/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.web.codec;
import org.springframework.http.codec.CodecConfigurer;
/**
* Callback interface that can be used to customize codecs configuration
* for an HTTP client and/or server with a {@link CodecConfigurer}.
* @author Brian Clozel
* @since 2.0
*/
@FunctionalInterface
public interface CodecCustomizer {
/**
* Callback to customize a {@link CodecConfigurer} instance.
* @param configurer codec configurer to customize
*/
void customize(CodecConfigurer configurer);
}
Loading…
Cancel
Save