You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

Spring Boot 3.x.x集成HttpClient4遇编译错误求助

在Spring Boot 3.x中集成Apache HttpClient4到RestTemplate的解决方案

问题描述

使用Spring Boot 3.4.1时,框架默认集成Apache HttpClient5,但需要自定义RestTemplate使用HttpClient4,配置后出现编译错误:HttpComponentsClientHttpRequestFactory的构造器参数要求为HttpClient5类型,而非HttpClient4的org.apache.http.client.HttpClient

已执行的操作:

  • pom.xml中排除HttpClient5依赖,引入HttpClient4及对应兼容版本的httpcore
  • 在主类中排除RestTemplateAutoConfigurationHttpClientAutoConfiguration自动配置类

核心原因

Spring Boot 3基于Spring 6,而Spring 6中的org.springframework.http.client.HttpComponentsClientHttpRequestFactory仅适配HttpClient5的API,不再支持HttpClient4的org.apache.http.client.HttpClient类型,导致参数类型不匹配。

解决方案

1. 彻底排除HttpClient5依赖

确保项目中无任何HttpClient5的依赖,可通过mvn dependency:tree命令检查依赖树,确认没有org.apache.httpcomponents:httpclient5的引用。

修改pom.xml,除了排除spring-boot-starter-web中的httpclient5,若引入了spring-boot-starter-httpclient也需一并排除:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient5</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- 若引入了starter-httpclient,添加以下排除配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-httpclient</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient5</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore</artifactId>
    <version>4.4.14</version>
</dependency>

2. 自定义适配HttpClient4的ClientHttpRequestFactory

由于Spring 6原生的HttpComponentsClientHttpRequestFactory不再支持HttpClient4,需自定义一个适配类:

import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.*;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;

public class HttpClient4RequestFactory extends AbstractClientHttpRequestFactoryWrapper {

    private final HttpClient httpClient;

    public HttpClient4RequestFactory(HttpClient httpClient) {
        super(null);
        this.httpClient = httpClient;
    }

    @Override
    protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory) throws IOException {
        HttpRequestBase httpRequest = switch (httpMethod) {
            case GET -> new HttpGet(uri);
            case POST -> new HttpPost(uri);
            case PUT -> new HttpPut(uri);
            case DELETE -> new HttpDelete(uri);
            case HEAD -> new HttpHead(uri);
            case OPTIONS -> new HttpOptions(uri);
            case PATCH -> new HttpPatch(uri);
            default -> throw new IllegalArgumentException("Unsupported HTTP method: " + httpMethod);
        };
        return new HttpClient4Request(httpClient, httpRequest);
    }

    private static class HttpClient4Request extends AbstractClientHttpRequest {

        private final HttpClient httpClient;
        private final HttpRequestBase httpRequest;

        public HttpClient4Request(HttpClient httpClient, HttpRequestBase httpRequest) {
            this.httpClient = httpClient;
            this.httpRequest = httpRequest;
        }

        @Override
        protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
            headers.forEach((key, values) -> httpRequest.setHeader(key, String.join(", ", values)));
            return new HttpClient4Response(httpClient.execute(httpRequest));
        }

        @Override
        public HttpMethod getMethod() {
            return HttpMethod.valueOf(httpRequest.getMethod());
        }

        @Override
        public URI getURI() {
            return httpRequest.getURI();
        }
    }

    private static class HttpClient4Response extends AbstractClientHttpResponse {

        private final org.apache.http.HttpResponse httpResponse;

        public HttpClient4Response(org.apache.http.HttpResponse httpResponse) {
            this.httpResponse = httpResponse;
        }

        @Override
        public int getRawStatusCode() throws IOException {
            return httpResponse.getStatusLine().getStatusCode();
        }

        @Override
        public String getStatusText() throws IOException {
            return httpResponse.getStatusLine().getReasonPhrase();
        }

        @Override
        public HttpHeaders getHeaders() {
            HttpHeaders headers = new HttpHeaders();
            httpResponse.getAllHeaders().forEach(header -> headers.add(header.getName(), header.getValue()));
            return headers;
        }

        @Override
        public InputStream getBody() throws IOException {
            return httpResponse.getEntity().getContent();
        }

        @Override
        public void close() {
            try {
                httpResponse.getEntity().getContent().close();
            } catch (IOException e) {
                // 忽略关闭异常
            }
        }
    }
}

3. 修改RestTemplate配置类

将原配置中的HttpComponentsClientHttpRequestFactory替换为自定义的HttpClient4RequestFactory

@Configuration
public class BlockingRestTemplateCustomizer {

    @Bean("restTemplate")
    public RestTemplate restTemplate() {
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        connectionManager.setMaxTotal(100);
        connectionManager.setDefaultMaxPerRoute(20);

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(3000)
                .build();

        HttpClient httpClient = HttpClients.custom()
                .setConnectionManager(connectionManager)
                .setDefaultRequestConfig(requestConfig)
                .build();

        // 使用自定义的RequestFactory适配HttpClient4
        ClientHttpRequestFactory factory = new HttpClient4RequestFactory(httpClient);
        return new RestTemplate(factory);
    }
}

4. 确认自动配置排除

确保主类中已排除相关自动配置,避免框架默认配置冲突:

@SpringBootApplication
@EnableAutoConfiguration(exclude = {
        RestTemplateAutoConfiguration.class,
        HttpClientAutoConfiguration.class
})
public class MsscBreweryClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(MsscBreweryClientApplication.class, args);
    }
}

说明

Spring官方在Spring 6及Spring Boot 3中已停止对HttpClient4的支持,推荐迁移至HttpClient5以获得更好的性能和长期维护性。若因特殊需求必须使用HttpClient4,可通过上述自定义适配类实现集成。

内容的提问来源于stack exchange,提问作者Neeraj

火山引擎 最新活动