Skip to content

Commit 78458af

Browse files
committed
Introduce ClientHttpRequestFactoryBuilder support
Add a new `ClientHttpRequestFactoryBuilder` interface to support the creation of `ClientHttpRequestFactory` instances. The new code will ultimately replace the existing `ClientHttpRequestFactories` class. The `ClientHttpRequestFactoryBuilder` is a functional interface with additional static factory methods for the various supported `ClientHttpRequestFactory` types. Each type has it's own builder which should allow us to support additional customization in the future. Unlike `ClientHttpRequestFactories`, the builder aligns with Spring Framework defaults and will detect the `JdkClientHttpRequestFactory` in preference of `SimpleClientHttpRequestFactory`. This commit also relocates `ClientHttpRequestFactorySettings` to bring it into the new `http.client` package. See gh-36266
1 parent 0a4ac28 commit 78458af

23 files changed

+2006
-24
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2012-2024 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+
* https://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.http.client;
18+
19+
import java.util.ArrayList;
20+
import java.util.Collection;
21+
import java.util.Collections;
22+
import java.util.List;
23+
import java.util.function.Consumer;
24+
25+
import org.springframework.boot.util.LambdaSafe;
26+
import org.springframework.http.client.ClientHttpRequestFactory;
27+
import org.springframework.util.Assert;
28+
29+
/**
30+
* Internal base class used for {@link ClientHttpRequestFactoryBuilder} implementations.
31+
*
32+
* @param <T> the {@link ClientHttpRequestFactory} type
33+
* @author Phillip Webb
34+
*/
35+
abstract class AbstractClientHttpRequestFactoryBuilder<T extends ClientHttpRequestFactory>
36+
implements ClientHttpRequestFactoryBuilder<T> {
37+
38+
private final List<Consumer<T>> customizers;
39+
40+
protected AbstractClientHttpRequestFactoryBuilder(List<Consumer<T>> customizers) {
41+
this.customizers = (customizers != null) ? customizers : Collections.emptyList();
42+
}
43+
44+
protected final List<Consumer<T>> mergedCustomizers(Consumer<T> customizer) {
45+
Assert.notNull(this.customizers, "'customizer' must not be null");
46+
return merge(this.customizers, List.of(customizer));
47+
}
48+
49+
protected final List<Consumer<T>> mergedCustomizers(Collection<Consumer<T>> customizers) {
50+
Assert.notNull(customizers, "'customizers' must not be null");
51+
Assert.noNullElements(customizers, "'customizers' must not contain null elements");
52+
return merge(this.customizers, customizers);
53+
}
54+
55+
private <E> List<E> merge(Collection<E> list, Collection<? extends E> additional) {
56+
List<E> merged = new ArrayList<>(list);
57+
merged.addAll(additional);
58+
return List.copyOf(merged);
59+
}
60+
61+
@Override
62+
@SuppressWarnings("unchecked")
63+
public final T build(ClientHttpRequestFactorySettings settings) {
64+
T factory = createClientHttpRequestFactory(
65+
(settings != null) ? settings : ClientHttpRequestFactorySettings.defaults());
66+
LambdaSafe.callbacks(Consumer.class, this.customizers, factory).invoke((consumer) -> consumer.accept(factory));
67+
return factory;
68+
}
69+
70+
protected abstract T createClientHttpRequestFactory(ClientHttpRequestFactorySettings settings);
71+
72+
}
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/*
2+
* Copyright 2012-2024 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+
* https://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.http.client;
18+
19+
import java.util.Collection;
20+
import java.util.List;
21+
import java.util.function.Consumer;
22+
import java.util.function.Supplier;
23+
24+
import org.springframework.boot.util.LambdaSafe;
25+
import org.springframework.http.client.ClientHttpRequestFactory;
26+
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
27+
import org.springframework.http.client.JdkClientHttpRequestFactory;
28+
import org.springframework.http.client.JettyClientHttpRequestFactory;
29+
import org.springframework.http.client.ReactorClientHttpRequestFactory;
30+
import org.springframework.http.client.SimpleClientHttpRequestFactory;
31+
import org.springframework.util.Assert;
32+
33+
/**
34+
* Interface used to build a fully configured {@link ClientHttpRequestFactory}. Builders
35+
* for {@link #httpComponents() Apache HTTP Components}, {@link #jetty() Jetty},
36+
* {@link #reactor() Reactor}, {@link #jdk() JDK} and {@link #simple() simple client} can
37+
* be obtained using the factory methods on this interface. The {@link #of(Class)} and
38+
* {@link #of(Supplier)} methods may be used to instantiate other
39+
* {@link ClientHttpRequestFactory} instances using reflection.
40+
*
41+
* @param <T> the {@link ClientHttpRequestFactory} type
42+
* @author Phillip Webb
43+
* @since 3.4.0
44+
*/
45+
@FunctionalInterface
46+
public interface ClientHttpRequestFactoryBuilder<T extends ClientHttpRequestFactory> {
47+
48+
/**
49+
* Build a default configured {@link ClientHttpRequestFactory}.
50+
* @return a default configured {@link ClientHttpRequestFactory}.
51+
*/
52+
default T build() {
53+
return build(null);
54+
}
55+
56+
/**
57+
* Build a fully configured {@link ClientHttpRequestFactory}, applying the given
58+
* {@code settings} if they are provided.
59+
* @param settings the settings to apply or {@code null}
60+
* @return a fully configured {@link ClientHttpRequestFactory}.
61+
*/
62+
T build(ClientHttpRequestFactorySettings settings);
63+
64+
/**
65+
* Return a new {@link ClientHttpRequestFactoryBuilder} that applies the given
66+
* customizer to the {@link ClientHttpRequestFactory} after it has been built.
67+
* @param customizer the customizers to apply
68+
* @return a new {@link ClientHttpRequestFactoryBuilder} instance
69+
*/
70+
default ClientHttpRequestFactoryBuilder<T> withCustomizer(Consumer<T> customizer) {
71+
return withCustomizers(List.of(customizer));
72+
}
73+
74+
/**
75+
* Return a new {@link ClientHttpRequestFactoryBuilder} that applies the given
76+
* customizers to the {@link ClientHttpRequestFactory} after it has been built.
77+
* @param customizers the customizers to apply
78+
* @return a new {@link ClientHttpRequestFactoryBuilder} instance
79+
*/
80+
@SuppressWarnings("unchecked")
81+
default ClientHttpRequestFactoryBuilder<T> withCustomizers(Collection<Consumer<T>> customizers) {
82+
Assert.notNull(customizers, "'customizers' must not be null");
83+
Assert.noNullElements(customizers, "'customizers' must not contain null elements");
84+
return (settings) -> {
85+
T factory = build(settings);
86+
LambdaSafe.callbacks(Consumer.class, customizers, factory).invoke((consumer) -> consumer.accept(factory));
87+
return factory;
88+
};
89+
}
90+
91+
/**
92+
* Return a {@link HttpComponentsClientHttpRequestFactoryBuilder} that can be used to
93+
* build a {@link HttpComponentsClientHttpRequestFactory}.
94+
* @return a new {@link HttpComponentsClientHttpRequestFactoryBuilder}
95+
*/
96+
static HttpComponentsClientHttpRequestFactoryBuilder httpComponents() {
97+
return new HttpComponentsClientHttpRequestFactoryBuilder();
98+
}
99+
100+
/**
101+
* Return a {@link JettyClientHttpRequestFactoryBuilder} that can be used to build a
102+
* {@link JettyClientHttpRequestFactory}.
103+
* @return a new {@link JettyClientHttpRequestFactoryBuilder}
104+
*/
105+
static JettyClientHttpRequestFactoryBuilder jetty() {
106+
return new JettyClientHttpRequestFactoryBuilder();
107+
}
108+
109+
/**
110+
* Return a {@link ReactorClientHttpRequestFactoryBuilder} that can be used to build a
111+
* {@link ReactorClientHttpRequestFactory}.
112+
* @return a new {@link ReactorClientHttpRequestFactoryBuilder}
113+
*/
114+
static ReactorClientHttpRequestFactoryBuilder reactor() {
115+
return new ReactorClientHttpRequestFactoryBuilder();
116+
}
117+
118+
/**
119+
* Return a {@link JdkClientHttpRequestFactoryBuilder} that can be used to build a
120+
* {@link JdkClientHttpRequestFactory} .
121+
* @return a new {@link JdkClientHttpRequestFactoryBuilder}
122+
*/
123+
static JdkClientHttpRequestFactoryBuilder jdk() {
124+
return new JdkClientHttpRequestFactoryBuilder();
125+
}
126+
127+
/**
128+
* Return a {@link SimpleClientHttpRequestFactoryBuilder} that can be used to build a
129+
* {@link SimpleClientHttpRequestFactory} .
130+
* @return a new {@link SimpleClientHttpRequestFactoryBuilder}
131+
*/
132+
static SimpleClientHttpRequestFactoryBuilder simple() {
133+
return new SimpleClientHttpRequestFactoryBuilder();
134+
}
135+
136+
/**
137+
* Return a new {@link ClientHttpRequestFactoryBuilder} for the given
138+
* {@code requestFactoryType}. The following implementations are supported without the
139+
* use of reflection:
140+
* <ul>
141+
* <li>{@link HttpComponentsClientHttpRequestFactory}</li>
142+
* <li>{@link JdkClientHttpRequestFactory}</li>
143+
* <li>{@link JettyClientHttpRequestFactory}</li>
144+
* <li>{@link ReactorClientHttpRequestFactory}</li>
145+
* <li>{@link SimpleClientHttpRequestFactory}</li>
146+
* </ul>
147+
* @param <T> the {@link ClientHttpRequestFactory} type
148+
* @param requestFactoryType the {@link ClientHttpRequestFactory} type
149+
* @return a new {@link ClientHttpRequestFactoryBuilder}
150+
*/
151+
@SuppressWarnings("unchecked")
152+
static <T extends ClientHttpRequestFactory> ClientHttpRequestFactoryBuilder<T> of(Class<T> requestFactoryType) {
153+
Assert.notNull(requestFactoryType, "'requestFactoryType' must not be null");
154+
Assert.isTrue(requestFactoryType != ClientHttpRequestFactory.class,
155+
"'requestFactoryType' must be an implementation of ClientHttpRequestFactory");
156+
if (requestFactoryType == HttpComponentsClientHttpRequestFactory.class) {
157+
return (ClientHttpRequestFactoryBuilder<T>) httpComponents();
158+
}
159+
if (requestFactoryType == JettyClientHttpRequestFactory.class) {
160+
return (ClientHttpRequestFactoryBuilder<T>) jetty();
161+
}
162+
if (requestFactoryType == ReactorClientHttpRequestFactory.class) {
163+
return (ClientHttpRequestFactoryBuilder<T>) reactor();
164+
}
165+
if (requestFactoryType == JdkClientHttpRequestFactory.class) {
166+
return (ClientHttpRequestFactoryBuilder<T>) jdk();
167+
}
168+
if (requestFactoryType == SimpleClientHttpRequestFactory.class) {
169+
return (ClientHttpRequestFactoryBuilder<T>) simple();
170+
}
171+
return new ReflectiveComponentsClientHttpRequestFactoryBuilder<>(requestFactoryType);
172+
}
173+
174+
/**
175+
* Return a new {@link ClientHttpRequestFactoryBuilder} from the given supplier, using
176+
* reflection to ultimately apply the {@link ClientHttpRequestFactorySettings}.
177+
* @param <T> the {@link ClientHttpRequestFactory} type
178+
* @param requestFactorySupplier the {@link ClientHttpRequestFactory} supplier
179+
* @return a new {@link ClientHttpRequestFactoryBuilder}
180+
*/
181+
static <T extends ClientHttpRequestFactory> ClientHttpRequestFactoryBuilder<T> of(
182+
Supplier<T> requestFactorySupplier) {
183+
return new ReflectiveComponentsClientHttpRequestFactoryBuilder<>(requestFactorySupplier);
184+
}
185+
186+
/**
187+
* Detect the most suitable {@link ClientHttpRequestFactoryBuilder} based on the
188+
* classpath. The methods favors builders in the following order:
189+
* <ol>
190+
* <li>{@link #httpComponents()}</li>
191+
* <li>{@link #jetty()}</li>
192+
* <li>{@link #reactor()}</li>
193+
* <li>{@link #jdk()}</li>
194+
* <li>{@link #simple()}</li>
195+
* </ol>
196+
* @return the most suitable {@link ClientHttpRequestFactoryBuilder} for the classpath
197+
*/
198+
static ClientHttpRequestFactoryBuilder<? extends ClientHttpRequestFactory> detect() {
199+
if (HttpComponentsClientHttpRequestFactoryBuilder.Classes.PRESENT) {
200+
return httpComponents();
201+
}
202+
if (JettyClientHttpRequestFactoryBuilder.Classes.PRESENT) {
203+
return jetty();
204+
}
205+
if (ReactorClientHttpRequestFactoryBuilder.Classes.PRESENT) {
206+
return reactor();
207+
}
208+
if (JdkClientHttpRequestFactoryBuilder.Classes.PRESENT) {
209+
return jdk();
210+
}
211+
return simple();
212+
}
213+
214+
}
Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.web.client;
17+
package org.springframework.boot.http.client;
1818

1919
import java.lang.reflect.Field;
2020
import java.lang.reflect.Method;
@@ -28,6 +28,7 @@
2828
import org.springframework.http.client.AbstractClientHttpRequestFactoryWrapper;
2929
import org.springframework.http.client.ClientHttpRequestFactory;
3030
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
31+
import org.springframework.http.client.JdkClientHttpRequestFactory;
3132
import org.springframework.http.client.JettyClientHttpRequestFactory;
3233
import org.springframework.http.client.ReactorClientHttpRequestFactory;
3334
import org.springframework.http.client.SimpleClientHttpRequestFactory;
@@ -36,12 +37,12 @@
3637
import org.springframework.util.ReflectionUtils;
3738

3839
/**
39-
* {@link RuntimeHintsRegistrar} for {@link ClientHttpRequestFactories}.
40+
* {@link RuntimeHintsRegistrar} for {@link ClientHttpRequestFactory} implementations.
4041
*
4142
* @author Andy Wilkinson
4243
* @author Phillip Webb
4344
*/
44-
class ClientHttpRequestFactoriesRuntimeHints implements RuntimeHintsRegistrar {
45+
class ClientHttpRequestFactoryRuntimeHints implements RuntimeHintsRegistrar {
4546

4647
@Override
4748
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
@@ -52,21 +53,29 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
5253

5354
private void registerHints(ReflectionHints hints, ClassLoader classLoader) {
5455
hints.registerField(findField(AbstractClientHttpRequestFactoryWrapper.class, "requestFactory"));
55-
hints.registerTypeIfPresent(classLoader, ClientHttpRequestFactories.APACHE_HTTP_CLIENT_CLASS, (typeHint) -> {
56-
typeHint.onReachableType(TypeReference.of(ClientHttpRequestFactories.APACHE_HTTP_CLIENT_CLASS));
57-
registerReflectionHints(hints, HttpComponentsClientHttpRequestFactory.class);
58-
});
59-
hints.registerTypeIfPresent(classLoader, ClientHttpRequestFactories.JETTY_CLIENT_CLASS, (typeHint) -> {
60-
typeHint.onReachableType(TypeReference.of(ClientHttpRequestFactories.JETTY_CLIENT_CLASS));
61-
registerReflectionHints(hints, JettyClientHttpRequestFactory.class, long.class);
62-
});
56+
registerClientHttpRequestFactoryHints(hints, classLoader,
57+
HttpComponentsClientHttpRequestFactoryBuilder.Classes.HTTP_CLIENTS,
58+
() -> registerReflectionHints(hints, HttpComponentsClientHttpRequestFactory.class));
59+
registerClientHttpRequestFactoryHints(hints, classLoader,
60+
JettyClientHttpRequestFactoryBuilder.Classes.HTTP_CLIENT,
61+
() -> registerReflectionHints(hints, JettyClientHttpRequestFactory.class, long.class));
62+
registerClientHttpRequestFactoryHints(hints, classLoader,
63+
ReactorClientHttpRequestFactoryBuilder.Classes.HTTP_CLIENT,
64+
() -> registerReflectionHints(hints, ReactorClientHttpRequestFactory.class, long.class));
65+
registerClientHttpRequestFactoryHints(hints, classLoader,
66+
JdkClientHttpRequestFactoryBuilder.Classes.HTTP_CLIENT,
67+
() -> registerReflectionHints(hints, JdkClientHttpRequestFactory.class));
6368
hints.registerType(SimpleClientHttpRequestFactory.class, (typeHint) -> {
6469
typeHint.onReachableType(HttpURLConnection.class);
6570
registerReflectionHints(hints, SimpleClientHttpRequestFactory.class);
6671
});
67-
hints.registerTypeIfPresent(classLoader, ClientHttpRequestFactories.REACTOR_CLIENT_CLASS, (typeHint) -> {
68-
typeHint.onReachableType(TypeReference.of(ClientHttpRequestFactories.REACTOR_CLIENT_CLASS));
69-
registerReflectionHints(hints, ReactorClientHttpRequestFactory.class, long.class);
72+
}
73+
74+
private void registerClientHttpRequestFactoryHints(ReflectionHints hints, ClassLoader classLoader, String className,
75+
Runnable action) {
76+
hints.registerTypeIfPresent(classLoader, className, (typeHint) -> {
77+
typeHint.onReachableType(TypeReference.of(className));
78+
action.run();
7079
});
7180
}
7281

0 commit comments

Comments
 (0)