Skip to content

Commit e2a7594

Browse files
committed
Configure Reactor HTTP client resources
This commit adds support for the new `ReactorResourceFactory` and ensures that such a bean is created and destroyed with the application context. This will create a `ClientHttpConnector` bean, to be configured on the `WebClient.Builder` instance - or let developers create their own `ClientHttpConnector` bean to override that opinion. By default, the `ReactorResourceFactory` is configured to participate with the global resources, for better efficiency. Closes gh-14058
1 parent f8ce714 commit e2a7594

File tree

7 files changed

+204
-11
lines changed

7 files changed

+204
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2012-2018 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 org.springframework.boot.autoconfigure.EnableAutoConfiguration;
20+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
22+
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.context.annotation.Import;
26+
import org.springframework.core.annotation.Order;
27+
import org.springframework.http.client.reactive.ClientHttpConnector;
28+
import org.springframework.web.reactive.function.client.WebClient;
29+
30+
/**
31+
* {@link EnableAutoConfiguration Auto-configuration} for {@link ClientHttpConnector}.
32+
* <p>
33+
* It can produce a {@link org.springframework.http.client.reactive.ClientHttpConnector}
34+
* bean and possibly a companion {@code ResourceFactory} bean, depending on the chosen
35+
* HTTP client library.
36+
*
37+
* @author Brian Clozel
38+
* @since 2.1.0
39+
*/
40+
@Configuration
41+
@ConditionalOnClass(WebClient.class)
42+
@Import({ ClientHttpConnectorConfiguration.ReactorNetty.class })
43+
public class ClientHttpConnectorAutoConfiguration {
44+
45+
@Bean
46+
@Order(0)
47+
@ConditionalOnBean(ClientHttpConnector.class)
48+
public WebClientCustomizer clientConnectorCustomizer(
49+
ClientHttpConnector clientHttpConnector) {
50+
return (builder) -> builder.clientConnector(clientHttpConnector);
51+
}
52+
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2012-2018 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.function.Function;
20+
21+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.http.client.reactive.ClientHttpConnector;
26+
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
27+
import org.springframework.http.client.reactive.ReactorResourceFactory;
28+
29+
/**
30+
* Configuration classes for WebClient client connectors.
31+
* <p>
32+
* Those should be {@code @Import} in a regular auto-configuration class to guarantee
33+
* their order of execution.
34+
*
35+
* @author Brian Clozel
36+
*/
37+
@Configuration
38+
class ClientHttpConnectorConfiguration {
39+
40+
@Configuration
41+
@ConditionalOnClass(reactor.netty.http.client.HttpClient.class)
42+
@ConditionalOnMissingBean(ClientHttpConnector.class)
43+
public static class ReactorNetty {
44+
45+
@Bean
46+
@ConditionalOnMissingBean
47+
public ReactorResourceFactory reactorResourceFactory() {
48+
ReactorResourceFactory factory = new ReactorResourceFactory();
49+
factory.setGlobalResources(false);
50+
return factory;
51+
}
52+
53+
@Bean
54+
public ReactorClientHttpConnector reactorClientHttpConnector(
55+
ReactorResourceFactory reactorResourceFactory) {
56+
return new ReactorClientHttpConnector(reactorResourceFactory,
57+
Function.identity());
58+
}
59+
60+
}
61+
62+
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@
4949
*/
5050
@Configuration
5151
@ConditionalOnClass(WebClient.class)
52-
@AutoConfigureAfter(CodecsAutoConfiguration.class)
52+
@AutoConfigureAfter({ CodecsAutoConfiguration.class,
53+
ClientHttpConnectorAutoConfiguration.class })
5354
public class WebClientAutoConfiguration {
5455

5556
private final WebClient.Builder webClientBuilder;

Diff for: spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration
121121
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
122122
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
123123
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
124+
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
124125
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
125126
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
126127
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2012-2018 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 org.junit.Test;
20+
21+
import org.springframework.boot.autoconfigure.AutoConfigurations;
22+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
23+
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
24+
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
25+
import org.springframework.http.client.reactive.ReactorResourceFactory;
26+
import org.springframework.web.reactive.function.client.WebClient;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
import static org.mockito.ArgumentMatchers.any;
30+
import static org.mockito.Mockito.mock;
31+
import static org.mockito.Mockito.times;
32+
import static org.mockito.Mockito.verify;
33+
34+
/**
35+
* Tests for {@link ClientHttpConnectorAutoConfiguration}
36+
*
37+
* @author Brian Clozel
38+
*/
39+
public class ClientHttpConnectorAutoConfigurationTests {
40+
41+
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
42+
.withConfiguration(
43+
AutoConfigurations.of(ClientHttpConnectorAutoConfiguration.class));
44+
45+
@Test
46+
public void shouldCreateHttpClientBeans() {
47+
this.contextRunner.run((context) -> {
48+
assertThat(context).hasSingleBean(ReactorResourceFactory.class);
49+
assertThat(context).hasSingleBean(ReactorClientHttpConnector.class);
50+
WebClientCustomizer clientCustomizer = context
51+
.getBean(WebClientCustomizer.class);
52+
WebClient.Builder builder = mock(WebClient.Builder.class);
53+
clientCustomizer.customize(builder);
54+
verify(builder, times(1))
55+
.clientConnector(any(ReactorClientHttpConnector.class));
56+
});
57+
}
58+
59+
}

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

+5-4
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@
4949
public class WebClientAutoConfigurationTests {
5050

5151
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
52-
.withConfiguration(AutoConfigurations.of(WebClientAutoConfiguration.class));
52+
.withConfiguration(
53+
AutoConfigurations.of(ClientHttpConnectorAutoConfiguration.class,
54+
WebClientAutoConfiguration.class));
5355

5456
@Test
5557
public void shouldCreateBuilder() {
@@ -58,7 +60,6 @@ public void shouldCreateBuilder() {
5860
WebClient webClient = builder.build();
5961
assertThat(webClient).isNotNull();
6062
});
61-
6263
}
6364

6465
@Test
@@ -82,7 +83,7 @@ public void webClientShouldApplyCustomizers() {
8283
.run((context) -> {
8384
WebClient.Builder builder = context.getBean(WebClient.Builder.class);
8485
WebClientCustomizer customizer = context
85-
.getBean(WebClientCustomizer.class);
86+
.getBean("webClientCustomizer", WebClientCustomizer.class);
8687
builder.build();
8788
verify(customizer).customize(any(WebClient.Builder.class));
8889
});
@@ -115,7 +116,7 @@ public void shouldGetPrototypeScopedBean() {
115116
verify(secondConnector).connect(eq(HttpMethod.GET),
116117
eq(URI.create("http://second.example.org/foo")), any());
117118
WebClientCustomizer customizer = context
118-
.getBean(WebClientCustomizer.class);
119+
.getBean("webClientCustomizer", WebClientCustomizer.class);
119120
verify(customizer, times(1)).customize(any(WebClient.Builder.class));
120121
});
121122
}

Diff for: spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc

+22-6
Original file line numberDiff line numberDiff line change
@@ -5785,13 +5785,14 @@ Finally, the most extreme (and rarely used) option is to create your own
57855785
== Calling REST Services with `WebClient`
57865786
If you have Spring WebFlux on your classpath, you can also choose to use `WebClient` to
57875787
call remote REST services. Compared to `RestTemplate`, this client has a more functional
5788-
feel and is fully reactive. You can create your own client instance with the builder,
5789-
`WebClient.create()`. See the {spring-reference}web.html#web-reactive-client[relevant
5790-
section on WebClient].
5788+
feel and is fully reactive. You can leanr more about the `WebClient` in the dedicated
5789+
{spring-reference}web-reactive.html#webflux-client[section in the Spring Framework docs].
57915790

5792-
Spring Boot creates and pre-configures such a builder for you. For example, client HTTP
5793-
codecs are configured in the same fashion as the server ones (see
5794-
<<boot-features-webflux-httpcodecs,WebFlux HTTP codecs auto-configuration>>).
5791+
Spring Boot creates and pre-configures a `WebClient.Builder` for you; it is strongly
5792+
advised to inject it in your components and use it to create `WebClient` instances.
5793+
Spring Boot is configuring that builder to share HTTP resources, reflect codecs
5794+
setup in the same fashion as the server ones (see
5795+
<<boot-features-webflux-httpcodecs,WebFlux HTTP codecs auto-configuration>>), and more.
57955796

57965797
The following code shows a typical example:
57975798

@@ -5815,6 +5816,21 @@ The following code shows a typical example:
58155816
----
58165817

58175818

5819+
[[boot-features-webclient-runtime]]
5820+
=== WebClient Runtime
5821+
5822+
Spring Boot will auto-detect which `ClientHttpConnector` to drive `WebClient`, depending
5823+
on the libraries available on the application classpath.
5824+
5825+
Developers can override this choice by defining their own `ClientHttpConnector` bean;
5826+
in this case, and depending on your HTTP client library of choice, you should also
5827+
define a resource factory bean that manages the HTTP resources for that client.
5828+
For example, a `ReactorResourceFactory` bean for the Reactor Netty client.
5829+
5830+
You can learn more about the
5831+
{spring-reference}web-reactive.html#webflux-client-builder[`WebClient` configuration
5832+
options in the Spring Framework reference documentation].
5833+
58185834

58195835
[[boot-features-webclient-customization]]
58205836
=== WebClient Customization

0 commit comments

Comments
 (0)