Use bean class loader when detecting ClientHttpRequestFactoryBuilder
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run Details
Build and Deploy Snapshot / Trigger Docs Build (push) Blocked by required conditions Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[early-access:true toolchain:true version:24], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[early-access:true toolchain:true version:24], map[id:windows-latest name:Windows]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:17], map[id:windows-latest name:Windows]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:21], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:21], map[id:windows-latest name:Windows]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:22], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:false version:22], map[id:windows-latest name:Windows]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:${{ vars.UBUNTU_MEDIUM || 'ubuntu-latest' }} name:Linux]) (push) Waiting to run Details
CI / ${{ matrix.os.name}} | Java ${{ matrix.java.version}} (map[toolchain:true version:23], map[id:windows-latest name:Windows]) (push) Waiting to run Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:false version:17]) (push) Waiting to run Details
Run System Tests / Java ${{ matrix.java.version}} (map[toolchain:true version:21]) (push) Waiting to run Details

Update `ClientHttpRequestFactoryBuilder` with a detect method that
accepts a specific classloader and update `HttpClientAutoConfiguration`
to use it.

Fixes gh-44986
This commit is contained in:
Phillip Webb 2025-04-07 18:44:35 -07:00
parent 95325a87ba
commit 417a4a0386
8 changed files with 77 additions and 13 deletions

View File

@ -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);
}
/**

View File

@ -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<ClientHttpRequestFactoryBuilderCustomizer<?>> clientHttpRequestFactoryBuilderCustomizers) {
ClientHttpRequestFactoryBuilder<?> builder = httpClientProperties.factoryBuilder();
ClientHttpRequestFactoryBuilder<?> builder = httpClientProperties.factoryBuilder(this.beanClassLoader);
return customize(builder, clientHttpRequestFactoryBuilderCustomizers.orderedStream().toList());
}

View File

@ -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)

View File

@ -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<T extends ClientHttpRequestFact
* @return the most suitable {@link ClientHttpRequestFactoryBuilder} for the classpath
*/
static ClientHttpRequestFactoryBuilder<? extends ClientHttpRequestFactory> 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:
* <ol>
* <li>{@link #httpComponents()}</li>
* <li>{@link #jetty()}</li>
* <li>{@link #reactor()}</li>
* <li>{@link #jdk()}</li>
* <li>{@link #simple()}</li>
* </ol>
* @param classLoader the class loader to use for detection
* @return the most suitable {@link ClientHttpRequestFactoryBuilder} for the classpath
* @since 3.5.0
*/
static ClientHttpRequestFactoryBuilder<? extends ClientHttpRequestFactory> 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();

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}