杰瑞科技汇

Java调用HttpClient,如何正确实现与优化?

目录

  1. 为什么选择 HttpClient? (与其他方案如 HttpURLConnection 的对比)
  2. 环境搭建 (Maven/Gradle 依赖)
  3. 核心组件简介
  4. 实战演练
    • 示例 1: 发送一个简单的 GET 请求
    • 示例 2: 发送一个 POST 请求 (发送 JSON 数据)
    • 示例 3: 发送一个 POST 请求 (发送表单数据)
    • 示例 4: 处理响应和错误
  5. 高级特性
    • 连接池
    • 异步请求
    • 超时设置
  6. 最佳实践与总结

为什么选择 HttpClient?

相比于 Java 原生的 HttpURLConnection,Apache HttpClient 有以下优势:

Java调用HttpClient,如何正确实现与优化?-图1
(图片来源网络,侵删)
  • API 更强大、更灵活:提供了更丰富的 API,可以轻松处理各种 HTTP 请求、响应、头部、Cookie 等。
  • 功能更全面:内置了连接池、重试机制、压缩支持、身份验证等高级功能,无需自己实现。
  • 性能更好:通过连接池可以高效地复用 TCP 连接,避免了频繁创建和销毁连接的开销。
  • 社区活跃,维护良好:由 Apache 软件基金会维护,是目前事实上的 Java HTTP 客户端标准之一。

环境搭建

你需要在你的项目中添加 HttpClient 的依赖。

使用 Maven

在你的 pom.xml 文件中添加以下依赖:

<dependencies>
    <!-- Apache HttpClient 核心库 -->
    <dependency>
        <groupId>org.apache.httpcomponents.client5</groupId>
        <artifactId>httpclient5</artifactId>
        <version>5.3.1</version> <!-- 请使用最新版本 -->
    </dependency>
    <!-- 为了支持 JSON,我们通常需要一个 JSON 库,如 Jackson -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.15.2</version> <!-- 请使用最新版本 -->
    </dependency>
</dependencies>

使用 Gradle

在你的 build.gradle 文件中添加以下依赖:

dependencies {
    // Apache HttpClient 核心库
    implementation 'org.apache.httpcomponents.client5:httpclient5:5.3.1' // 请使用最新版本
    // 为了支持 JSON,我们通常需要一个 JSON 库,如 Jackson
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2' // 请使用最新版本
}

核心组件简介

在开始编码前,了解几个核心组件很重要:

Java调用HttpClient,如何正确实现与优化?-图2
(图片来源网络,侵删)
  • CloseableHttpClient: 执行 HTTP 请求的主要入口点,它是一个客户端对象,负责管理连接和执行请求,使用后需要关闭。
  • HttpUriRequest: 表示一个 HTTP 请求的接口,常用的实现类有 HttpGet, HttpPost, HttpPut, HttpDelete 等。
  • HttpResponse: 表示从服务器收到的 HTTP 响应,它包含状态码、响应头和响应体。
  • EntityUtils: 一个工具类,用于从 HttpEntity (响应体) 中读取内容并转换为字符串或字节数组。

实战演练

示例 1: 发送一个简单的 GET 请求

这个例子会向 httpbin.org/get 发送一个 GET 请求,并打印响应内容。

import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.ParseException;
public class SimpleGetRequest {
    public static void main(String[] args) {
        // 1. 创建 HttpClient 实例
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 2. 创建 HttpGet 请求
            HttpGet request = new HttpGet("https://httpbin.org/get?name=test&age=30");
            // 3. 执行请求,获取响应
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                // 4. 检查响应状态码
                System.out.println("Response Code: " + response.getCode());
                // 5. 获取响应实体
                String responseBody = EntityUtils.toString(response.getEntity());
                // 6. 打印响应内容
                System.out.println("Response Body:");
                System.out.println(responseBody);
            } catch (ParseException | IOException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

关键点:

  • 使用 try-with-resources 语句 (try (CloseableHttpClient ...)try (CloseableHttpResponse ...)) 来确保 HttpClientHttpResponse 在使用后被正确关闭,防止资源泄漏。
  • EntityUtils.toString(response.getEntity()) 将响应体(通常是字节流)转换为字符串。

示例 2: 发送一个 POST 请求 (发送 JSON 数据)

这个例子会向 httpbin.org/post 发送一个 POST 请求,请求体是 JSON 格式。

import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.ParseException;
public class PostJsonRequest {
    public static void main(String[] args) {
        // 1. 创建 HttpClient 实例
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 2. 创建 HttpPost 请求
            HttpPost postRequest = new HttpPost("https://httpbin.org/post");
            // 3. 准备 JSON 数据
            ObjectMapper objectMapper = new ObjectMapper();
            User user = new User("Alice", 25);
            String jsonBody = objectMapper.writeValueAsString(user);
            // 4. 设置请求体和内容类型
            StringEntity entity = new StringEntity(jsonBody, ContentType.APPLICATION_JSON);
            postRequest.setEntity(entity);
            // 5. 执行请求
            try (CloseableHttpResponse response = httpClient.execute(postRequest)) {
                System.out.println("Response Code: " + response.getCode());
                String responseBody = EntityUtils.toString(response.getEntity());
                System.out.println("Response Body:");
                System.out.println(responseBody);
            } catch (ParseException | IOException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    // 用于 JSON 序列化的简单 POJO
    static class User {
        private String name;
        private int age;
        public User() {}
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
        // Getters and Setters (省略,实际项目中需要)
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        public int getAge() { return age; }
        public void setAge(int age) { this.age = age; }
    }
}

关键点:

Java调用HttpClient,如何正确实现与优化?-图3
(图片来源网络,侵删)
  • HttpPost 用于创建 POST 请求。
  • StringEntity 用于将字符串(如 JSON)包装成一个 HTTP 实体。
  • ContentType.APPLICATION_JSON 明确告诉服务器我们发送的是 JSON 数据。
  • 使用 Jackson 的 ObjectMapper 将 Java 对象轻松转换为 JSON 字符串。

示例 3: 发送一个 POST 请求 (发送表单数据)

这个例子演示如何向 httpbin.org/post 发送 application/x-www-form-urlencoded 格式的表单数据。

import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.apache.hc.core5.net.URIBuilder;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
public class PostFormRequest {
    public static void main(String[] args) throws URISyntaxException {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 1. 创建 HttpPost 请求,并可以构建带查询参数的 URL
            URI uri = new URIBuilder("https://httpbin.org/post")
                    .addParameter("param1", "value1")
                    .build();
            HttpPost postRequest = new HttpPost(uri);
            // 2. 准备表单数据
            List<BasicNameValuePair> formParams = new ArrayList<>();
            formParams.add(new BasicNameValuePair("username", "john.doe"));
            formParams.add(new BasicNameValuePair("password", "secret123"));
            // 3. 设置请求体
            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(formParams);
            postRequest.setEntity(formEntity);
            // 4. 执行请求
            try (CloseableHttpResponse response = httpClient.execute(postRequest)) {
                System.out.println("Response Code: " + response.getCode());
                String responseBody = EntityUtils.toString(response.getEntity());
                System.out.println("Response Body:");
                System.out.println(responseBody);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

关键点:

  • UrlEncodedFormEntity 用于创建表单实体。
  • BasicNameValuePair 表示一个键值对。
  • URIBuilder 是一个方便的工具,用于构建复杂的 URL,包括查询参数。

示例 4: 处理响应和错误

处理 HTTP 响应时,状态码非常重要。

// ... (创建 HttpClient 和 Request 的代码相同)
try (CloseableHttpResponse response = httpClient.execute(request)) {
    int statusCode = response.getCode();
    if (statusCode >= 200 && statusCode < 300) {
        // 请求成功
        String responseBody = EntityUtils.toString(response.getEntity());
        System.out.println("Success: " + responseBody);
    } else {
        // 请求失败
        System.err.println("Request failed with status code: " + statusCode);
        // 可以读取错误响应体
        String errorBody = EntityUtils.toString(response.getEntity());
        System.err.println("Error Body: " + errorBody);
    }
} // ...

高级特性

连接池

在高并发场景下,为每个请求都创建一个 HttpClient 是非常低效的,连接池可以复用 TCP 连接,显著提升性能。

import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.pool.PoolStats;
import org.apache.hc.core5.reactor.ConnectingIOReactor;
import org.apache.hc.core5.reactor.ConnectingIOReactorFactory;
import org.apache.hc.core5.reactor.IOReactorConfig;
// 创建带连接池的 HttpClient
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// 设置最大连接数
cm.setMaxTotal(100);
// 设置每个路由(目标主机)的最大连接数
cm.setDefaultMaxPerRoute(20);
// 创建 HttpClient
CloseableHttpClient httpClient = HttpClients.custom()
        .setConnectionManager(cm)
        .build();
// 使用 httpClient ...
// 使用完后关闭
httpClient.close();
// 注意:连接池本身也会被关闭

异步请求

HttpClient 5.x 提供了强大的异步非阻塞 API,基于 Java NIO 和 CompletableFuture

import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
public class AsyncRequestExample {
    public static void main(String[] args) throws Exception {
        // 1. 创建异步客户端
        try (CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault()) {
            // 2. 启动客户端
            httpClient.start();
            // 3. 创建异步请求
            SimpleHttpRequest request = SimpleRequestBuilder.get("https://httpbin.org/get")
                    .addParameter("name", "async")
                    .build();
            // 4. 执行异步请求,并提供回调
            httpClient.execute(request, new FutureCallback<SimpleHttpResponse>() {
                @Override
                public void completed(SimpleHttpResponse response) {
                    System.out.println("Async Response Code: " + response.getCode());
                    System.out.println("Async Response Body: " + response.getBodyText());
                }
                @Override
                public void failed(Exception ex) {
                    System.err.println("Async request failed: " + ex.getMessage());
                }
                @Override
                public void cancelled() {
                    System.err.println("Async request was cancelled.");
                }
            });
            // 5. 为了让主线程等待异步任务完成,可以睡一会儿
            // 在实际应用中,主线程通常有自己的事件循环
            System.out.println("Request sent. Waiting for response...");
            Thread.sleep(5000); // 等待5秒
        }
    }
}

超时设置

为了避免因网络问题或服务器无响应而无限期等待,设置超时非常重要。

import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.io.config.ConnectionRequestConfig;
RequestConfig requestConfig = RequestConfig.custom()
        .setConnectTimeout(5000)     // 连接超时 (毫秒)
        .setConnectionRequestTimeout(3000) // 从连接池获取连接的超时
        .setResponseTimeout(10000)   // 等待响应的超时
        .build();
CloseableHttpClient httpClient = HttpClients.custom()
        .setDefaultRequestConfig(requestConfig)
        .build();

最佳实践与总结

  1. 总是使用 try-with-resources:确保 CloseableHttpClientCloseableHttpResponse 被正确关闭,防止资源泄漏。
  2. 重用 HttpClient 实例:不要为每个请求都创建一个新的 HttpClient,它应该是线程安全的,可以在整个应用生命周期内被重用。
  3. 使用连接池:对于任何生产环境或高并发场景,务必使用连接池来提升性能。
  4. 设置合理的超时:为连接、请求和响应设置合理的超时时间,使你的应用更加健壮。
  5. 处理响应状态码:不要只假设请求会成功,检查响应状态码并处理可能的错误。
  6. 考虑使用更高级的封装:对于复杂的业务逻辑,可以考虑基于 HttpClient 封装一个更简洁、更符合你项目风格的客户端工具类。

希望这份详细的指南能帮助你掌握在 Java 中使用 HttpClient 进行 HTTP 调用!

分享:
扫描分享到社交APP
上一篇
下一篇