概述
在Java应用开发中,HTTP客户端是常见的组件之一。本文将分享一个HTTP工具类的优化过程,通过代码重构提升性能、可维护性和稳定性。我们将分析问题,并逐步介绍优化方案。
原代码
贴上代码,看看问题🧐
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
public class HttpUtil {
private static final Logger log = LoggerFactory.getLogger(HttpUtil.class);
public static String get(HttpArgs httpArgs) {
for (int i = 1; i <= 3; i++) {
try {
// 不报错就直接返回,报错就重试两次
return sendGet(httpArgs);
} catch (Exception o_O) {
log.info("第{}次请求失败", i, o_O);
try {
TimeUnit.SECONDS.sleep(10);
} catch (Exception ignored) {
}
}
}
return null;
}
private static String sendGet(HttpArgs httpArgs) throws Exception {
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // 指定 HTTP 协议版本 (HTTP_1_1 或 HTTP_2)
.connectTimeout(Duration.ofSeconds(30)) // 设置连接超时时间
.followRedirects(HttpClient.Redirect.NORMAL) // 设置重定向策略 (ALWAYS, NEVER, NORMAL)
.build();
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(httpArgs.getUrl())) // 设置目标 URI
.timeout(Duration.ofSeconds(30));
Map<String, String> headerMap = httpArgs.getHeaderMap();
for (String key : headerMap.keySet()) {
String value = headerMap.get(key);
builder.header(key, value);
}
HttpRequest request = builder.GET().build();
log.info("请求接口,{},{}", httpArgs, request);
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
String result = null;
if (response.body() != null) {
result = response.body();
}
log.info("请求结果 statusCode:{} uri:{} version:{} result:{}", response.statusCode(), response.uri(), response.version(), Optional.ofNullable(result).orElse("").substring(0, 300));
return result;
}
// 测试
public static void main(String[] args) {
HttpArgs httpArgs = new HttpArgs();
httpArgs.setUrl("https://m.cls.cn/nodeapi/telegraphs");
String s = HttpUtil.get(httpArgs);
System.out.println(s);
}
}
代码分析
- HttpClient重复创建:每次请求都创建新的HttpClient实例,造成资源浪费
- 参数处理不足:没有对查询参数进行编码处理
- 异常处理不完善:线程中断处理不当
- 性能优化不足:缺乏连接池和线程池配置
优化方案
HttpClient单例化
java
// 单例可重用的HttpClient
private volatile static HttpClient client;
private static HttpClient getClient() {
if (client == null) {
synchronized (HttpUtil.class) {
if (client == null) {
client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(30))
.followRedirects(HttpClient.Redirect.NORMAL)
.executor(Executors.newFixedThreadPool(5)) // 使用有界线程池
.build();
}
}
}
return client;
}
优化点:
- 使用双重检查锁定实现线程安全的单例模式
- 添加线程池配置提高并发处理能力
- 避免频繁创建HttpClient带来的性能开销
查询参数编码处理
java
String query = httpArgs.getParamMap().entrySet().stream()
.map(entry -> entry.getKey() + "=" + URLEncoder.encode(
String.valueOf(Optional.ofNullable(entry.getValue()).orElse("")),
StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
优化点:
- 使用URLEncoder确保参数正确编码
- 使用流式API简化代码
- 处理null值避免空指针异常
改进异常处理
java
try {
TimeUnit.SECONDS.sleep(10);
} catch (Exception ignored) {
// 多线程环境中断恢复后,通过调用interrupt()恢复中断状态
Thread.currentThread().interrupt();
}
优化点:
- 正确处理线程中断状态
- 符合多线程开发规范😀
简化Header处理
java
// 使用Lambda表达式简化代码
httpArgs.getHeaderMap().forEach(builder::header);
优化点:
- 使用方法引用替代传统循环
- 代码更简洁易读
完整优化后代码
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 网络请求工具
* 注意:该工具会把请求异常会当作空值返回
*/
public class HttpUtil {
private static final Logger log = LoggerFactory.getLogger(HttpUtil.class);
// 单例可重用的HttpClient
private volatile static HttpClient client;
private static HttpClient getClient() {
if (client == null) {
synchronized (HttpUtil.class) {
if (client == null) {
client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(30))
.followRedirects(HttpClient.Redirect.NORMAL)
.executor(Executors.newFixedThreadPool(5))
.build();
}
}
}
return client;
}
public static String get(HttpArgs httpArgs) {
for (int i = 1; i <= 3; i++) {
try {
return sendGet(httpArgs);
} catch (Exception o_O) {
log.info("第{}次请求失败", i, o_O);
try {
TimeUnit.SECONDS.sleep(10);
} catch (Exception ignored) {
Thread.currentThread().interrupt();
}
}
}
return null;
}
private static String sendGet(HttpArgs httpArgs) throws Exception {
String query = httpArgs.getParamMap().entrySet().stream()
.map(entry -> entry.getKey() + "=" + URLEncoder.encode(
String.valueOf(Optional.ofNullable(entry.getValue()).orElse("")),
StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
HttpRequest.Builder builder = HttpRequest.newBuilder()
.uri(URI.create(httpArgs.getUrl() + "?" + query))
.timeout(Duration.ofSeconds(30));
httpArgs.getHeaderMap().forEach(builder::header);
HttpRequest request = builder.GET().build();
log.info("请求接口,{},{}", httpArgs, request);
HttpResponse<String> response = getClient().send(request, HttpResponse.BodyHandlers.ofString());
String result = response.body();
log.info("请求结果 statusCode:{} uri:{} version:{} result:{}",
response.statusCode(), response.uri(), response.version(),
StringUtil.emptySubString(result, 0, 300));
return result;
}
// 测试方法
public static void main(String[] args) {
HttpArgs httpArgs = new HttpArgs();
httpArgs.setUrl("https://m.cls.cn/nodeapi/telegraphs");
String s = HttpUtil.get(httpArgs);
System.out.println(s);
}
}
兄弟们,觉得不错就点波关注😘