Add `with` methods to apply pre-packaged customizations

Add `with` methods to `HttpRequestFactoryBuilder` and
`ClientHttpConnectorBuilder` that operate in a similar way to the
`WebClient.Builder.apply(...)` method.

Closes gh-47205
This commit is contained in:
Phillip Webb 2025-09-15 16:45:10 -07:00
parent e9c08b8dcc
commit 336e7de9fc
17 changed files with 181 additions and 0 deletions

View File

@ -20,6 +20,7 @@ import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.config.RequestConfig;
@ -143,6 +144,18 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder
this.httpClientBuilder.withDefaultRequestConfigCustomizer(defaultRequestConfigCustomizer));
}
/**
* Return a new {@link HttpComponentsClientHttpRequestFactoryBuilder} that applies the
* given customizer. This can be useful for applying pre-packaged customizations.
* @param customizer the customizer to apply
* @return a new {@link HttpComponentsClientHttpRequestFactoryBuilder}
* @since 4.0.0
*/
public HttpComponentsClientHttpRequestFactoryBuilder with(
UnaryOperator<HttpComponentsClientHttpRequestFactoryBuilder> customizer) {
return customizer.apply(this);
}
@Override
protected HttpComponentsClientHttpRequestFactory createClientHttpRequestFactory(
ClientHttpRequestFactorySettings settings) {

View File

@ -21,6 +21,7 @@ import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import org.jspecify.annotations.Nullable;
@ -88,6 +89,17 @@ public final class JdkClientHttpRequestFactoryBuilder
this.httpClientBuilder.withCustomizer(httpClientCustomizer));
}
/**
* Return a new {@link JdkClientHttpRequestFactoryBuilder} that applies the given
* customizer. This can be useful for applying pre-packaged customizations.
* @param customizer the customizer to apply
* @return a new {@link JdkClientHttpRequestFactoryBuilder}
* @since 4.0.0
*/
public JdkClientHttpRequestFactoryBuilder with(UnaryOperator<JdkClientHttpRequestFactoryBuilder> customizer) {
return customizer.apply(this);
}
@Override
protected JdkClientHttpRequestFactory createClientHttpRequestFactory(ClientHttpRequestFactorySettings settings) {
HttpClient httpClient = this.httpClientBuilder.build(asHttpClientSettings(settings.withReadTimeout(null)));

View File

@ -20,6 +20,7 @@ import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpClientTransport;
@ -103,6 +104,17 @@ public final class JettyClientHttpRequestFactoryBuilder
this.httpClientBuilder.withClientConnectorCustomizerCustomizer(clientConnectorCustomizerCustomizer));
}
/**
* Return a new {@link JettyClientHttpRequestFactoryBuilder} that applies the given
* customizer. This can be useful for applying pre-packaged customizations.
* @param customizer the customizer to apply
* @return a new {@link JettyClientHttpRequestFactoryBuilder}
* @since 4.0.0
*/
public JettyClientHttpRequestFactoryBuilder with(UnaryOperator<JettyClientHttpRequestFactoryBuilder> customizer) {
return customizer.apply(this);
}
@Override
protected JettyClientHttpRequestFactory createClientHttpRequestFactory(ClientHttpRequestFactorySettings settings) {
HttpClient httpClient = this.httpClientBuilder.build(asHttpClientSettings(settings.withTimeouts(null, null)));

View File

@ -107,6 +107,18 @@ public final class ReactorClientHttpRequestFactoryBuilder
this.httpClientBuilder.withHttpClientCustomizer(httpClientCustomizer));
}
/**
* Return a new {@link ReactorClientHttpRequestFactoryBuilder} that applies the given
* customizer. This can be useful for applying pre-packaged customizations.
* @param customizer the customizer to apply
* @return a new {@link ReactorClientHttpRequestFactoryBuilder}
* @since 4.0.0
*/
public ReactorClientHttpRequestFactoryBuilder with(
UnaryOperator<ReactorClientHttpRequestFactoryBuilder> customizer) {
return customizer.apply(this);
}
@Override
protected ReactorClientHttpRequestFactory createClientHttpRequestFactory(
ClientHttpRequestFactorySettings settings) {

View File

@ -22,6 +22,7 @@ import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSocketFactory;
@ -64,6 +65,17 @@ public final class SimpleClientHttpRequestFactoryBuilder
return new SimpleClientHttpRequestFactoryBuilder(mergedCustomizers(customizers));
}
/**
* Return a new {@link SimpleClientHttpRequestFactoryBuilder} that applies the given
* customizer. This can be useful for applying pre-packaged customizations.
* @param customizer the customizer to apply
* @return a new {@link SimpleClientHttpRequestFactoryBuilder}
* @since 4.0.0
*/
public SimpleClientHttpRequestFactoryBuilder with(UnaryOperator<SimpleClientHttpRequestFactoryBuilder> customizer) {
return customizer.apply(this);
}
@Override
protected SimpleClientHttpRequestFactory createClientHttpRequestFactory(ClientHttpRequestFactorySettings settings) {
SslBundle sslBundle = settings.sslBundle();

View File

@ -19,6 +19,7 @@ package org.springframework.boot.http.client.reactive;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
@ -126,6 +127,18 @@ public final class HttpComponentsClientHttpConnectorBuilder
this.httpClientBuilder.withDefaultRequestConfigCustomizer(defaultRequestConfigCustomizer));
}
/**
* Return a new {@link HttpComponentsClientHttpConnectorBuilder} that applies the
* given customizer. This can be useful for applying pre-packaged customizations.
* @param customizer the customizer to apply
* @return a new {@link HttpComponentsClientHttpConnectorBuilder}
* @since 4.0.0
*/
public HttpComponentsClientHttpConnectorBuilder with(
UnaryOperator<HttpComponentsClientHttpConnectorBuilder> customizer) {
return customizer.apply(this);
}
@Override
protected HttpComponentsClientHttpConnector createClientHttpConnector(ClientHttpConnectorSettings settings) {
CloseableHttpAsyncClient client = this.httpClientBuilder.build(asHttpClientSettings(settings));

View File

@ -21,6 +21,7 @@ import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import org.jspecify.annotations.Nullable;
@ -83,6 +84,17 @@ public final class JdkClientHttpConnectorBuilder extends AbstractClientHttpConne
this.httpClientBuilder.withCustomizer(httpClientCustomizer));
}
/**
* Return a new {@link JdkClientHttpConnectorBuilder} that applies the given
* customizer. This can be useful for applying pre-packaged customizations.
* @param customizer the customizer to apply
* @return a new {@link JdkClientHttpConnectorBuilder}
* @since 4.0.0
*/
public JdkClientHttpConnectorBuilder with(UnaryOperator<JdkClientHttpConnectorBuilder> customizer) {
return customizer.apply(this);
}
@Override
protected JdkClientHttpConnector createClientHttpConnector(ClientHttpConnectorSettings settings) {
HttpClient httpClient = this.httpClientBuilder.build(asHttpClientSettings(settings.withReadTimeout(null)));

View File

@ -19,6 +19,7 @@ package org.springframework.boot.http.client.reactive;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpClientTransport;
@ -99,6 +100,17 @@ public final class JettyClientHttpConnectorBuilder
this.httpClientBuilder.withClientConnectorCustomizerCustomizer(clientConnectorCustomizerCustomizer));
}
/**
* Return a new {@link JettyClientHttpConnectorBuilder} that applies the given
* customizer. This can be useful for applying pre-packaged customizations.
* @param customizer the customizer to apply
* @return a new {@link JettyClientHttpConnectorBuilder}
* @since 4.0.0
*/
public JettyClientHttpConnectorBuilder with(UnaryOperator<JettyClientHttpConnectorBuilder> customizer) {
return customizer.apply(this);
}
@Override
protected JettyClientHttpConnector createClientHttpConnector(ClientHttpConnectorSettings settings) {
HttpClient httpClient = this.httpClientBuilder.build(asHttpClientSettings(settings));

View File

@ -99,6 +99,17 @@ public final class ReactorClientHttpConnectorBuilder
this.httpClientBuilder.withHttpClientCustomizer(httpClientCustomizer));
}
/**
* Return a new {@link ReactorClientHttpConnectorBuilder} that applies the given
* customizer. This can be useful for applying pre-packaged customizations.
* @param customizer the customizer to apply
* @return a new {@link ReactorClientHttpConnectorBuilder}
* @since 4.0.0
*/
public ReactorClientHttpConnectorBuilder with(UnaryOperator<ReactorClientHttpConnectorBuilder> customizer) {
return customizer.apply(this);
}
@Override
protected ReactorClientHttpConnector createClientHttpConnector(ClientHttpConnectorSettings settings) {
HttpClient httpClient = this.httpClientBuilder.build(asHttpClientSettings(settings));

View File

@ -92,6 +92,15 @@ class HttpComponentsClientHttpRequestFactoryBuilderTests
assertThat(bundles).contains(settings.sslBundle());
}
@Test
void with() {
TestCustomizer<HttpClientBuilder> customizer = new TestCustomizer<>();
ClientHttpRequestFactoryBuilder.httpComponents()
.with((builder) -> builder.withHttpClientCustomizer(customizer))
.build();
customizer.assertCalled();
}
@Override
protected long connectTimeout(HttpComponentsClientHttpRequestFactory requestFactory) {
return (long) ReflectionTestUtils.getField(requestFactory, "connectTimeout");

View File

@ -60,6 +60,13 @@ class JdkClientHttpRequestFactoryBuilderTests
assertThat(httpClient.executor()).containsSame(executor);
}
@Test
void with() {
TestCustomizer<HttpClient.Builder> customizer = new TestCustomizer<>();
ClientHttpRequestFactoryBuilder.jdk().with((builder) -> builder.withHttpClientCustomizer(customizer)).build();
customizer.assertCalled();
}
@Override
protected long connectTimeout(JdkClientHttpRequestFactory requestFactory) {
HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient");

View File

@ -55,6 +55,13 @@ class JettyClientHttpRequestFactoryBuilderTests
clientConnectorCustomizerCustomizer.assertCalled();
}
@Test
void with() {
TestCustomizer<HttpClient> customizer = new TestCustomizer<>();
ClientHttpRequestFactoryBuilder.jetty().with((builder) -> builder.withHttpClientCustomizer(customizer)).build();
customizer.assertCalled();
}
@Override
protected long connectTimeout(JettyClientHttpRequestFactory requestFactory) {
return ((HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient")).getConnectTimeout();

View File

@ -85,6 +85,19 @@ class ReactorClientHttpRequestFactoryBuilderTests
assertThat(httpClients).hasSize(2);
}
@Test
void with() {
boolean[] called = new boolean[1];
Supplier<HttpClient> httpClientFactory = () -> {
called[0] = true;
return HttpClient.create();
};
ClientHttpRequestFactoryBuilder.reactor()
.with((builder) -> builder.withHttpClientFactory(httpClientFactory))
.build();
assertThat(called).containsExactly(true);
}
@Override
protected long connectTimeout(ReactorClientHttpRequestFactory requestFactory) {
return (int) ((HttpClient) ReflectionTestUtils.getField(requestFactory, "httpClient")).configuration()

View File

@ -93,6 +93,15 @@ class HttpComponentsClientHttpConnectorBuilderTests
assertThat(bundles).contains(settings.sslBundle());
}
@Test
void with() {
TestCustomizer<HttpAsyncClientBuilder> customizer = new TestCustomizer<>();
ClientHttpConnectorBuilder.httpComponents()
.with((builder) -> builder.withHttpClientCustomizer(customizer))
.build();
customizer.assertCalled();
}
@Override
protected long connectTimeout(HttpComponentsClientHttpConnector connector) {
return getConnectorConfig(connector).getConnectTimeout().toMilliseconds();

View File

@ -60,6 +60,13 @@ class JdkClientHttpConnectorBuilderTests extends AbstractClientHttpConnectorBuil
assertThat(httpClient.executor()).containsSame(executor);
}
@Test
void with() {
TestCustomizer<HttpClient.Builder> customizer = new TestCustomizer<>();
ClientHttpConnectorBuilder.jdk().with((builder) -> builder.withHttpClientCustomizer(customizer)).build();
customizer.assertCalled();
}
@Override
protected long connectTimeout(JdkClientHttpConnector connector) {
HttpClient httpClient = (HttpClient) ReflectionTestUtils.getField(connector, "httpClient");

View File

@ -56,6 +56,13 @@ class JettyClientHttpConnectorBuilderTests extends AbstractClientHttpConnectorBu
clientConnectorCustomizerCustomizer.assertCalled();
}
@Test
void with() {
TestCustomizer<HttpClient> customizer = new TestCustomizer<>();
ClientHttpConnectorBuilder.jetty().with((builder) -> builder.withHttpClientCustomizer(customizer)).build();
customizer.assertCalled();
}
@Override
protected long connectTimeout(JettyClientHttpConnector connector) {
return ((HttpClient) ReflectionTestUtils.getField(connector, "httpClient")).getConnectTimeout();

View File

@ -84,6 +84,19 @@ class ReactorClientHttpConnectorBuilderTests
assertThat(httpClients).hasSize(2);
}
@Test
void with() {
boolean[] called = new boolean[1];
Supplier<HttpClient> httpClientFactory = () -> {
called[0] = true;
return HttpClient.create();
};
ClientHttpConnectorBuilder.reactor()
.with((builder) -> builder.withHttpClientFactory(httpClientFactory))
.build();
assertThat(called).containsExactly(true);
}
@Override
protected long connectTimeout(ReactorClientHttpConnector connector) {
return (int) ((HttpClient) ReflectionTestUtils.getField(connector, "httpClient")).configuration()