diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfiguration.java
new file mode 100644
index 0000000000..d068a457d2
--- /dev/null
+++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfiguration.java
@@ -0,0 +1,65 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+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.util.CollectionUtils;
+import org.springframework.web.reactive.function.client.WebClient;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for {@link WebClient}.
+ *
This will produce a {@link WebClient.Builder} bean with the {@code prototype} scope,
+ * meaning each injection point will receive a newly cloned instance of the builder.
+ *
+ * @author Brian Clozel
+ * @since 2.0.0
+ */
+@Configuration
+@ConditionalOnClass(WebClient.class)
+public class WebClientAutoConfiguration {
+
+ private final WebClient.Builder webClientBuilder;
+
+
+ public WebClientAutoConfiguration(ObjectProvider> customizerProvider) {
+ this.webClientBuilder = WebClient.builder();
+ List customizers = customizerProvider.getIfAvailable();
+ if (!CollectionUtils.isEmpty(customizers)) {
+ customizers = new ArrayList<>(customizers);
+ AnnotationAwareOrderComparator.sort(customizers);
+ customizers.forEach(customizer -> customizer.customize(this.webClientBuilder));
+ }
+ }
+
+ @Bean
+ @Scope("prototype")
+ @ConditionalOnMissingBean
+ public WebClient.Builder webClientBuilder(List customizers) {
+ return this.webClientBuilder.clone();
+ }
+}
diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
index 81a43a9aa2..03559d5683 100644
--- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
+++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
@@ -114,6 +114,7 @@ org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
+org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java
index c2cd673387..35be60e0db 100644
--- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java
@@ -105,9 +105,9 @@ public class WebFluxAutoConfigurationTests {
.getBean(RequestMappingHandlerAdapter.class);
assertThat((List) ReflectionTestUtils
.getField(adapter.getArgumentResolverConfigurer(), "customResolvers"))
- .contains(
- this.context.getBean("firstResolver",
- HandlerMethodArgumentResolver.class),
+ .contains(
+ this.context.getBean("firstResolver",
+ HandlerMethodArgumentResolver.class),
this.context.getBean("secondResolver",
HandlerMethodArgumentResolver.class));
}
diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfigurationTests.java
new file mode 100644
index 0000000000..3c98e035cc
--- /dev/null
+++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/function/client/WebClientAutoConfigurationTests.java
@@ -0,0 +1,131 @@
+/*
+ * 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.net.URI;
+
+import org.junit.After;
+import org.junit.Test;
+import reactor.core.publisher.Mono;
+
+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.web.reactive.function.client.WebClient;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Tests for {@link WebClientAutoConfiguration}
+ *
+ * @author Brian Clozel
+ */
+public class WebClientAutoConfigurationTests {
+
+ private AnnotationConfigApplicationContext context;
+
+ @After
+ public void close() {
+ if (this.context != null) {
+ this.context.close();
+ }
+ }
+
+ @Test
+ public void webClientShouldApplyCustomizers() throws Exception {
+ load(WebClientCustomizerConfig.class);
+ WebClient.Builder builder = this.context.getBean(WebClient.Builder.class);
+ WebClientCustomizer customizer = this.context.getBean(WebClientCustomizer.class);
+ builder.build();
+ verify(customizer).customize(any(WebClient.Builder.class));
+ }
+
+ @Test
+ public void shouldGetPrototypeScopedBean() throws Exception {
+ load(WebClientCustomizerConfig.class);
+
+ ClientHttpConnector firstConnector = mock(ClientHttpConnector.class);
+ given(firstConnector.connect(any(), any(), any())).willReturn(Mono.empty());
+ WebClient.Builder firstBuilder = this.context.getBean(WebClient.Builder.class);
+ firstBuilder.clientConnector(firstConnector).baseUrl("http://first.example.org");
+
+ ClientHttpConnector secondConnector = mock(ClientHttpConnector.class);
+ given(secondConnector.connect(any(), any(), any())).willReturn(Mono.empty());
+ WebClient.Builder secondBuilder = this.context.getBean(WebClient.Builder.class);
+ secondBuilder.clientConnector(secondConnector).baseUrl("http://second.example.org");
+
+ assertThat(firstBuilder).isNotEqualTo(secondBuilder);
+
+ firstBuilder.build().get().uri("/foo").exchange().block();
+ secondBuilder.build().get().uri("/foo").exchange().block();
+
+ verify(firstConnector).connect(eq(HttpMethod.GET), eq(URI.create("http://first.example.org/foo")), any());
+ verify(secondConnector).connect(eq(HttpMethod.GET), eq(URI.create("http://second.example.org/foo")), any());
+ WebClientCustomizer customizer = this.context.getBean(WebClientCustomizer.class);
+ verify(customizer, times(1)).customize(any(WebClient.Builder.class));
+ }
+
+ @Test
+ public void shouldNotCreateClientBuilderIfAlreadyPresent() throws Exception {
+ load(WebClientCustomizerConfig.class, CustomWebClientBuilderConfig.class);
+ WebClient.Builder builder = this.context.getBean(WebClient.Builder.class);
+ assertThat(builder).isInstanceOf(MyWebClientBuilder.class);
+ }
+
+
+ private void load(Class>... config) {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+ ctx.register(config);
+ ctx.register(WebClientAutoConfiguration.class);
+ ctx.refresh();
+ this.context = ctx;
+ }
+
+ @Configuration
+ static class WebClientCustomizerConfig {
+
+ @Bean
+ public WebClientCustomizer webClientCustomizer() {
+ return mock(WebClientCustomizer.class);
+ }
+
+ }
+
+ @Configuration
+ static class CustomWebClientBuilderConfig {
+
+ @Bean
+ public MyWebClientBuilder myWebClientBuilder() {
+ return mock(MyWebClientBuilder.class);
+ }
+
+ }
+
+ interface MyWebClientBuilder extends WebClient.Builder {
+
+ }
+
+}
diff --git a/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories
index 08255a6c2c..9edc8e10b6 100644
--- a/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories
+++ b/spring-boot-test-autoconfigure/src/main/resources/META-INF/spring.factories
@@ -110,7 +110,8 @@ org.springframework.boot.test.autoconfigure.web.client.WebClientRestTemplateAuto
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
-org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration
+org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
+org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration
# AutoConfigureWebMvc auto-configuration imports
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc=\
diff --git a/spring-boot/src/main/java/org/springframework/boot/web/reactive/function/client/WebClientCustomizer.java b/spring-boot/src/main/java/org/springframework/boot/web/reactive/function/client/WebClientCustomizer.java
new file mode 100644
index 0000000000..3c4e90cd5c
--- /dev/null
+++ b/spring-boot/src/main/java/org/springframework/boot/web/reactive/function/client/WebClientCustomizer.java
@@ -0,0 +1,35 @@
+/*
+ * 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.reactive.function.client;
+
+import org.springframework.web.reactive.function.client.WebClient;
+
+/**
+ * Callback interface that can be used to customize a {@link WebClient.Builder}.
+ *
+ * @author Brian Clozel
+ * @since 2.0.0
+ */
+@FunctionalInterface
+public interface WebClientCustomizer {
+
+ /**
+ * Callback to customize a {@link WebClient.Builder} instance.
+ * @param webClientBuilder the client builder to customize
+ */
+ void customize(WebClient.Builder webClientBuilder);
+}