Skip to content

Commit 417a4a0

Browse files
committed
Use bean class loader when detecting ClientHttpRequestFactoryBuilder
Update `ClientHttpRequestFactoryBuilder` with a detect method that accepts a specific classloader and update `HttpClientAutoConfiguration` to use it. Fixes gh-44986
1 parent 95325a8 commit 417a4a0

File tree

8 files changed

+77
-13
lines changed

8 files changed

+77
-13
lines changed

Diff for: spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/AbstractHttpRequestFactoryProperties.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,12 @@ public void setFactory(Factory factory) {
4848

4949
/**
5050
* Return a {@link ClientHttpRequestFactoryBuilder} based on the properties.
51+
* @param classLoader the class loader to use for detection
5152
* @return a {@link ClientHttpRequestFactoryBuilder}
5253
*/
53-
protected final ClientHttpRequestFactoryBuilder<?> factoryBuilder() {
54+
protected final ClientHttpRequestFactoryBuilder<?> factoryBuilder(ClassLoader classLoader) {
5455
Factory factory = getFactory();
55-
return (factory != null) ? factory.builder() : ClientHttpRequestFactoryBuilder.detect();
56+
return (factory != null) ? factory.builder() : ClientHttpRequestFactoryBuilder.detect(classLoader);
5657
}
5758

5859
/**

Diff for: spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfiguration.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.List;
2020

21+
import org.springframework.beans.factory.BeanClassLoaderAware;
2122
import org.springframework.beans.factory.ObjectProvider;
2223
import org.springframework.boot.autoconfigure.AutoConfiguration;
2324
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -47,13 +48,20 @@
4748
@ConditionalOnClass(ClientHttpRequestFactory.class)
4849
@Conditional(NotReactiveWebApplicationCondition.class)
4950
@EnableConfigurationProperties(HttpClientProperties.class)
50-
public class HttpClientAutoConfiguration {
51+
public class HttpClientAutoConfiguration implements BeanClassLoaderAware {
52+
53+
private ClassLoader beanClassLoader;
54+
55+
@Override
56+
public void setBeanClassLoader(ClassLoader classLoader) {
57+
this.beanClassLoader = classLoader;
58+
}
5159

5260
@Bean
5361
@ConditionalOnMissingBean
5462
ClientHttpRequestFactoryBuilder<?> clientHttpRequestFactoryBuilder(HttpClientProperties httpClientProperties,
5563
ObjectProvider<ClientHttpRequestFactoryBuilderCustomizer<?>> clientHttpRequestFactoryBuilderCustomizers) {
56-
ClientHttpRequestFactoryBuilder<?> builder = httpClientProperties.factoryBuilder();
64+
ClientHttpRequestFactoryBuilder<?> builder = httpClientProperties.factoryBuilder(this.beanClassLoader);
5765
return customize(builder, clientHttpRequestFactoryBuilderCustomizers.orderedStream().toList());
5866
}
5967

Diff for: spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfigurationTests.java

+29
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@
2828
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
2929
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
3030
import org.springframework.boot.http.client.HttpComponentsClientHttpRequestFactoryBuilder;
31+
import org.springframework.boot.http.client.JdkClientHttpRequestFactoryBuilder;
3132
import org.springframework.boot.http.client.JettyClientHttpRequestFactoryBuilder;
33+
import org.springframework.boot.http.client.ReactorClientHttpRequestFactoryBuilder;
3234
import org.springframework.boot.http.client.SimpleClientHttpRequestFactoryBuilder;
35+
import org.springframework.boot.test.context.FilteredClassLoader;
3336
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3437
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
3538
import org.springframework.context.annotation.Bean;
@@ -87,6 +90,32 @@ private List<String> sslPropertyValues() {
8790
return propertyValues;
8891
}
8992

93+
@Test
94+
void whenHttpComponentsIsUnavailableThenJettyClientBeansAreDefined() {
95+
this.contextRunner
96+
.withClassLoader(new FilteredClassLoader(org.apache.hc.client5.http.impl.classic.HttpClients.class))
97+
.run((context) -> assertThat(context.getBean(ClientHttpRequestFactoryBuilder.class))
98+
.isExactlyInstanceOf(JettyClientHttpRequestFactoryBuilder.class));
99+
}
100+
101+
@Test
102+
void whenHttpComponentsAndJettyAreUnavailableThenReactorClientBeansAreDefined() {
103+
this.contextRunner
104+
.withClassLoader(new FilteredClassLoader(org.apache.hc.client5.http.impl.classic.HttpClients.class,
105+
org.eclipse.jetty.client.HttpClient.class))
106+
.run((context) -> assertThat(context.getBean(ClientHttpRequestFactoryBuilder.class))
107+
.isExactlyInstanceOf(ReactorClientHttpRequestFactoryBuilder.class));
108+
}
109+
110+
@Test
111+
void whenHttpComponentsAndJettyAndReactorAreUnavailableThenJdkClientBeansAreDefined() {
112+
this.contextRunner
113+
.withClassLoader(new FilteredClassLoader(org.apache.hc.client5.http.impl.classic.HttpClients.class,
114+
org.eclipse.jetty.client.HttpClient.class, reactor.netty.http.client.HttpClient.class))
115+
.run((context) -> assertThat(context.getBean(ClientHttpRequestFactoryBuilder.class))
116+
.isExactlyInstanceOf(JdkClientHttpRequestFactoryBuilder.class));
117+
}
118+
90119
@Test
91120
void whenReactiveWebApplicationBeansAreNotConfigured() {
92121
new ReactiveWebApplicationContextRunner().withConfiguration(autoConfigurations)

Diff for: spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ClientHttpRequestFactoryBuilder.java

+23-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2024 the original author or authors.
2+
* Copyright 2012-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -196,16 +196,34 @@ static <T extends ClientHttpRequestFactory> ClientHttpRequestFactoryBuilder<T> o
196196
* @return the most suitable {@link ClientHttpRequestFactoryBuilder} for the classpath
197197
*/
198198
static ClientHttpRequestFactoryBuilder<? extends ClientHttpRequestFactory> detect() {
199-
if (HttpComponentsClientHttpRequestFactoryBuilder.Classes.PRESENT) {
199+
return detect(null);
200+
}
201+
202+
/**
203+
* Detect the most suitable {@link ClientHttpRequestFactoryBuilder} based on the
204+
* classpath. The methods favors builders in the following order:
205+
* <ol>
206+
* <li>{@link #httpComponents()}</li>
207+
* <li>{@link #jetty()}</li>
208+
* <li>{@link #reactor()}</li>
209+
* <li>{@link #jdk()}</li>
210+
* <li>{@link #simple()}</li>
211+
* </ol>
212+
* @param classLoader the class loader to use for detection
213+
* @return the most suitable {@link ClientHttpRequestFactoryBuilder} for the classpath
214+
* @since 3.5.0
215+
*/
216+
static ClientHttpRequestFactoryBuilder<? extends ClientHttpRequestFactory> detect(ClassLoader classLoader) {
217+
if (HttpComponentsClientHttpRequestFactoryBuilder.Classes.present(classLoader)) {
200218
return httpComponents();
201219
}
202-
if (JettyClientHttpRequestFactoryBuilder.Classes.PRESENT) {
220+
if (JettyClientHttpRequestFactoryBuilder.Classes.present(classLoader)) {
203221
return jetty();
204222
}
205-
if (ReactorClientHttpRequestFactoryBuilder.Classes.PRESENT) {
223+
if (ReactorClientHttpRequestFactoryBuilder.Classes.present(classLoader)) {
206224
return reactor();
207225
}
208-
if (JdkClientHttpRequestFactoryBuilder.Classes.PRESENT) {
226+
if (JdkClientHttpRequestFactoryBuilder.Classes.present(classLoader)) {
209227
return jdk();
210228
}
211229
return simple();

Diff for: spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/HttpComponentsClientHttpRequestFactoryBuilder.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,9 @@ static class Classes {
156156

157157
static final String HTTP_CLIENTS = "org.apache.hc.client5.http.impl.classic.HttpClients";
158158

159-
static final boolean PRESENT = ClassUtils.isPresent(HTTP_CLIENTS, null);
159+
static boolean present(ClassLoader classLoader) {
160+
return ClassUtils.isPresent(HTTP_CLIENTS, classLoader);
161+
}
160162

161163
}
162164

Diff for: spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/JdkClientHttpRequestFactoryBuilder.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ static class Classes {
8686

8787
static final String HTTP_CLIENT = "java.net.http.HttpClient";
8888

89-
static final boolean PRESENT = ClassUtils.isPresent(HTTP_CLIENT, null);
89+
static boolean present(ClassLoader classLoader) {
90+
return ClassUtils.isPresent(HTTP_CLIENT, classLoader);
91+
}
9092

9193
}
9294

Diff for: spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/JettyClientHttpRequestFactoryBuilder.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ static class Classes {
116116

117117
static final String HTTP_CLIENT = "org.eclipse.jetty.client.HttpClient";
118118

119-
static final boolean PRESENT = ClassUtils.isPresent(HTTP_CLIENT, null);
119+
static boolean present(ClassLoader classLoader) {
120+
return ClassUtils.isPresent(HTTP_CLIENT, classLoader);
121+
}
120122

121123
}
122124

Diff for: spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ReactorClientHttpRequestFactoryBuilder.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ static class Classes {
9191

9292
static final String HTTP_CLIENT = "reactor.netty.http.client.HttpClient";
9393

94-
static final boolean PRESENT = ClassUtils.isPresent(HTTP_CLIENT, null);
94+
static boolean present(ClassLoader classLoader) {
95+
return ClassUtils.isPresent(HTTP_CLIENT, classLoader);
96+
}
9597

9698
}
9799

0 commit comments

Comments
 (0)