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:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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,\
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user