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
This commit is contained in:
Brian Clozel
2018-08-22 19:14:34 +02:00
parent f8ce714c88
commit e2a7594246
7 changed files with 204 additions and 11 deletions

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2012-2018 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 org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
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.Import;
import org.springframework.core.annotation.Order;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
/**
* {@link EnableAutoConfiguration Auto-configuration} for {@link ClientHttpConnector}.
* <p>
* It can produce a {@link org.springframework.http.client.reactive.ClientHttpConnector}
* bean and possibly a companion {@code ResourceFactory} bean, depending on the chosen
* HTTP client library.
*
* @author Brian Clozel
* @since 2.1.0
*/
@Configuration
@ConditionalOnClass(WebClient.class)
@Import({ ClientHttpConnectorConfiguration.ReactorNetty.class })
public class ClientHttpConnectorAutoConfiguration {
@Bean
@Order(0)
@ConditionalOnBean(ClientHttpConnector.class)
public WebClientCustomizer clientConnectorCustomizer(
ClientHttpConnector clientHttpConnector) {
return (builder) -> builder.clientConnector(clientHttpConnector);
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2012-2018 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.function.Function;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.client.reactive.ReactorResourceFactory;
/**
* Configuration classes for WebClient client connectors.
* <p>
* Those should be {@code @Import} in a regular auto-configuration class to guarantee
* their order of execution.
*
* @author Brian Clozel
*/
@Configuration
class ClientHttpConnectorConfiguration {
@Configuration
@ConditionalOnClass(reactor.netty.http.client.HttpClient.class)
@ConditionalOnMissingBean(ClientHttpConnector.class)
public static class ReactorNetty {
@Bean
@ConditionalOnMissingBean
public ReactorResourceFactory reactorResourceFactory() {
ReactorResourceFactory factory = new ReactorResourceFactory();
factory.setGlobalResources(false);
return factory;
}
@Bean
public ReactorClientHttpConnector reactorClientHttpConnector(
ReactorResourceFactory reactorResourceFactory) {
return new ReactorClientHttpConnector(reactorResourceFactory,
Function.identity());
}
}
}

View File

@@ -49,7 +49,8 @@ import org.springframework.web.reactive.function.client.WebClient;
*/
@Configuration
@ConditionalOnClass(WebClient.class)
@AutoConfigureAfter(CodecsAutoConfiguration.class)
@AutoConfigureAfter({ CodecsAutoConfiguration.class,
ClientHttpConnectorAutoConfiguration.class })
public class WebClientAutoConfiguration {
private final WebClient.Builder webClientBuilder;

View File

@@ -121,6 +121,7 @@ org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2012-2018 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 org.junit.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.client.reactive.ReactorResourceFactory;
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.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link ClientHttpConnectorAutoConfiguration}
*
* @author Brian Clozel
*/
public class ClientHttpConnectorAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(ClientHttpConnectorAutoConfiguration.class));
@Test
public void shouldCreateHttpClientBeans() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(ReactorResourceFactory.class);
assertThat(context).hasSingleBean(ReactorClientHttpConnector.class);
WebClientCustomizer clientCustomizer = context
.getBean(WebClientCustomizer.class);
WebClient.Builder builder = mock(WebClient.Builder.class);
clientCustomizer.customize(builder);
verify(builder, times(1))
.clientConnector(any(ReactorClientHttpConnector.class));
});
}
}

View File

@@ -49,7 +49,9 @@ import static org.mockito.Mockito.verify;
public class WebClientAutoConfigurationTests {
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(WebClientAutoConfiguration.class));
.withConfiguration(
AutoConfigurations.of(ClientHttpConnectorAutoConfiguration.class,
WebClientAutoConfiguration.class));
@Test
public void shouldCreateBuilder() {
@@ -58,7 +60,6 @@ public class WebClientAutoConfigurationTests {
WebClient webClient = builder.build();
assertThat(webClient).isNotNull();
});
}
@Test
@@ -82,7 +83,7 @@ public class WebClientAutoConfigurationTests {
.run((context) -> {
WebClient.Builder builder = context.getBean(WebClient.Builder.class);
WebClientCustomizer customizer = context
.getBean(WebClientCustomizer.class);
.getBean("webClientCustomizer", WebClientCustomizer.class);
builder.build();
verify(customizer).customize(any(WebClient.Builder.class));
});
@@ -115,7 +116,7 @@ public class WebClientAutoConfigurationTests {
verify(secondConnector).connect(eq(HttpMethod.GET),
eq(URI.create("http://second.example.org/foo")), any());
WebClientCustomizer customizer = context
.getBean(WebClientCustomizer.class);
.getBean("webClientCustomizer", WebClientCustomizer.class);
verify(customizer, times(1)).customize(any(WebClient.Builder.class));
});
}

View File

@@ -5785,13 +5785,14 @@ Finally, the most extreme (and rarely used) option is to create your own
== Calling REST Services with `WebClient`
If you have Spring WebFlux on your classpath, you can also choose to use `WebClient` to
call remote REST services. Compared to `RestTemplate`, this client has a more functional
feel and is fully reactive. You can create your own client instance with the builder,
`WebClient.create()`. See the {spring-reference}web.html#web-reactive-client[relevant
section on WebClient].
feel and is fully reactive. You can leanr more about the `WebClient` in the dedicated
{spring-reference}web-reactive.html#webflux-client[section in the Spring Framework docs].
Spring Boot creates and pre-configures such a builder for you. For example, client HTTP
codecs are configured in the same fashion as the server ones (see
<<boot-features-webflux-httpcodecs,WebFlux HTTP codecs auto-configuration>>).
Spring Boot creates and pre-configures a `WebClient.Builder` for you; it is strongly
advised to inject it in your components and use it to create `WebClient` instances.
Spring Boot is configuring that builder to share HTTP resources, reflect codecs
setup in the same fashion as the server ones (see
<<boot-features-webflux-httpcodecs,WebFlux HTTP codecs auto-configuration>>), and more.
The following code shows a typical example:
@@ -5815,6 +5816,21 @@ The following code shows a typical example:
----
[[boot-features-webclient-runtime]]
=== WebClient Runtime
Spring Boot will auto-detect which `ClientHttpConnector` to drive `WebClient`, depending
on the libraries available on the application classpath.
Developers can override this choice by defining their own `ClientHttpConnector` bean;
in this case, and depending on your HTTP client library of choice, you should also
define a resource factory bean that manages the HTTP resources for that client.
For example, a `ReactorResourceFactory` bean for the Reactor Netty client.
You can learn more about the
{spring-reference}web-reactive.html#webflux-client-builder[`WebClient` configuration
options in the Spring Framework reference documentation].
[[boot-features-webclient-customization]]
=== WebClient Customization