基于 HttpClient 封装的 HTTP 请求工具类
一、人类写的
java
package com.sky.utils;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Http工具类
*/
public class HttpClientUtil {
static final int TIMEOUT_MSEC = 5 * 1000;
/**
* 发送GET方式请求
* @param url
* @param paramMap
* @return
*/
public static String doGet(String url,Map<String,String> paramMap){
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
String result = "";
CloseableHttpResponse response = null;
try{
URIBuilder builder = new URIBuilder(url);
if(paramMap != null){
for (String key : paramMap.keySet()) {
builder.addParameter(key,paramMap.get(key));
}
}
URI uri = builder.build();
//创建GET请求
HttpGet httpGet = new HttpGet(uri);
//发送请求
response = httpClient.execute(httpGet);
//判断响应状态
if(response.getStatusLine().getStatusCode() == 200){
result = EntityUtils.toString(response.getEntity(),"UTF-8");
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
response.close();
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
/**
* 发送POST方式请求
* @param url
* @param paramMap
* @return
* @throws IOException
*/
public static String doPost(String url, Map<String, String> paramMap) throws IOException {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
// 创建参数列表
if (paramMap != null) {
List<NameValuePair> paramList = new ArrayList();
for (Map.Entry<String, String> param : paramMap.entrySet()) {
paramList.add(new BasicNameValuePair(param.getKey(), param.getValue()));
}
// 模拟表单
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
httpPost.setEntity(entity);
}
httpPost.setConfig(builderRequestConfig());
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
} catch (Exception e) {
throw e;
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
/**
* 发送POST方式请求
* @param url
* @param paramMap
* @return
* @throws IOException
*/
public static String doPost4Json(String url, Map<String, String> paramMap) throws IOException {
// 创建Httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
CloseableHttpResponse response = null;
String resultString = "";
try {
// 创建Http Post请求
HttpPost httpPost = new HttpPost(url);
if (paramMap != null) {
//构造json格式数据
JSONObject jsonObject = new JSONObject();
for (Map.Entry<String, String> param : paramMap.entrySet()) {
jsonObject.put(param.getKey(),param.getValue());
}
StringEntity entity = new StringEntity(jsonObject.toString(),"utf-8");
//设置请求编码
entity.setContentEncoding("utf-8");
//设置数据类型
entity.setContentType("application/json");
httpPost.setEntity(entity);
}
httpPost.setConfig(builderRequestConfig());
// 执行http请求
response = httpClient.execute(httpPost);
resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
} catch (Exception e) {
throw e;
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return resultString;
}
private static RequestConfig builderRequestConfig() {
return RequestConfig.custom()
.setConnectTimeout(TIMEOUT_MSEC)
.setConnectionRequestTimeout(TIMEOUT_MSEC)
.setSocketTimeout(TIMEOUT_MSEC).build();
}
}
二、AI 建议
这个工具类可以用 ,特别是对于学习阶段 、简单的脚本任务 或者并发量极低的内部工具来说,它是完全合格的。
但是,如果你打算将它应用在生产环境 (特别是高并发、微服务架构)中,这个版本存在几个严重的性能隐患。
为了让你从"能用"进阶到"好用",我为你详细分析一下这段代码的优缺点,以及如何改进。
✅ 优点(做得好的地方)
- 功能完整:涵盖了 GET、POST(表单)、POST(JSON)三种最常用的场景。
- JSON 处理 :
doPost4Json方法利用 FastJson 手动构建 JSON 字符串,解决了原生 HttpClient 处理 JSON 不够直观的问题。 - 超时配置 :
builderRequestConfig方法设置了连接超时和读取超时,这是一个非常好的习惯,防止程序因为网络卡顿而无限挂起。 - 编码处理 :显式指定了
UTF-8,避免了中文乱码问题。
⚠️ 核心隐患(生产环境不可用原因)
这个工具类最大的问题在于 CloseableHttpClient 的创建方式。
1. 每次请求都创建新客户端(性能杀手)
在你的代码中:
java
// 每次调用 doGet 或 doPost 时,都会执行这一行
CloseableHttpClient httpClient = HttpClients.createDefault();
- 后果 :
HttpClients.createDefault()会创建一个新的连接管理器。虽然你最后调用了httpClient.close(),但频繁地创建和销毁 TCP 连接(包括 DNS 解析、三次握手、SSL 握手等)是非常消耗资源的。 - 生产环境表现 :在高并发下,服务器会出现大量的
TIME_WAIT状态连接,导致端口耗尽,响应速度极慢,甚至抛出ConnectionPoolTimeoutException。
2. 缺乏连接池(Connection Pool)
- 后果:没有复用连接。HTTP 协议的一个巨大优势是"长连接"(Keep-Alive),即建立一次连接可以发送多次请求。你的代码每次请求完就彻底断开,浪费了这一特性。
3. 异常处理略显粗糙
- 代码 :
catch (Exception e) { e.printStackTrace(); } - 问题 :在生产环境中,
e.printStackTrace()只是打印到控制台,日志系统(如 Logback/Log4j)通常抓取不到这些错误。应该使用日志框架(如log.error(...))来记录。
🚀 改进建议(如何升级为生产级)
要让这个工具类变得"专业",只需要做一个核心改动 :将 HttpClient 变为静态单例,并配置连接池。
以下是基于你原有代码的优化版本(保留了你的逻辑风格,只改了架构):
java
package com.sky.utils;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; // 引入连接池
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HttpClientUtil {
// 1. 使用 Logger 替代 printStackTrace
private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);
// 2. 定义全局唯一的 HttpClient 实例 (单例)
private static final CloseableHttpClient httpClient;
// 3. 静态代码块初始化,配置连接池
static {
// 创建连接池管理器
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(50); // 最大连接数
cm.setDefaultMaxPerRoute(10); // 每个路由的最大连接数
// 构建 HttpClient,注入连接池
httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
}
static final int TIMEOUT_MSEC = 5 * 1000;
/**
* 发送GET方式请求
*/
public static String doGet(String url, Map<String, String> paramMap) {
CloseableHttpResponse response = null;
String result = "";
try {
URIBuilder builder = new URIBuilder(url);
if (paramMap != null) {
for (String key : paramMap.keySet()) {
builder.addParameter(key, paramMap.get(key));
}
}
URI uri = builder.build();
HttpGet httpGet = new HttpGet(uri);
httpGet.setConfig(builderRequestConfig()); // 记得把超时配置也加上
// 4. 使用全局的 httpClient 执行请求,而不是新建的
response = httpClient.execute(httpGet);
if (response.getStatusLine().getStatusCode() == 200) {
result = EntityUtils.toString(response.getEntity(), "UTF-8");
}
} catch (Exception e) {
logger.error("GET请求异常: {}", url, e); // 记录日志
} finally {
// 5. 只关闭 response,不要关闭 httpClient,否则连接池就废了
if (response != null) {
try {
response.close();
} catch (IOException e) {
logger.error("关闭响应流异常", e);
}
}
}
return result;
}
// doPost 和 doPost4Json 方法同理:
// 1. 删掉 CloseableHttpClient httpClient = HttpClients.createDefault();
// 2. 直接使用类变量 httpClient.execute(httpPost);
// 3. finally 块中只关闭 response
// ... 其他方法保持不变,只需替换 httpClient 的获取方式 ...
private static RequestConfig builderRequestConfig() {
return RequestConfig.custom()
.setConnectTimeout(TIMEOUT_MSEC)
.setConnectionRequestTimeout(TIMEOUT_MSEC)
.setSocketTimeout(TIMEOUT_MSEC).build();
}
}
📌 总结
- 学习用 :你现在的代码完全没问题,逻辑清晰,有助于理解 HTTP 请求的底层流程(构建 URL -> 构建请求 -> 执行 -> 处理响应 -> 关闭资源)。
- 工作用 :请务必采用连接池 模式(即上面的改进版)。在面试或实际开发中,如果能说出"我知道
HttpClients.createDefault()每次创建新连接会有性能问题,所以我使用了PoolingHttpClientConnectionManager",这会是一个非常加分的亮点。
三、AI 写的
在 Java 后端开发中,直接使用原生的 HttpURLConnection 往往代码繁琐且难以维护。基于 Apache HttpClient 封装一个通用的工具类(HttpClientUtil)是业界的最佳实践。
我为你整理了一个生产级 的封装方案。这个方案重点解决了连接池管理 (避免频繁创建销毁连接)、超时控制 以及JSON 数据处理等核心问题。
🚀 核心设计思路
在编写代码之前,我们需要明确这个工具类的设计原则,以确保其在高并发场景下的稳定性:
- 单例模式与连接池 :
HttpClient实例应当是单例的,并配置PoolingHttpClientConnectionManager。这能复用 TCP 连接,大幅提升性能。 - 自动资源释放 :利用
try-with-resources语法糖,确保CloseableHttpResponse和流被正确关闭,防止内存泄漏。 - 灵活的数据支持 :同时支持
Map参数(表单提交)和Object/JSON(RESTful 接口)。 - 完善的异常处理:统一处理超时、状态码错误,并记录日志。
🛠️ 完整工具类代码实现
你可以直接参考以下代码结构。该类封装了 GET、POST、PUT、DELETE 方法,并内置了连接池配置。
java
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;
/**
* 基于 Apache HttpClient 的 HTTP 请求工具类
* 特点:支持连接池、自动序列化 JSON、超时控制
*/
public class HttpClientUtil {
private static final Logger logger = LoggerFactory.getLogger(HttpClientUtil.class);
// 全局单例 HttpClient(带连接池)
private static final CloseableHttpClient httpClient;
private static final ObjectMapper objectMapper = new ObjectMapper();
static {
// 1. 配置连接池
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
cm.setMaxTotal(200); // 最大连接数
cm.setDefaultMaxPerRoute(20); // 每个路由的最大连接数
// 2. 配置请求参数(超时时间)
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(5000) // 连接建立超时:5秒
.setSocketTimeout(10000) // 数据传输超时:10秒
.setConnectionRequestTimeout(5000) // 从连接池获取连接超时:5秒
.build();
// 3. 创建 HttpClient 实例
httpClient = HttpClients.custom()
.setConnectionManager(cm)
.setDefaultRequestConfig(requestConfig)
.evictExpiredConnections() // 自动关闭过期连接
.evictIdleConnections(60, java.util.concurrent.TimeUnit.SECONDS) // 自动关闭空闲连接
.build();
}
// 私有构造,禁止实例化
private HttpClientUtil() {}
// ==================== GET 请求 ====================
public static String doGet(String url) throws IOException {
return doGet(url, null, null);
}
public static String doGet(String url, Map<String, String> params) throws IOException {
return doGet(url, params, null);
}
public static String doGet(String url, Map<String, String> params, Map<String, String> headers) throws IOException {
URIBuilder uriBuilder = new URIBuilder(url);
if (params != null) {
params.forEach(uriBuilder::setParameter);
}
HttpGet httpGet = new HttpGet(uriBuilder.build());
addHeaders(httpGet, headers);
return executeRequest(httpGet);
}
// ==================== POST 请求 (JSON) ====================
public static String doPostJson(String url, Object body) throws IOException {
return doPostJson(url, body, null);
}
public static String doPostJson(String url, Object body, Map<String, String> headers) throws IOException {
HttpPost httpPost = new HttpPost(url);
// 自动将对象序列化为 JSON 字符串
String jsonStr = objectMapper.writeValueAsString(body);
StringEntity entity = new StringEntity(jsonStr, ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8));
httpPost.setEntity(entity);
// 默认添加 Content-Type,除非用户自定义覆盖
if (headers != null && !headers.containsKey("Content-Type")) {
httpPost.setHeader("Content-Type", "application/json; charset=utf-8");
}
addHeaders(httpPost, headers);
return executeRequest(httpPost);
}
// ==================== POST 请求 (表单) ====================
public static String doPostForm(String url, Map<String, String> params) throws IOException {
HttpPost httpPost = new HttpPost(url);
if (params != null) {
List<NameValuePair> nvps = new ArrayList<>();
params.forEach((k, v) -> nvps.add(new BasicNameValuePair(k, v)));
httpPost.setEntity(new UrlEncodedFormEntity(nvps, StandardCharsets.UTF_8));
}
return executeRequest(httpPost);
}
// ==================== PUT / DELETE 请求 ====================
public static String doPutJson(String url, Object body) throws IOException {
HttpPut httpPut = new HttpPut(url);
String jsonStr = objectMapper.writeValueAsString(body);
httpPut.setEntity(new StringEntity(jsonStr, ContentType.APPLICATION_JSON));
return executeRequest(httpPut);
}
public static String doDelete(String url) throws IOException {
return executeRequest(new HttpDelete(url));
}
// ==================== 核心执行逻辑 ====================
private static String executeRequest(HttpRequestBase request) throws IOException {
try (CloseableHttpResponse response = httpClient.execute(request)) {
int statusCode = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
String result = entity != null ? EntityUtils.toString(entity, StandardCharsets.UTF_8) : null;
if (statusCode >= 200 && statusCode < 300) {
logger.debug("请求成功: {}, 响应: {}", request.getURI(), result);
return result;
} else {
logger.error("请求失败: {}, 状态码: {}, 响应: {}", request.getURI(), statusCode, result);
throw new IOException("HTTP Error: " + statusCode);
}
}
}
private static void addHeaders(HttpRequestBase request, Map<String, String> headers) {
if (headers != null) {
headers.forEach(request::setHeader);
}
}
}
📊 关键功能点解析
为了让你更清楚如何使用和维护这个工具类,我总结了以下几个关键点:
1. 连接池配置 (Performance)
在静态代码块中,我们初始化了 PoolingHttpClientConnectionManager。
- 为什么要这样做? 如果不使用连接池,每次请求都会重新建立 TCP 三次握手,对于高并发系统来说,这是巨大的性能浪费。
- 配置建议:
setMaxTotal通常设置为 CPU 核数 * 2 + 磁盘数,或者根据压测结果调整(示例中设为 200)。
2. 超时设置 (Stability)
代码中设置了三种超时时间:
- Connect Timeout (5s):建立连接的超时。如果服务器宕机或网络不通,快速失败。
- Socket Timeout (10s):数据传输超时。防止服务器处理时间过长导致线程阻塞。
- Connection Request Timeout (5s):从连接池获取连接的超时。防止连接池耗尽时线程无限等待。
3. JSON 自动序列化 (Usability)
利用 Jackson 的 ObjectMapper,我们在 doPostJson 方法中直接接收 Object 类型参数。
- 优势 :调用方不需要手动
JSON.toJSONString(obj),直接传实体类或 Map 即可,代码更清爽。
📝 使用示例
封装完成后,调用变得非常简单:
场景 1:发送 JSON 数据 (最常见)
java
// 定义请求体对象
Map<String, Object> params = new HashMap<>();
params.put("username", "admin");
params.put("password", "123456");
try {
// 一行代码完成 POST 请求
String response = HttpClientUtil.doPostJson("https://api.example.com/login", params);
System.out.println("登录结果: " + response);
} catch (IOException e) {
e.printStackTrace();
}
场景 2:带请求头的 GET 请求
java
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer token123456");
try {
String userInfo = HttpClientUtil.doGet("https://api.example.com/user/info", null, headers);
System.out.println(userInfo);
} catch (IOException e) {
// 处理异常
}
⚠️ 注意事项
- 依赖引入 :确保你的
pom.xml中包含了httpclient和jackson依赖。 - 线程安全 :上述工具类中的
httpClient是线程安全的,可以在多线程环境下共享。 - 字符集 :代码中显式指定了
StandardCharsets.UTF_8,避免出现中文乱码问题。
这个封装方案既保留了 Apache HttpClient 的强大功能,又屏蔽了底层的复杂性,非常适合在企业级项目中使用。