OkHttp3.X 工具类封装:链式调用,支持HTTPS、重试、文件上传
基于OkHttp3.X封装,提供链式调用API,简化GET/POST请求,支持HTTPS、自动重试、文件上传等功能,提升开发效率。
在 Android 和 Java 开发中,OkHttp 是最常用的网络请求库之一。为了简化日常开发中的重复配置,提升开发效率,本文将分享一个线程安全、支持默认与自定义配置、链式调用的 OkHttp 工具类,并详细解析其核心功能与实现细节。
一、工具类核心优势
- 双重配置模式
- 默认配置:预定义通用参数(超时时间、连接池、重试策略),适用于大多数场景。
- 自定义配置:基于默认配置扩展,支持添加拦截器、修改超时参数、配置 HTTPS 证书等。
- 线程安全
- 使用双重检查锁定实现单例,确保多线程环境下实例唯一。
- 链式请求构建
- 支持 GET/POST 请求,灵活拼接 URL 参数、请求头、请求体(FormData、JSON、二进制等)。
- HTTPS 安全适配
- 支持调试模式(跳过证书验证)、自定义证书(自签名证书)、系统默认证书三种模式。
- 统一异常处理
- 自动关闭响应体,封装非 2xx 状态码为
HttpException
,简化错误处理逻辑。
- 自动关闭响应体,封装非 2xx 状态码为
二、快速开始
1. 引入依赖
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
2. 工具类核心功能
2.1 GET请求
// 带URL参数和Headers
String result = OkHttp_Util.get("https://api.example.com")
.addUrlParam("page", "1")
.addHeader("Authorization", "Bearer token123")
.execute_Pro();
// 批量添加参数
Map<String, String> params = new HashMap<>();
params.put("key1", "value1");
params.put("key2", "value2");
String result = OkHttp_Util.get("https://api.example.com")
.addUrlParam(params)
.execute_Pro();
2.2 POST请求
// 提交JSON
String json = "{\"name\":\"Dolphin\",\"age\":25}";
String result = OkHttp_Util.post("https://api.example.com")
.jsonBody(json)
.execute_Pro();
// FormData表单
Map<String, String> formData = new HashMap<>();
formData.put("username", "admin");
formData.put("password", "123456");
String result = OkHttp_Util.post("https://api.example.com")
.formData(formData)
.execute_Pro();
// 文件上传
File file = new File("avatar.jpg");
MediaType mediaType = MediaType.parse("image/jpeg");
String result = OkHttp_Util.post("https://api.upload.com")
.formData("file", file, mediaType)
.execute_Pro();
2.3 自定义配置
// 创建自定义Client(添加拦截器+长超时)
OkHttpClient.Builder builder = OkHttp_Util.customBuilder()
.addInterceptor(new OkHttp_Util.LoggingInterceptor())
.connectTimeout(30, TimeUnit.SECONDS);
OkHttp_Util.buildCustomInstance(builder);
OkHttpClient customClient = OkHttp_Util.getCustomInstance();
// 使用自定义Client发起请求
String result = OkHttp_Util.get("https://api.example.com", customClient)
.addUrlParam("debug", "true")
.execute_Pro();
2.4 异步请求
OkHttp_Util.get("https://api.example.com")
.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
String body = OkHttp_Util.parseResponse(response);
// 处理响应
}
@Override
public void onFailure(Call call, IOException e) {
// 处理失败
}
});
三、核心设计解析
3.1 单例模式管理
// 默认配置单例
private static volatile OkHttpClient defaultInstance;
// 自定义配置单例
private static volatile OkHttpClient customInstance;
public static OkHttpClient getDefaultInstance() {
if (defaultInstance == null) {
synchronized (OkHttp_Util.class) {
if (defaultInstance == null) {
defaultInstance = defaultBuilder().build();
}
}
}
return defaultInstance;
}
3.2 HTTPS安全配置
支持三种模式:
- 调试模式:跳过证书验证(仅测试环境)
- 自定义证书:指定PEM证书
- 系统默认:使用系统CA证书
private static OkHttpClient.Builder configSSL(OkHttpClient.Builder builder, OkHttpConfig config) {
// 调试模式:信任所有证书
if (config.debugMode) {
builder.sslSocketFactory(createInsecureSocketFactory(), new TrustAllManager());
}
// 自定义证书
else if (config.trustedCertificates != null) {
X509TrustManager trustManager = createCustomTrustManager(config.trustedCertificates);
builder.sslSocketFactory(trustManager.getSocketFactory(), trustManager);
}
// 系统默认证书
else {
X509TrustManager trustManager = getSystemTrustManager();
builder.sslSocketFactory(trustManager.getSocketFactory(), trustManager);
}
return builder;
}
3.3 重试拦截器
private static class RetryInterceptor implements Interceptor {
private final int maxRetries;
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
IOException exception = null;
for (int i = 0; i <= maxRetries; i++) {
try {
Response response = chain.proceed(request);
if (response.isSuccessful()) return response;
} catch (IOException e) {
exception = e;
}
}
throw exception != null ? exception : new IOException("请求失败");
}
}
四、注意事项
- HTTPS安全:生产环境务必使用系统证书或自定义证书
- 资源释放:同步请求自动关闭响应体,异步需手动关闭
- 超时配置:根据业务需求调整默认超时时间
- 线程安全:OkHttpClient实例线程安全,建议复用
- 异常处理:使用
execute_Pro()
自动处理非200响应
五、完整代码
package com.dolphin.util;
import okhttp3.*;
import javax.net.ssl.*;
import java.io.File;
import java.io.IOException;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* OkHttp 工具类(线程安全)
* <p>
* 提供两种 OkHttpClient 实例管理模式:
* <ul>
* <li>【默认配置】预定义通用网络参数,适用于大多数场景:
* <ul>
* <li>连接/读取/写入超时:10 秒</li>
* <li>允许重定向和失败重试(最大 3 次)</li>
* <li>连接池:5 个空闲连接,保持 10 分钟</li>
* <li>系统默认证书信任策略(非调试模式)</li>
* </ul>
* </li>
* <li>【自定义配置】基于默认配置扩展,支持灵活定制:
* <ul>
* <li>添加拦截器(日志、签名、认证等)</li>
* <li>修改超时参数、连接池配置</li>
* <li>配置 HTTPS 证书信任策略(调试模式/自定义证书/系统默认)</li>
* </ul>
* </li>
* </ul>
* <p>
* 使用示例:
* <pre>
* // 1. 使用默认配置发起 GET 请求
* String resultDefault = OkHttp_Util.get("https://api.example.com")
* .addUrlParam("page", "1")
* .addHeader("User-Agent", "OkHttp-Util/1.0")
* .execute_Pro(); // 自动处理响应和异常
*
* // 2. 自定义配置(添加日志拦截器+长连接超时)
* OkHttpClient.Builder customBuilder = OkHttp_Util.customBuilder()
* .addInterceptor(new OkHttp_Util.LoggingInterceptor()) // 添加自定义拦截器
* .connectTimeout(60, TimeUnit.SECONDS); // 延长连接超时时间
* OkHttp_Util.buildCustomInstance(customBuilder); // 初始化自定义单例
* OkHttpClient customClient = OkHttp_Util.getCustomInstance(); // 获取到 一个 单例,并且是自定义配置的 OkHttpClient 实例
* String resultCustom = OkHttp_Util.get("https://api.example.com", customClient)
* .addUrlParam("page", "1")
* .addHeader("User-Agent", "OkHttp-Util/1.0")
* .execute_Pro(); // 自动处理响应和异常
*
* // 3. 上传文件(multipart/form-data)
* File file = new File("example.pdf");
* String uploadResult = OkHttp_Util.post("https://api.upload.com")
* .addHeader("Authorization", "Bearer YOUR_TOKEN")
* .formData("file", file, MediaType.parse("application/pdf")) // 上传文件
* .execute_Pro();
*
* // 4. 处理异步请求
* OkHttp_Util.get("https://api.async.com")
* .enqueue(new Callback() {
* @Override
* public void onResponse(Call call, Response response) throws IOException {
* String body = OkHttp_Util.parseResponse(response);
* // 处理成功响应
* }
* @Override
* public void onFailure(Call call, IOException e) {
* // 处理失败(网络异常)
* }
* });
* </pre>
*
* @author DolphinHome
* @date 2025/04/30
*/
public class OkHttp_Util {
/*
// 使用 Log4j 记录日志
private static final Logger log = LoggerFactory.getLogger(OkHttp_Util.class);
*/
// ------------------------------ 单例实例 ------------------------------
/**
* 默认配置的 OkHttpClient 单例(线程安全,双重检查锁定实现)
*/
private static volatile OkHttpClient defaultInstance;
/**
* 自定义配置的 OkHttpClient 单例(线程安全,按需初始化)
*/
private static volatile OkHttpClient customInstance;
// ------------------------------ 构造方法 ------------------------------
/**
* 私有构造方法,禁止类实例化
* <p>
* 通过断言错误防止反射创建实例,强化工具类设计
* </p>
*
* @throws AssertionError 始终抛出,明确禁止实例化
*/
private OkHttp_Util() {
throw new AssertionError("不可实例化工具类");
}
// ------------------------------ 配置类 ------------------------------
/**
* OkHttp 配置参数容器
* <p>
* 包含网络请求核心配置,所有参数均有默认值:
* <ul>
* <li>超时:连接/读取/写入默认 10 秒</li>
* <li>重定向:默认允许</li>
* <li>重试:默认允许,最大 3 次</li>
* <li>连接池:5 个空闲连接,保持 10 分钟</li>
* <li>HTTPS:默认使用系统证书,调试模式可跳过验证</li>
* </ul>
*
* <p>
* 自定义证书信任(生产环境):
* <pre>
* // 1. 加载 PEM 证书文件
* X509Certificate cert = loadCertificateFromFile("trusted.crt");
* OkHttpConfig config = new OkHttpConfig();
* config.setTrustedCertificates(new X509Certificate[]{cert}); // 设置自定义证书
*
* // 2. 通过自定义构建器应用配置(需修改 defaultBuilder 逻辑,此处仅示例)
* OkHttpClient.Builder builder = OkHttp_Util.customBuilder();
* configSSL(builder, config); // 内部方法,实际通过 OkHttpConfig 传递
* </pre>
* <p>
* 调试模式(测试环境):
* <pre>
* OkHttpConfig debugConfig = new OkHttpConfig();
* debugConfig.setDebugMode(true); // 跳过所有证书验证(危险!仅测试用)
* </pre>
*/
public static class OkHttpConfig {
// 超时配置(单位:秒)
private int connectTimeout; // 连接超时:建立 TCP 连接的最大等待时间
private int readTimeout; // 读取超时:等待服务器响应数据的最大时间
private int writeTimeout; // 写入超时:发送请求体到服务器的最大时间
// 重定向策略
private boolean followRedirects; // 是否允许自动跟随重定向(默认允许)
// 重试策略
private boolean retryOnConnectionFailure; // 连接失败时是否重试(默认允许)
private int maxRetryCount; // 最大重试次数(默认 3 次)
// 连接池配置
private int maxIdleConnections = 5; // 最大空闲连接数(默认 5 个)
private long keepAliveDuration = 10; // 连接存活时间(默认 10 分钟)
private TimeUnit keepAliveUnit = TimeUnit.MINUTES; // 存活时间单位
// HTTPS 安全配置
private boolean debugMode; // 调试模式(跳过证书验证,默认关闭)
private X509Certificate[] trustedCertificates; // 自定义信任证书(PEM 格式,默认 null)
/**
* 无参构造:使用全默认配置
*/
public OkHttpConfig() {
this(
10,
10,
10,
true,
true,
3,
5,
10,
TimeUnit.MINUTES,
false,
null
);
}
public OkHttpConfig(
int connectTimeout,
int readTimeout,
int writeTimeout,
boolean followRedirects,
boolean retryOnConnectionFailure,
int maxRetryCount,
int maxIdleConnections,
int keepAliveDuration,
TimeUnit keepAliveUnit,
boolean debugMode,
X509Certificate[] trustedCertificates
) {
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
this.followRedirects = followRedirects;
this.retryOnConnectionFailure = retryOnConnectionFailure;
this.maxRetryCount = maxRetryCount;
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDuration = keepAliveDuration;
this.keepAliveUnit = keepAliveUnit;
this.debugMode = debugMode;
this.trustedCertificates = trustedCertificates;
}
// region get/set 方法
public int getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(int connectTimeout) {
this.connectTimeout = connectTimeout;
}
public int getReadTimeout() {
return readTimeout;
}
public void setReadTimeout(int readTimeout) {
this.readTimeout = readTimeout;
}
public int getWriteTimeout() {
return writeTimeout;
}
public void setWriteTimeout(int writeTimeout) {
this.writeTimeout = writeTimeout;
}
public boolean isFollowRedirects() {
return followRedirects;
}
public void setFollowRedirects(boolean followRedirects) {
this.followRedirects = followRedirects;
}
public boolean isRetryOnConnectionFailure() {
return retryOnConnectionFailure;
}
public void setRetryOnConnectionFailure(boolean retryOnConnectionFailure) {
this.retryOnConnectionFailure = retryOnConnectionFailure;
}
public int getMaxRetryCount() {
return maxRetryCount;
}
public void setMaxRetryCount(int maxRetryCount) {
this.maxRetryCount = maxRetryCount;
}
public int getMaxIdleConnections() {
return maxIdleConnections;
}
public void setMaxIdleConnections(int maxIdleConnections) {
this.maxIdleConnections = maxIdleConnections;
}
public long getKeepAliveDuration() {
return keepAliveDuration;
}
public void setKeepAliveDuration(long keepAliveDuration) {
this.keepAliveDuration = keepAliveDuration;
}
public TimeUnit getKeepAliveUnit() {
return keepAliveUnit;
}
public void setKeepAliveUnit(TimeUnit keepAliveUnit) {
this.keepAliveUnit = keepAliveUnit;
}
public boolean isDebugMode() {
return debugMode;
}
public void setDebugMode(boolean debugMode) {
this.debugMode = debugMode;
}
public X509Certificate[] getTrustedCertificates() {
return trustedCertificates;
}
public void setTrustedCertificates(X509Certificate[] trustedCertificates) {
this.trustedCertificates = trustedCertificates;
}
// endregion
}
// ------------------------------ 自定义配置单例 ------------------------------
/**
* 获取自定义配置的 OkHttpClient 单例
* <p>
* 必须先通过 {@link #buildCustomInstance(OkHttpClient.Builder)} 初始化
* </p>
* <p>
* 使用示例:
* </p>
* <pre>
* OkHttpClient.Builder customBuilder = OkHttp_Util.customBuilder()
* .addInterceptor(new OkHttp_Util.LoggingInterceptor()) // 添加自定义拦截器
* .connectTimeout(60, TimeUnit.SECONDS); // 延长连接超时时间
* OkHttp_Util.buildCustomInstance(customBuilder); // 初始化自定义单例
* OkHttpClient customClient = OkHttp_Util.getCustomInstance(); // 获取到 一个 单例,并且是自定义配置的 OkHttpClient 实例
* String resultCustom = OkHttp_Util.get("https://api.example.com", customClient)
* .addUrlParam("page", "1")
* .addHeader("User-Agent", "OkHttp-Util/1.0")
* .execute_Pro(); // 自动处理响应和异常
* </pre>
*
* @return 自定义配置的 OkHttpClient 实例(线程安全单例)
* @throws IllegalStateException 未初始化自定义配置时抛出
*/
public static OkHttpClient getCustomInstance() {
if (customInstance == null) {
throw new IllegalStateException("自定义 OkHttpClient 未初始化,请先调用 buildCustomInstance()");
}
return customInstance;
}
/**
* 初始化自定义配置单例
* <p>
* 基于默认配置构建器扩展,支持添加拦截器、修改超时等
* <p>
* <b>线程安全</b>:采用双重检查锁定,确保单例唯一性
* </p>
* <p>
* 使用示例:
* </p>
* <pre>
* OkHttpClient.Builder customBuilder = OkHttp_Util.customBuilder()
* .addInterceptor(new OkHttp_Util.LoggingInterceptor()) // 添加自定义拦截器
* .connectTimeout(60, TimeUnit.SECONDS); // 延长连接超时时间
* OkHttp_Util.buildCustomInstance(customBuilder); // 初始化自定义单例
* OkHttpClient customClient = OkHttp_Util.getCustomInstance(); // 获取到 一个 单例,并且是自定义配置的 OkHttpClient 实例
* String resultCustom = OkHttp_Util.get("https://api.example.com", customClient)
* .addUrlParam("page", "1")
* .addHeader("User-Agent", "OkHttp-Util/1.0")
* .execute_Pro(); // 自动处理响应和异常
* </pre>
*
* @param builder OkHttpClient 构建器(不可为 null)
* @throws NullPointerException builder 为 null 时抛出
*/
public static void buildCustomInstance(OkHttpClient.Builder builder) {
if (builder == null) throw new NullPointerException("builder 不能为 null");
if (customInstance == null) {
synchronized (OkHttp_Util.class) {
if (customInstance == null) {
customInstance = builder.build();
}
}
}
}
/**
* 获取可扩展的 OkHttpClient 构建器(基于默认配置)
* <p>
* 每次调用返回新的构建器实例,不影响现有单例:
* - 包含默认的超时时间、连接池和重试策略
* - 支持添加自定义拦截器(如签名、认证、日志等)
* - 可修改 HTTPS 证书信任策略
* </p>
* <p>
* 使用示例:
* </p>
* <pre>
* // 自定义配置 OkHttpClient 单例
* OkHttpClient.Builder customBuilder = OkHttp_Util.customBuilder()
* .addInterceptor(new OkHttp_Util.LoggingInterceptor()) // 添加自定义拦截器
* .connectTimeout(60, TimeUnit.SECONDS); // 延长连接超时时间
* OkHttp_Util.buildCustomInstance(customBuilder); // 初始化自定义单例
* OkHttpClient customClient = OkHttp_Util.getCustomInstance(); // 获取到 一个 单例,并且是自定义配置的 OkHttpClient 实例【注意:此客户端是单例的,调用后会将其设置为单例实例】
* String resultCustom = OkHttp_Util.get("https://api.example.com", customClient)
* .addUrlParam("page", "1")
* .addHeader("User-Agent", "OkHttp-Util/1.0")
* .execute_Pro(); // 自动处理响应和异常
*
*
* // 另一个 OkHttpClient 实例(非单例)
* OkHttpClient customClient = OkHttp_Util.customBuilder()
* .addInterceptor(new XXXXXInterceptor())
* .connectTimeout(60, TimeUnit.SECONDS)
* .build(); // 获取到了一个自定义配置的 OkHttpClient 实例【注意:此客户端不是单例的,每次调用都会创建一个新的客户端】
* String resultCustom = OkHttp_Util.get("https://api.example.com", customClient)
* .addUrlParam("page", "1")
* .addHeader("User-Agent", "OkHttp-Util/1.0")
* .execute_Pro(); // 自动处理响应和异常
* </pre>
*
* @return 预配置默认参数的 OkHttpClient.Builder 实例
*/
public static OkHttpClient.Builder customBuilder() {
// 通过newBuilder()方法克隆默认配置,保证原实例不受影响
return getDefaultInstance().newBuilder();
}
// ------------------------------ 默认配置单例 ------------------------------
/**
* 获取默认配置的 OkHttpClient 单例
* <p>
* 首次调用时初始化,采用双重检查锁定保证线程安全
* <p>
* 默认配置详情:
* <ul>
* <li>超时:10 秒(连接/读取/写入)</li>
* <li>连接池:5 个空闲连接,保持 10 分钟</li>
* <li>重试策略:3 次失败重试(通过 {@link RetryInterceptor} 实现)</li>
* <li>HTTPS:使用系统默认证书信任策略(非调试模式)</li>
* </ul>
* </p>
* <p>
* 使用示例:
* </p>
* <pre>
* OkHttpClient client = OkHttp_Util.getDefaultInstance(); // 获取到 一个 单例,并且默认配置过的 OkHttpClient 实例
* Request request = new Request.Builder()
* .url("https://api.default.com")
* .get()
* .build();
* try (Response response = client.newCall(request).execute()) {
* String body = OkHttp_Util.parseResponse(response);
* }
* </pre>
*
* @return 预配置的 OkHttpClient 单例(线程安全)
*/
public static OkHttpClient getDefaultInstance() {
// 第一次检查:避免每次访问都进行同步
if (defaultInstance == null) {
synchronized (OkHttp_Util.class) {
// 第二次检查:防止重复创建
if (defaultInstance == null) {
defaultInstance = defaultBuilder().build(); // 使用默认的自定义配置
}
}
}
return defaultInstance;
}
/**
* 创建默认配置的 OkHttpClient.Builder
* <p>
* 包含以下核心配置:
* <ol>
* <li>基础超时参数(10 秒)</li>
* <li>连接池配置(5 个空闲连接,10 分钟存活)</li>
* <li>重试拦截器(最大 3 次重试)</li>
* <li>HTTPS 证书信任策略(根据 {@link OkHttpConfig} 动态配置)</li>
* </ol>
* </p>
*
* @return 预配置默认参数的构建器实例
*/
private static OkHttpClient.Builder defaultBuilder() {
// 自定义配置
OkHttpConfig config = new OkHttpConfig();
OkHttpClient.Builder builder = new OkHttpClient.Builder()
// 设置连接超时(建立TCP连接的最大等待时间)
.connectTimeout(config.connectTimeout, TimeUnit.SECONDS)
// 设置读取超时(等待服务器返回数据的最大时间)
.readTimeout(config.readTimeout, TimeUnit.SECONDS)
// 设置写入超时(发送请求体到服务器的最大时间)
.writeTimeout(config.writeTimeout, TimeUnit.SECONDS)
// 设置是否允许重定向
.followRedirects(config.followRedirects)
// 设置是否允许失败重试
.retryOnConnectionFailure(config.retryOnConnectionFailure)
// 配置连接池(复用HTTP/HTTP2连接,减少延迟)
.connectionPool(new ConnectionPool(
config.maxIdleConnections, // 最大空闲连接数
config.keepAliveDuration, // 保持连接时间
config.keepAliveUnit)) // 保持连接时间单位
// TODO: 通过 Interceptor 实现 最大重试次数
.addInterceptor(new RetryInterceptor(config.maxRetryCount));
// 设置信任证书(PEM格式)【测试环境中 debug=true 时,不设置信任证书】
configSSL(builder, config);
return builder;
}
// ------------------------------ HTTPS 安全配置 ------------------------------
/**
* 配置 HTTPS 证书信任策略
* <p>
* 支持三种模式:
* <ul>
* <li><b>调试模式</b>({@code debugMode=true}):
* <ul>
* <li>跳过所有证书验证(存在严重安全风险,仅限测试环境!)</li>
* <li>警告:可能导致中间人攻击,绝不能用于生产环境</li>
* </ul>
* </li>
* <li><b>自定义证书</b>({@code trustedCertificates 非空}):
* <ul>
* <li>仅信任指定的 PEM 格式证书</li>
* <li>适用于双向认证或自签名证书场景</li>
* </ul>
* </li>
* <li><b>系统默认</b>(默认模式):
* <ul>
* <li>使用设备/系统预装的 CA 证书</li>
* <li>适用于生产环境的标准 HTTPS 通信</li>
* </ul>
* </li>
* </ul>
* </p>
*
* @param builder OkHttpClient 构建器实例
* @param config 配置参数(包含 HTTPS 相关配置)
* @return 应用证书策略后的构建器实例
* @throws RuntimeException 证书配置失败时抛出(包装底层异常)
*/
private static OkHttpClient.Builder configSSL(OkHttpClient.Builder builder, OkHttpConfig config) {
try {
SSLContext sslContext;
X509TrustManager trustManager;
if (config.debugMode) {
// 调试模式:禁用证书验证(风险提示:仅用于测试环境!)
sslContext = SSLContext.getInstance("TLSv1.3");
trustManager = new TrustAllManager();
sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
builder.sslSocketFactory(sslContext.getSocketFactory(), trustManager);
} else if (config.trustedCertificates != null && config.trustedCertificates.length > 0) {
// 自定义证书模式:使用指定的 PEM 证书
trustManager = createCustomTrustManager(config.trustedCertificates);
sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(null, new TrustManager[]{trustManager}, null);
builder.sslSocketFactory(sslContext.getSocketFactory(), trustManager);
} else {
// 生产环境默认:使用系统信任的证书
trustManager = getSystemTrustManager();
sslContext = SSLContext.getInstance("TLSv1.3");
sslContext.init(null, new TrustManager[]{trustManager}, null);
builder.sslSocketFactory(sslContext.getSocketFactory(), trustManager);
}
// 允许所有主机名验证(需根据实际需求调整,此处为演示简化)
builder.hostnameVerifier((hostname, session) -> true);
} catch (Exception e) {
throw new RuntimeException("HTTPS 证书配置失败", e);
}
return builder;
}
// region 安全组件
/**
* 调试模式专用:信任所有证书的管理器
* <p>
* 重写证书验证方法,跳过所有客户端和服务器证书检查
* 警告:此实现存在严重安全漏洞,仅限测试环境使用!
* </p>
*/
@Deprecated
private static class TrustAllManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
// 客户端证书验证(此处无需处理)
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
// 跳过服务器证书验证(调试模式专用)
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0]; // 返回空列表表示接受所有颁发者
}
}
/**
* 调试模式专用:创建忽略证书验证的 SSLSocketFactory
* <p>
* 警告:此模式会绕过所有证书验证,存在中间人攻击风险,仅限测试环境使用!
* </p>
*
* @return 不安全的 SSLSocketFactory 实例
* @throws Exception SSL 上下文初始化失败时抛出
*/
private static SSLSocketFactory createInsecureSocketFactory() throws Exception {
SSLContext context = SSLContext.getInstance("TLSv1.3");
context.init(null, new TrustManager[]{new TrustAllManager()}, new SecureRandom());
return context.getSocketFactory();
}
/**
* 获取系统默认的证书信任管理器
* <p>
* 使用设备或系统预装的证书颁发机构 (CA) 列表
* </p>
*
* @return 系统默认的 X509TrustManager 实例
* @throws Exception 信任管理器初始化失败时抛出
*/
private static X509TrustManager getSystemTrustManager() throws Exception {
TrustManagerFactory factory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
factory.init((KeyStore) null);
return (X509TrustManager) factory.getTrustManagers()[0];
}
/**
* 创建自定义证书信任管理器(PEM 格式)
* <p>
* 将指定的证书添加到信任列表,用于验证服务器证书
* </p>
*
* @param certificates PEM 格式的 X509 证书数组
* @return 自定义的 X509TrustManager 实例
* @throws Exception 密钥库或信任管理器初始化失败时抛出
*/
private static X509TrustManager createCustomTrustManager(X509Certificate[] certificates)
throws Exception {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
for (int i = 0; i < certificates.length; i++) {
keyStore.setCertificateEntry("cert-" + i, certificates[i]);
}
TrustManagerFactory factory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
factory.init(keyStore);
return (X509TrustManager) factory.getTrustManagers()[0];
}
// endregion
// region 拦截器
private static class LoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long startNs = System.nanoTime();
// 记录请求详情
// log.debug("Request => {} {}\nHeaders: {}", request.method(), request.url(), request.headers());
Response response = chain.proceed(request);
long costMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
// 记录响应指标
// log.info("Response <= {} {} ({}ms)\nCode: {}", request.method(), request.url(), costMs, response.code());
return response;
}
}
/**
* 失败重试拦截器(基于默认配置的最大重试次数)
* <p>
* 实现逻辑:
* <ul>
* <li>对每个请求尝试 {@code maxRetries + 1} 次(包括首次)</li>
* <li>仅处理 {@link IOException}(网络层异常)</li>
* <li>成功响应({@code response.isSuccessful()})立即返回</li>
* </ul>
* </p>
*/
private static class RetryInterceptor implements Interceptor {
private final int maxRetries; // 最大重试次数(不包含首次请求)
/**
* @param maxRetries 最大重试次数(建议 0-5,默认 3)
*/
public RetryInterceptor(int maxRetries) {
this.maxRetries = maxRetries;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
IOException exception = null;
for (int i = 0; i <= maxRetries; i++) { // 包括首次请求,总尝试次数 maxRetries + 1
try {
Response response = chain.proceed(request);
if (response.isSuccessful()) return response;
// 非成功响应不重试(如 4xx/5xx 错误,由上层处理)
return response;
} catch (IOException e) {
exception = e;
// log.warn("请求失败(尝试 {}/{}): {}", i + 1, maxRetries + 1, e.getMessage());
System.out.println(String.format("请求失败(尝试 %d/%d): %s", i + 1, maxRetries + 1, e.getMessage()));
}
}
throw exception != null ? exception : new IOException("网络请求无响应");
}
}
// endregion
// region 统一响应处理
/**
* 解析响应并处理异常
* <p>
* 自动关闭响应体,封装非成功状态码为 {@link HttpException}
* </p>
*
* @param response OkHttp 响应实例(不可为 null)
* @return 响应正文(UTF-8 字符串)
* @throws IOException 网络异常或响应体为空
* @throws HttpException 非 2xx 状态码(包含状态码和消息)
*/
public static String parseResponse(Response response) throws IOException {
try {
if (!response.isSuccessful()) {
throw new HttpException(response.code(),
"HTTP错误: " + response.code() + " - " + response.message());
}
ResponseBody body = response.body();
if (body == null) throw new HttpException(500, "响应体为空");
return body.string();
} finally {
response.close();
}
}
/**
* 自定义 HTTP 异常(包含状态码)
*/
public static class HttpException extends IOException {
private final int statusCode; // HTTP 状态码(如 404, 500)
public HttpException(int statusCode, String message) {
super(message);
this.statusCode = statusCode;
}
public int getStatusCode() {
return statusCode;
}
}
// endregion
// region 链式调用
/**
* 链式请求构建器(支持 GET/POST 请求)
* <p>
* POST 提交 JSON 数据示例:
* <pre>
* String jsonBody = "{\"name\":\"Dolphin\",\"age\":18}";
* String result = OkHttp_Util.post("https://api.post.com")
* .addHeader("Content-Type", "application/json")
* .jsonBody(jsonBody) // 设置 JSON 请求体
* .execute_Pro(); // 自动处理 200 以外状态码
* </pre>
* <p>
* GET 请求带查询参数示例:
* <pre>
* Map<String, String> params = new HashMap<>();
* params.put("key1", "value1");
* params.put("key2", "value2");
* String result = OkHttp_Util.get("https://api.get.com")
* .addUrlParam(params) // 批量添加查询参数
* .execute_Pro();
* </pre>
*/
public static class RequestChainBuilder {
// region MediaType 常量
// 定义 JSON 数据的媒体类型,字符集为 UTF-8
private static final MediaType JSON_TYPE = MediaType.get("application/json; charset=utf-8");
// 定义表单数据的媒体类型,字符集为 UTF-8
private static final MediaType FORM_URLENCODED_TYPE = MediaType.get("application/x-www-form-urlencoded; charset=utf-8");
// 定义纯文本数据的媒体类型,字符集为 UTF-8
private static final MediaType TEXT_PLAIN_TYPE = MediaType.get("text/plain; charset=utf-8");
// 定义 XML 数据的媒体类型,字符集为 UTF-8
private static final MediaType XML_TYPE = MediaType.get("application/xml; charset=utf-8");
// endregion
private final OkHttpClient client; // 使用的 OkHttpClient 实例(默认或自定义)
private final HttpUrl.Builder urlBuilder; // URL 构建器(包含基础 URL 和查询参数)
private final Request.Builder requestBuilder; // 请求构建器(包含头信息和方法)
private RequestBody requestBody; // 请求体(仅 POST/PUT 等方法需要)
/**
* 私有构造方法(内部使用,外部通过工厂方法创建)
*
* @param url 基础 URL(不可为 null,需包含协议如 http://)
* @param client OkHttpClient 实例(默认或自定义)
* @throws NullPointerException 当 url 或 client 为 null 时抛出
*/
private RequestChainBuilder(String url, OkHttpClient client) {
// 确保 client 不为 null,若为 null 则抛出异常
this.client = Objects.requireNonNull(client, "client 不能为 null");
// 解析 URL 并创建 URL 构建器,若 URL 格式错误则抛出异常
this.urlBuilder = Objects.requireNonNull(HttpUrl.parse(url), "url 格式错误").newBuilder();
// 创建请求构建器
this.requestBuilder = new Request.Builder();
}
/**
* 添加单个 URL 查询参数(链式调用)。
*
* @param key 参数名,不可为 null
* @param value 参数值,可为 null(将被编码为空字符串)
* @return 当前构建器实例,支持链式调用
* @throws IllegalArgumentException 当 key 为 null 时抛出
*/
public RequestChainBuilder addUrlParam(String key, String value) {
// 确保参数名不为空,若为空则抛出异常
if (key == null) throw new IllegalArgumentException("参数名不能为空");
// 向 URL 构建器中添加查询参数,若值为 null 则编码为空字符串
urlBuilder.addQueryParameter(key, value != null ? value : "");
// 返回当前构建器实例,以支持链式调用
return this;
}
/**
* 添加单个请求头字段(链式调用)。
*
* @param key 头字段名,不可为 null
* @param value 头字段值,可为 null(OkHttp 会自动处理 null 值)
* @return 当前构建器实例,支持链式调用
* @throws IllegalArgumentException 当 key 为 null 时抛出
*/
public RequestChainBuilder addHeader(String key, String value) {
if (key == null) throw new IllegalArgumentException("头字段名不能为空");
requestBuilder.addHeader(key, value);
return this;
}
/**
* 批量添加 URL 参数
*
* @param params 包含要添加的 URL 参数的 Map
* @return 当前构建器实例
*/
public RequestChainBuilder addUrlParam(Map<String, String> params) {
// 若参数不为空,则遍历参数并调用 addUrlParam 方法添加每个参数
if (params != null) {
for (Map.Entry<String, String> entry : params.entrySet()) {
addUrlParam(entry.getKey(), entry.getValue());
}
}
// 返回当前构建器实例,以支持链式调用
return this;
}
/**
* 批量添加请求头
*
* @param headers 包含要添加的请求头的 Map
* @return 当前构建器实例
*/
public RequestChainBuilder addHeaders(Map<String, String> headers) {
// 若头信息不为空,则遍历头信息并调用 addHeader 方法添加每个头信息
if (headers != null) {
for (Map.Entry<String, String> entry : headers.entrySet()) {
addHeader(entry.getKey(), entry.getValue());
}
}
// 返回当前构建器实例,以支持链式调用
return this;
}
/****************** GET请求封装 ********************/
/**
* 设置请求方法为 GET
* GET 请求不需要请求体,调用后可继续添加查询参数或请求头
*
* @return 当前构建器实例
*/
public RequestChainBuilder get() {
// 向请求构建器中设置请求方法为 GET
requestBuilder.get();
// 返回当前构建器实例,以支持链式调用
return this;
}
/****************** POST请求封装 ********************/
/**
* 设置请求方法为 POST
* 调用后需通过 {@link #formData(Map)}、{@link #jsonBody(String)} 等方法设置请求体
*
* @return 当前构建器实例
*/
public RequestChainBuilder post() {
// 默认设置空请求体
this.requestBody = RequestBody.create(null, new byte[0]);
// 返回当前构建器实例,以支持链式调用
return this;
}
// ------------------------------ Body 参数配置 ------------------------------
/**
* 设置 FormData 表单参数(multipart/form-data)
* 自动构建 {@link MultipartBody},媒体类型为 {@code multipart/form-data}
*
* @param params 表单参数(键值对)
* @return 当前实例
*/
public RequestChainBuilder formData(Map<String, String> params) {
// 参数校验
if (params == null) throw new IllegalArgumentException("参数 params 不能为空");
// 创建 MultipartBody 构建器,并设置媒体类型为 multipart/form-data
MultipartBody.Builder bodyBuilder = new MultipartBody.Builder()
.setType(MultipartBody.FORM);
// 遍历参数并添加到 MultipartBody 构建器中
params.forEach(bodyBuilder::addFormDataPart);
// 构建请求体
this.requestBody = bodyBuilder.build();
// 返回当前构建器实例,以支持链式调用
return this;
}
/**
* 设置 multipart/form-data 表单参数(含文件上传)
* <p>
* 示例:上传单个文件
* <pre>
* File avatar = new File("avatar.png");
* MediaType imageType = MediaType.parse("image/png");
* String uploadResult = OkHttp_Util.post("https://api.upload.com")
* .formData("file", avatar, imageType) // 文件参数
* .addFormDataPart("username", "dolphin") // 普通表单参数(需配合 MultipartBody.Builder)
* .execute_Pro();
* </pre>
*
* @param key 表单字段名
* @param file 上传文件
* @param mediaType 文件类型(如 MediaType.parse("image/png"))
* @return 当前构建器实例
*/
public RequestChainBuilder formData(String key, File file, MediaType mediaType) {
// 参数校验
if (key == null) throw new IllegalArgumentException("参数 key 不能为空");
if (file == null) throw new IllegalArgumentException("参数 file 不能为空");
if (mediaType == null) throw new IllegalArgumentException("参数 mediaType 不能为空");
// 创建 MultipartBody 构建器,并设置媒体类型为 multipart/form-data
MultipartBody.Builder builder = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
// 添加文件部分,设置 Content-Disposition 头信息
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"" + key + "\"; filename=\"" + file.getName() + "\""),
RequestBody.create(mediaType, file)
);
// 构建请求体
this.requestBody = builder.build();
// 返回当前构建器实例,以支持链式调用
return this;
}
/**
* 设置 x-www-form-urlencoded 表单参数
* 自动构建 {@link FormBody},媒体类型为 {@code application/x-www-form-urlencoded}
*
* @param params 表单参数(键值对)
* @return 当前实例
*/
public RequestChainBuilder formUrlEncoded(Map<String, String> params) {
// 参数校验
if (params == null) throw new IllegalArgumentException("参数 params 不能为空");
// 创建 FormBody 构建器
FormBody.Builder bodyBuilder = new FormBody.Builder();
// 遍历参数并添加到 FormBody 构建器中
params.forEach(bodyBuilder::add);
// 构建请求体
this.requestBody = bodyBuilder.build();
// 返回当前构建器实例,以支持链式调用
return this;
}
/**
* 设置 Raw JSON 数据
* 自动设置媒体类型为 {@code application/json; charset=utf-8}
*
* @param json JSON 字符串
* @return 当前实例
*/
public RequestChainBuilder jsonBody(String json) {
// 参数校验
if (json == null) throw new IllegalArgumentException("参数 json 不能为空");
// 创建 JSON 格式的请求体
this.requestBody = RequestBody.create(json, JSON_TYPE);
// 返回当前构建器实例,以支持链式调用
return this;
}
/**
* 设置 Raw 文本数据
* 自动设置媒体类型为 {@code text/plain; charset=utf-8}
*
* @param text 文本内容
* @return 当前实例
*/
public RequestChainBuilder rawText(String text) {
// 参数校验
if (text == null) throw new IllegalArgumentException("参数 text 不能为空");
// 创建纯文本格式的请求体
this.requestBody = RequestBody.create(text, TEXT_PLAIN_TYPE);
// 返回当前构建器实例,以支持链式调用
return this;
}
/**
* 设置 Raw XML 数据
* 自动设置媒体类型为 {@code application/xml; charset=utf-8}
*
* @param xml XML 字符串
* @return 当前实例
*/
public RequestChainBuilder rawXml(String xml) {
// 参数校验
if (xml == null) throw new IllegalArgumentException("参数 xml 不能为空");
// 创建 XML 格式的请求体
this.requestBody = RequestBody.create(xml, XML_TYPE);
// 返回当前构建器实例,以支持链式调用
return this;
}
/**
* 设置二进制数据
* 媒体类型为 {@code application/octet-stream}
*
* @param data 二进制字节数组
* @return 当前实例
*/
public RequestChainBuilder binaryBody(byte[] data) {
// 参数校验
if (data == null) throw new IllegalArgumentException("参数 data 不能为空");
// 创建二进制格式的请求体
this.requestBody = RequestBody.create(data, MediaType.get("application/octet-stream"));
// 返回当前构建器实例,以支持链式调用
return this;
}
/**
* 执行同步请求并返回原始响应对象。
* <p>
* 适用于需要手动处理响应状态码和响应体的场景,需调用者手动关闭响应体。
*
* @return OkHttp 的 {@link Response} 对象
* @throws IOException 网络异常、请求超时或服务器错误
*/
public Response execute() throws IOException {
// 构建最终URL
HttpUrl httpUrl = urlBuilder.build();
// POST请求设置请求体
if (requestBody != null) {
// 若请求体不为空,则设置请求方法为 POST 并设置请求体
requestBuilder.method("POST", requestBody);
}
// 设置URL并构建请求
Request request = requestBuilder.url(httpUrl).build();
// 执行请求并返回响应
return client.newCall(request).execute();
}
/**
* 执行同步请求并返回处理后的响应字符串(自动处理非成功状态码)。
* <p>
* 功能包括:
* <ul>
* <li>自动关闭响应体(通过 try-with-resources)</li>
* <li>检查响应状态码,非 2xx 状态码抛出 {@link OkHttp_Util.HttpException}</li>
* <li>将响应体转换为 UTF-8 编码的字符串</li>
* </ul>
* 自动关闭响应体,处理非成功状态码({@code response.isSuccessful()})
* </p>
*
* @return 响应正文(UTF-8 字符串)
* @throws IOException 网络异常、响应体解析失败或 I/O 错误
* @throws OkHttp_Util.HttpException 当响应状态码非 2xx 时抛出,包含状态码和错误消息
*/
public String execute_Pro() throws IOException {
// 构建最终URL
HttpUrl httpUrl = urlBuilder.build();
// POST请求设置请求体
if (requestBody != null) {
// 若请求体不为空,则设置请求方法为 POST 并设置请求体
requestBuilder.method("POST", requestBody);
}
// 设置URL并构建请求
Request request = requestBuilder.url(httpUrl).build();
// 执行请求并自动关闭响应体
try (Response response = client.newCall(request).execute()) {
// 解析响应并返回响应正文
return OkHttp_Util.parseResponse(response);
}
}
/**
* 执行异步请求,通过回调处理响应结果。
* <p>
* 支持处理成功响应({@link Callback#onResponse(Call, Response)})和失败情况({@link Callback#onFailure(Call, IOException)})。
* <p>
* **注意**:异步请求不会自动关闭响应体,需在 {@link Callback#onResponse(Call, Response)} 中手动关闭。
*
* @param callback 异步回调接口,不可为 null
* @throws NullPointerException 当 callback 为 null 时抛出
*/
public void enqueue(Callback callback) {
try {
// 检查 OkHttpClient 实例是否为 null
if (client == null) {
// 若为 null,则调用回调的 onFailure 方法并传入异常信息
callback.onFailure(null, new IOException("OkHttpClient 实例为 null"));
return;
}
// 构建请求
Request request = requestBuilder.url(urlBuilder.build()).build();
// 检查请求是否为 null
if (request == null) {
// 若为 null,则调用回调的 onFailure 方法并传入异常信息
callback.onFailure(null, new IOException("请求构建失败,Request 为 null"));
return;
}
// 执行异步请求
client.newCall(request).enqueue(callback);
} catch (IllegalStateException e) {
// 记录 URL 构建失败的日志
// log.error("URL构建失败", e);
System.err.println("URL构建失败: " + e.getMessage());
// 调用回调的 onFailure 方法并传入异常信息
callback.onFailure(null, new IOException("URL构建失败"));
} catch (Exception e) {
// 记录异步请求失败的日志
// log.error("异步请求失败", e);
System.err.println("异步请求失败: " + e.getMessage());
// 调用回调的 onFailure 方法并传入异常信息
callback.onFailure(null, new IOException("异步请求失败"));
}
}
}
/**
* 重载工厂方法:创建一个 GET 请求的链式构建器
*
* @param url 请求的 URL
* @return 链式构建器实例
*/
public static RequestChainBuilder get(String url) {
// 创建一个 RequestChainBuilder 实例并设置请求方法为 GET
return new RequestChainBuilder(url, OkHttp_Util.getDefaultInstance()).get();
}
/**
* 重载工厂方法:创建一个 POST 请求的链式构建器
*
* @param url 请求的 URL
* @return 链式构建器实例
*/
public static RequestChainBuilder post(String url) {
// 创建一个 RequestChainBuilder 实例并设置请求方法为 POST
return new RequestChainBuilder(url, OkHttp_Util.getDefaultInstance()).post();
}
/**
* 重载工厂方法:支持传入自定义 OkHttpClient 实例,创建一个使用自定义 OkHttpClient 实例的 GET 请求的链式构建器
*
* @param url 请求的 URL
* @param client 自定义的 OkHttpClient 实例
* @return 链式构建器实例
*/
public static RequestChainBuilder get(String url, OkHttpClient client) {
// 创建一个 RequestChainBuilder 实例并设置请求方法为 GET,使用自定义的 OkHttpClient 实例
return new RequestChainBuilder(url, client).get();
}
/**
* 重载工厂方法:支持传入自定义 OkHttpClient 实例,创建一个使用自定义 OkHttpClient 实例的 POST 请求的链式构建器
*
* @param url 请求的 URL
* @param client 自定义的 OkHttpClient 实例
* @return 链式构建器实例
*/
public static RequestChainBuilder post(String url, OkHttpClient client) {
// 创建一个 RequestChainBuilder 实例并设置请求方法为 POST,使用自定义的 OkHttpClient 实例
return new RequestChainBuilder(url, client).post();
}
// endregion
public static void main(String[] args) throws IOException {
// 使用默认配置
OkHttpClient defaultClient = OkHttp_Util.getDefaultInstance();
// -------------------------------------------------------------------------------
String responseDefault = OkHttp_Util.get("https://api.example.com")
.addUrlParam("key", "value")
.execute_Pro();
// -------------------------------------------------------------------------------
// 创建自定义配置
OkHttpClient.Builder customBuilder = OkHttp_Util.customBuilder()
.addInterceptor(new LoggingInterceptor())
.connectTimeout(30, TimeUnit.SECONDS);
// 提交并实例化自定义配置(单例)
OkHttp_Util.buildCustomInstance(customBuilder);
// 获取自定义配置实例
OkHttpClient customClient = OkHttp_Util.getCustomInstance();
String responseCustom = OkHttp_Util.get("https://api.example.com", customClient)
.addUrlParam("key", "value")
.execute_Pro();
}
}
六、总结
本工具类通过封装OkHttp的复杂配置,提供以下优势:
- 开发效率提升:链式API简化网络请求编写
- 可维护性强:统一配置入口,修改全局参数方便
- 安全性增强:标准化HTTPS证书管理
- 健壮性保障:内置重试机制和异常处理
适合中大型项目作为基础网络组件使用,建议根据实际业务需求调整超时时间和重试策略。