diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ReactorClientHttpRequestFactoryBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ReactorClientHttpRequestFactoryBuilder.java index e7490b10f1a..9b05e12c77d 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ReactorClientHttpRequestFactoryBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ReactorClientHttpRequestFactoryBuilder.java @@ -20,12 +20,14 @@ import java.time.Duration; import java.util.Collection; import java.util.List; import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.function.UnaryOperator; import reactor.netty.http.client.HttpClient; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.http.client.ReactorClientHttpRequestFactory; +import org.springframework.http.client.ReactorResourceFactory; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -63,6 +65,33 @@ public final class ReactorClientHttpRequestFactoryBuilder return new ReactorClientHttpRequestFactoryBuilder(mergedCustomizers(customizers), this.httpClientBuilder); } + /** + * Return a new {@link ReactorClientHttpRequestFactoryBuilder} that uses the given + * {@link ReactorResourceFactory} to create the underlying {@link HttpClient}. + * @param reactorResourceFactory the {@link ReactorResourceFactory} to use + * @return a new {@link ReactorClientHttpRequestFactoryBuilder} instance + * @since 3.5.0 + */ + public ReactorClientHttpRequestFactoryBuilder withReactorResourceFactory( + ReactorResourceFactory reactorResourceFactory) { + Assert.notNull(reactorResourceFactory, "'reactorResourceFactory' must not be null"); + return new ReactorClientHttpRequestFactoryBuilder(getCustomizers(), + this.httpClientBuilder.withReactorResourceFactory(reactorResourceFactory)); + } + + /** + * Return a new {@link ReactorClientHttpRequestFactoryBuilder} that uses the given + * factory to create the underlying {@link HttpClient}. + * @param factory the factory to use + * @return a new {@link ReactorClientHttpRequestFactoryBuilder} instance + * @since 3.5.0 + */ + public ReactorClientHttpRequestFactoryBuilder withHttpClientFactory(Supplier factory) { + Assert.notNull(factory, "'factory' must not be null"); + return new ReactorClientHttpRequestFactoryBuilder(getCustomizers(), + this.httpClientBuilder.withHttpClientFactory(factory)); + } + /** * Return a new {@link ReactorClientHttpRequestFactoryBuilder} that applies additional * customization to the underlying {@link HttpClient}. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ReactorHttpClientBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ReactorHttpClientBuilder.java index b3cf80c0107..bb8932b8a30 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ReactorHttpClientBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ReactorHttpClientBuilder.java @@ -17,6 +17,7 @@ package org.springframework.boot.http.client; import java.time.Duration; +import java.util.function.Supplier; import java.util.function.UnaryOperator; import javax.net.ssl.SSLException; @@ -30,6 +31,7 @@ import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.ssl.SslBundle; import org.springframework.boot.ssl.SslManagerBundle; import org.springframework.boot.ssl.SslOptions; +import org.springframework.http.client.ReactorResourceFactory; import org.springframework.util.Assert; import org.springframework.util.function.ThrowingConsumer; @@ -43,16 +45,42 @@ import org.springframework.util.function.ThrowingConsumer; */ public final class ReactorHttpClientBuilder { + private final Supplier factory; + private final UnaryOperator customizer; public ReactorHttpClientBuilder() { - this(UnaryOperator.identity()); + this(HttpClient::create, UnaryOperator.identity()); } - private ReactorHttpClientBuilder(UnaryOperator customizer) { + private ReactorHttpClientBuilder(Supplier httpClientFactory, UnaryOperator customizer) { + this.factory = httpClientFactory; this.customizer = customizer; } + /** + * Return a new {@link ReactorHttpClientBuilder} that uses the given + * {@link ReactorResourceFactory} to create the {@link HttpClient}. + * @param reactorResourceFactory the {@link ReactorResourceFactory} to use + * @return a new {@link ReactorHttpClientBuilder} instance + */ + public ReactorHttpClientBuilder withReactorResourceFactory(ReactorResourceFactory reactorResourceFactory) { + Assert.notNull(reactorResourceFactory, "'reactorResourceFactory' must not be null"); + return new ReactorHttpClientBuilder(() -> HttpClient.create(reactorResourceFactory.getConnectionProvider()), + (httpClient) -> this.customizer.apply(httpClient).runOn(reactorResourceFactory.getLoopResources())); + } + + /** + * Return a new {@link ReactorHttpClientBuilder} that uses the given factory to create + * the {@link HttpClient}. + * @param factory the factory to use + * @return a new {@link ReactorHttpClientBuilder} instance + */ + public ReactorHttpClientBuilder withHttpClientFactory(Supplier factory) { + Assert.notNull(factory, "'factory' must not be null"); + return new ReactorHttpClientBuilder(factory, this.customizer); + } + /** * Return a new {@link ReactorHttpClientBuilder} that applies additional customization * to the underlying {@link HttpClient}. @@ -72,7 +100,7 @@ public final class ReactorHttpClientBuilder { */ public HttpClient build(HttpClientSettings settings) { settings = (settings != null) ? settings : HttpClientSettings.DEFAULTS; - HttpClient httpClient = applyDefaults(HttpClient.create()); + HttpClient httpClient = applyDefaults(this.factory.get()); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); httpClient = map.from(settings::connectTimeout).to(httpClient, this::setConnectTimeout); httpClient = map.from(settings::readTimeout).to(httpClient, HttpClient::responseTimeout); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/reactive/ReactorClientHttpConnectorBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/reactive/ReactorClientHttpConnectorBuilder.java index ee66942694e..cfe6ade6afa 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/reactive/ReactorClientHttpConnectorBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/reactive/ReactorClientHttpConnectorBuilder.java @@ -19,11 +19,13 @@ package org.springframework.boot.http.client.reactive; import java.util.Collection; import java.util.List; import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.function.UnaryOperator; import reactor.netty.http.client.HttpClient; import org.springframework.boot.http.client.ReactorHttpClientBuilder; +import org.springframework.http.client.ReactorResourceFactory; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -60,6 +62,30 @@ public final class ReactorClientHttpConnectorBuilder return new ReactorClientHttpConnectorBuilder(mergedCustomizers(customizers), this.httpClientBuilder); } + /** + * Return a new {@link ReactorClientHttpConnectorBuilder} that uses the given + * {@link ReactorResourceFactory} to create the underlying {@link HttpClient}. + * @param reactorResourceFactory the {@link ReactorResourceFactory} to use + * @return a new {@link ReactorClientHttpConnectorBuilder} instance + */ + public ReactorClientHttpConnectorBuilder withReactorResourceFactory(ReactorResourceFactory reactorResourceFactory) { + Assert.notNull(reactorResourceFactory, "'reactorResourceFactory' must not be null"); + return new ReactorClientHttpConnectorBuilder(getCustomizers(), + this.httpClientBuilder.withReactorResourceFactory(reactorResourceFactory)); + } + + /** + * Return a new {@link ReactorClientHttpConnectorBuilder} that uses the given factory + * to create the underlying {@link HttpClient}. + * @param factory the factory to use + * @return a new {@link ReactorClientHttpConnectorBuilder} instance + */ + public ReactorClientHttpConnectorBuilder withHttpClientFactory(Supplier factory) { + Assert.notNull(factory, "'factory' must not be null"); + return new ReactorClientHttpConnectorBuilder(getCustomizers(), + this.httpClientBuilder.withHttpClientFactory(factory)); + } + /** * Return a new {@link ReactorClientHttpConnectorBuilder} that applies additional * customization to the underlying {@link HttpClient}. diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/http/client/ReactorClientHttpRequestFactoryBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/http/client/ReactorClientHttpRequestFactoryBuilderTests.java index 858014132b8..a204e1bd7e0 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/http/client/ReactorClientHttpRequestFactoryBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/http/client/ReactorClientHttpRequestFactoryBuilderTests.java @@ -19,6 +19,7 @@ package org.springframework.boot.http.client; import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; import java.util.function.UnaryOperator; import io.netty.channel.ChannelOption; @@ -26,9 +27,12 @@ import org.junit.jupiter.api.Test; import reactor.netty.http.client.HttpClient; import org.springframework.http.client.ReactorClientHttpRequestFactory; +import org.springframework.http.client.ReactorResourceFactory; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.spy; /** * Tests for {@link ReactorClientHttpRequestFactoryBuilder} and @@ -44,6 +48,25 @@ class ReactorClientHttpRequestFactoryBuilderTests super(ReactorClientHttpRequestFactory.class, ClientHttpRequestFactoryBuilder.reactor()); } + @Test + void withwithHttpClientFactory() { + boolean[] called = new boolean[1]; + Supplier httpClientFactory = () -> { + called[0] = true; + return HttpClient.create(); + }; + ClientHttpRequestFactoryBuilder.reactor().withHttpClientFactory(httpClientFactory).build(); + assertThat(called).containsExactly(true); + } + + @Test + void withReactorResourceFactory() { + ReactorResourceFactory resourceFactory = spy(new ReactorResourceFactory()); + ClientHttpRequestFactoryBuilder.reactor().withReactorResourceFactory(resourceFactory).build(); + then(resourceFactory).should().getConnectionProvider(); + then(resourceFactory).should().getLoopResources(); + } + @Test void withCustomizers() { List httpClients = new ArrayList<>(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/http/client/reactive/ReactorClientHttpConnectorBuilderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/http/client/reactive/ReactorClientHttpConnectorBuilderTests.java index 358ff462e5d..4f022586def 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/http/client/reactive/ReactorClientHttpConnectorBuilderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/http/client/reactive/ReactorClientHttpConnectorBuilderTests.java @@ -18,18 +18,21 @@ package org.springframework.boot.http.client.reactive; import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; import java.util.function.UnaryOperator; import io.netty.channel.ChannelOption; import org.junit.jupiter.api.Test; import reactor.netty.http.client.HttpClient; -import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; import org.springframework.boot.http.client.ReactorHttpClientBuilder; +import org.springframework.http.client.ReactorResourceFactory; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.spy; /** * Tests for {@link ReactorClientHttpConnectorBuilder} and @@ -44,6 +47,25 @@ class ReactorClientHttpConnectorBuilderTests super(ReactorClientHttpConnector.class, ClientHttpConnectorBuilder.reactor()); } + @Test + void withwithHttpClientFactory() { + boolean[] called = new boolean[1]; + Supplier httpClientFactory = () -> { + called[0] = true; + return HttpClient.create(); + }; + ClientHttpConnectorBuilder.reactor().withHttpClientFactory(httpClientFactory).build(); + assertThat(called).containsExactly(true); + } + + @Test + void withReactorResourceFactory() { + ReactorResourceFactory resourceFactory = spy(new ReactorResourceFactory()); + ClientHttpConnectorBuilder.reactor().withReactorResourceFactory(resourceFactory).build(); + then(resourceFactory).should().getConnectionProvider(); + then(resourceFactory).should().getLoopResources(); + } + @Test void withCustomizers() { List httpClients = new ArrayList<>();