diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/AbstractHttpRequestFactoryProperties.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/AbstractHttpRequestFactoryProperties.java index 5c8ae4b43a..c00519acb7 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/AbstractHttpRequestFactoryProperties.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/AbstractHttpRequestFactoryProperties.java @@ -48,11 +48,12 @@ public abstract class AbstractHttpRequestFactoryProperties extends AbstractHttpC /** * Return a {@link ClientHttpRequestFactoryBuilder} based on the properties. + * @param classLoader the class loader to use for detection * @return a {@link ClientHttpRequestFactoryBuilder} */ - protected final ClientHttpRequestFactoryBuilder factoryBuilder() { + protected final ClientHttpRequestFactoryBuilder factoryBuilder(ClassLoader classLoader) { Factory factory = getFactory(); - return (factory != null) ? factory.builder() : ClientHttpRequestFactoryBuilder.detect(); + return (factory != null) ? factory.builder() : ClientHttpRequestFactoryBuilder.detect(classLoader); } /** diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfiguration.java index 58df283414..2b0a55b74b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfiguration.java @@ -18,6 +18,7 @@ package org.springframework.boot.autoconfigure.http.client; import java.util.List; +import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -47,13 +48,20 @@ import org.springframework.http.client.ClientHttpRequestFactory; @ConditionalOnClass(ClientHttpRequestFactory.class) @Conditional(NotReactiveWebApplicationCondition.class) @EnableConfigurationProperties(HttpClientProperties.class) -public class HttpClientAutoConfiguration { +public class HttpClientAutoConfiguration implements BeanClassLoaderAware { + + private ClassLoader beanClassLoader; + + @Override + public void setBeanClassLoader(ClassLoader classLoader) { + this.beanClassLoader = classLoader; + } @Bean @ConditionalOnMissingBean ClientHttpRequestFactoryBuilder clientHttpRequestFactoryBuilder(HttpClientProperties httpClientProperties, ObjectProvider> clientHttpRequestFactoryBuilderCustomizers) { - ClientHttpRequestFactoryBuilder builder = httpClientProperties.factoryBuilder(); + ClientHttpRequestFactoryBuilder builder = httpClientProperties.factoryBuilder(this.beanClassLoader); return customize(builder, clientHttpRequestFactoryBuilderCustomizers.orderedStream().toList()); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfigurationTests.java index 7f2ef917ad..a90d31949e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/client/HttpClientAutoConfigurationTests.java @@ -28,8 +28,11 @@ import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects; import org.springframework.boot.http.client.HttpComponentsClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.JdkClientHttpRequestFactoryBuilder; import org.springframework.boot.http.client.JettyClientHttpRequestFactoryBuilder; +import org.springframework.boot.http.client.ReactorClientHttpRequestFactoryBuilder; import org.springframework.boot.http.client.SimpleClientHttpRequestFactoryBuilder; +import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.context.annotation.Bean; @@ -87,6 +90,32 @@ class HttpClientAutoConfigurationTests { return propertyValues; } + @Test + void whenHttpComponentsIsUnavailableThenJettyClientBeansAreDefined() { + this.contextRunner + .withClassLoader(new FilteredClassLoader(org.apache.hc.client5.http.impl.classic.HttpClients.class)) + .run((context) -> assertThat(context.getBean(ClientHttpRequestFactoryBuilder.class)) + .isExactlyInstanceOf(JettyClientHttpRequestFactoryBuilder.class)); + } + + @Test + void whenHttpComponentsAndJettyAreUnavailableThenReactorClientBeansAreDefined() { + this.contextRunner + .withClassLoader(new FilteredClassLoader(org.apache.hc.client5.http.impl.classic.HttpClients.class, + org.eclipse.jetty.client.HttpClient.class)) + .run((context) -> assertThat(context.getBean(ClientHttpRequestFactoryBuilder.class)) + .isExactlyInstanceOf(ReactorClientHttpRequestFactoryBuilder.class)); + } + + @Test + void whenHttpComponentsAndJettyAndReactorAreUnavailableThenJdkClientBeansAreDefined() { + this.contextRunner + .withClassLoader(new FilteredClassLoader(org.apache.hc.client5.http.impl.classic.HttpClients.class, + org.eclipse.jetty.client.HttpClient.class, reactor.netty.http.client.HttpClient.class)) + .run((context) -> assertThat(context.getBean(ClientHttpRequestFactoryBuilder.class)) + .isExactlyInstanceOf(JdkClientHttpRequestFactoryBuilder.class)); + } + @Test void whenReactiveWebApplicationBeansAreNotConfigured() { new ReactiveWebApplicationContextRunner().withConfiguration(autoConfigurations) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ClientHttpRequestFactoryBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ClientHttpRequestFactoryBuilder.java index 1711ae9399..15b68aeff0 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ClientHttpRequestFactoryBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/ClientHttpRequestFactoryBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2024 the original author or authors. + * Copyright 2012-2025 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. @@ -196,16 +196,34 @@ public interface ClientHttpRequestFactoryBuilder detect() { - if (HttpComponentsClientHttpRequestFactoryBuilder.Classes.PRESENT) { + return detect(null); + } + + /** + * Detect the most suitable {@link ClientHttpRequestFactoryBuilder} based on the + * classpath. The methods favors builders in the following order: + *
    + *
  1. {@link #httpComponents()}
  2. + *
  3. {@link #jetty()}
  4. + *
  5. {@link #reactor()}
  6. + *
  7. {@link #jdk()}
  8. + *
  9. {@link #simple()}
  10. + *
+ * @param classLoader the class loader to use for detection + * @return the most suitable {@link ClientHttpRequestFactoryBuilder} for the classpath + * @since 3.5.0 + */ + static ClientHttpRequestFactoryBuilder detect(ClassLoader classLoader) { + if (HttpComponentsClientHttpRequestFactoryBuilder.Classes.present(classLoader)) { return httpComponents(); } - if (JettyClientHttpRequestFactoryBuilder.Classes.PRESENT) { + if (JettyClientHttpRequestFactoryBuilder.Classes.present(classLoader)) { return jetty(); } - if (ReactorClientHttpRequestFactoryBuilder.Classes.PRESENT) { + if (ReactorClientHttpRequestFactoryBuilder.Classes.present(classLoader)) { return reactor(); } - if (JdkClientHttpRequestFactoryBuilder.Classes.PRESENT) { + if (JdkClientHttpRequestFactoryBuilder.Classes.present(classLoader)) { return jdk(); } return simple(); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/HttpComponentsClientHttpRequestFactoryBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/HttpComponentsClientHttpRequestFactoryBuilder.java index 5549f57a8c..b0110066e5 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/HttpComponentsClientHttpRequestFactoryBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/HttpComponentsClientHttpRequestFactoryBuilder.java @@ -156,7 +156,9 @@ public final class HttpComponentsClientHttpRequestFactoryBuilder static final String HTTP_CLIENTS = "org.apache.hc.client5.http.impl.classic.HttpClients"; - static final boolean PRESENT = ClassUtils.isPresent(HTTP_CLIENTS, null); + static boolean present(ClassLoader classLoader) { + return ClassUtils.isPresent(HTTP_CLIENTS, classLoader); + } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/JdkClientHttpRequestFactoryBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/JdkClientHttpRequestFactoryBuilder.java index 9e20c07d3f..b2baaa94f4 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/JdkClientHttpRequestFactoryBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/JdkClientHttpRequestFactoryBuilder.java @@ -86,7 +86,9 @@ public final class JdkClientHttpRequestFactoryBuilder static final String HTTP_CLIENT = "java.net.http.HttpClient"; - static final boolean PRESENT = ClassUtils.isPresent(HTTP_CLIENT, null); + static boolean present(ClassLoader classLoader) { + return ClassUtils.isPresent(HTTP_CLIENT, classLoader); + } } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/JettyClientHttpRequestFactoryBuilder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/JettyClientHttpRequestFactoryBuilder.java index 25b63f9951..121847872c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/JettyClientHttpRequestFactoryBuilder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/http/client/JettyClientHttpRequestFactoryBuilder.java @@ -116,7 +116,9 @@ public final class JettyClientHttpRequestFactoryBuilder static final String HTTP_CLIENT = "org.eclipse.jetty.client.HttpClient"; - static final boolean PRESENT = ClassUtils.isPresent(HTTP_CLIENT, null); + static boolean present(ClassLoader classLoader) { + return ClassUtils.isPresent(HTTP_CLIENT, classLoader); + } } 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 23a1c38bea..e7490b10f1 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 @@ -91,7 +91,9 @@ public final class ReactorClientHttpRequestFactoryBuilder static final String HTTP_CLIENT = "reactor.netty.http.client.HttpClient"; - static final boolean PRESENT = ClassUtils.isPresent(HTTP_CLIENT, null); + static boolean present(ClassLoader classLoader) { + return ClassUtils.isPresent(HTTP_CLIENT, classLoader); + } }