一、为什么选择 OkHttp3?
| 特性 |
说明 |
| 连接池复用 |
避免频繁 TCP 握手,高并发下性能优异 |
| HTTP/2 支持 |
多路复用,单连接承载多个请求 |
| 超时控制精细 |
连接超时、读取超时、写入超时可独立配置 |
| 拦截器机制 |
统一处理日志、重试、加解密等横切逻辑 |
| Dispatcher |
控制并发请求数,防止资源耗尽 |
二、核心概念
2.1 一次请求的完整链路
1
| OkHttpClient → Request → Call → (拦截器链) → Response
|
- OkHttpClient:客户端工厂,管理连接池、超时、拦截器等全局配置(应全局单例)
- Request:描述一次 HTTP 请求(URL、Method、Header、Body)
- Call:Request 的可执行对象,
.execute() 同步执行,.enqueue() 异步执行
- Response:服务器返回的响应(状态码、Header、Body)
2.2 关键组件
| 组件 |
作用 |
默认值 |
ConnectionPool |
连接复用池,空闲连接保持存活 |
5个空闲连接,存活5分钟 |
Dispatcher |
调度器,控制并发请求上限 |
最大64请求,单个Host最大5请求 |
Interceptor |
拦截器链,可自定义处理逻辑 |
重试、重定向、缓存等内置拦截器 |
三、实战案例一:GET 请求(JiLianDaApi 模式)
3.1 完整代码(带注释)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
| import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import okhttp3.*;
import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit;
@Slf4j public class ThirdPartyApi {
private static final String BASE_URL = "https://api.example.com/data/list";
private static final String API_KEY = "your-api-key";
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS)
.connectionPool(new ConnectionPool(32, 5, TimeUnit.MINUTES))
.retryOnConnectionFailure(false)
.build();
public static List<DataVo> fetchDataList(int pageNum, int pageSize) { List<DataVo> result = new ArrayList<>();
Headers headers = new Headers.Builder() .add("xApiKey", API_KEY) .add("Accept", "application/json") .build();
String fullUrl = BASE_URL + "?pageNum=" + pageNum + "&pageSize=" + pageSize;
try { Request request = new Request.Builder() .url(fullUrl) .get() .headers(headers) .build();
Response response = HTTP_CLIENT.newCall(request).execute();
if (response.isSuccessful() && response.body() != null) { String responseBody = response.body().string();
JSONObject jsonMsg = JSONObject.parseObject(responseBody); if (jsonMsg.getBoolean("success")) { result = JSONObject.parseArray( jsonMsg.getJSONArray("data").toJSONString(), DataVo.class ); log.info("查询成功, 共 {} 条", result.size()); } else { log.warn("接口返回失败, code: {}", jsonMsg.getInteger("code")); } } else { log.warn("HTTP 请求失败, 状态码: {}", response.code()); } } catch (IOException e) { log.error("请求异常", e); }
return result; } }
|
四、实战案例二:GET 请求 + 坐标转换(AnzhilianOpenApi 模式)
4.1 完整代码(带注释)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import okhttp3.*;
import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit;
@Slf4j public class GpsApi {
private static final String URL = "https://api.example.com/gps/query?gpsType=0";
private static final String TOKEN = "your-jwt-token";
private static final String API_KEY = "your-api-key";
private static final OkHttpClient HTTP_CLIENT;
static { ConnectionPool pool = new ConnectionPool(32, 5, TimeUnit.MINUTES);
Dispatcher dispatcher = new Dispatcher(); dispatcher.setMaxRequests(64); dispatcher.setMaxRequestsPerHost(16);
HTTP_CLIENT = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(0, TimeUnit.SECONDS) .connectionPool(pool) .dispatcher(dispatcher) .retryOnConnectionFailure(false) .build(); }
public static List<GpsVo> fetchGpsData() { List<GpsVo> gpsList = new ArrayList<>();
Headers headers = new Headers.Builder() .add("Authorization", TOKEN) .add("apiKey", API_KEY) .add("Version", "3.0.0") .build();
try { Request request = new Request.Builder() .url(URL) .get() .headers(headers) .build();
Response response = HTTP_CLIENT.newCall(request).execute();
if (response.isSuccessful() && response.body() != null) { JSONObject jsonMsg = JSONObject.parseObject(response.body().string());
if (jsonMsg != null && jsonMsg.getInteger("errorCode") == 0) { String results = jsonMsg.getString("results"); gpsList = JSONObject.parseArray(results, GpsVo.class);
gpsList.forEach(gps -> { gps.setWgs84Lat(gps.getLat()); gps.setWgs84Lng(gps.getLng());
double[] converted = CoordTransformUtil.wgs84ToGcj02( gps.getLng(), gps.getLat()); gps.setLng(converted[0]); gps.setLat(converted[1]); }); } } } catch (IOException e) { log.error("GPS 数据查询失败", e); }
return gpsList; } }
|
五、POST 请求模式(附加)
5.1 POST JSON Body
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public static void postJsonExample() { MediaType JSON = MediaType.parse("application/json; charset=utf-8");
JSONObject bodyJson = new JSONObject(); bodyJson.put("name", "张三"); bodyJson.put("age", 25);
RequestBody requestBody = RequestBody.create(bodyJson.toJSONString(), JSON);
Request request = new Request.Builder() .url("https://api.example.com/user/create") .post(requestBody) .header("Content-Type", "application/json") .build();
try (Response response = HTTP_CLIENT.newCall(request).execute()) { if (response.isSuccessful()) { log.info("响应: {}", response.body().string()); } } catch (IOException e) { log.error("请求失败", e); } }
|
5.2 POST 表单提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public static void postFormExample() { FormBody formBody = new FormBody.Builder() .add("username", "admin") .add("password", "123456") .build();
Request request = new Request.Builder() .url("https://api.example.com/login") .post(formBody) .build();
try (Response response = HTTP_CLIENT.newCall(request).execute()) { log.info("登录结果: {}", response.body().string()); } catch (IOException e) { log.error("请求失败", e); } }
|
六、最佳实践总结
6.1 必须做
| 实践 |
原因 |
| OkHttpClient 全局单例 |
连接池复用,避免重复创建 TCP 连接 |
response.body().string() 只读一次 |
响应体是流,读完即关闭 |
try-catch IOException |
网络请求必须处理异常 |
| 日志记录 |
成功打印数据量,失败打印错误信息,便于排查 |
6.2 推荐做
| 实践 |
说明 |
| 敏感信息外部化 |
Token/Key 放配置中心,不要硬编码 |
| 超时按场景调整 |
大数据接口加大 readTimeout,实时接口减小 |
使用 try-with-resources |
自动关闭 Response,归还连接到池中 |
异步场景用 enqueue() |
避免阻塞主线程 |
6.3 避免做
| 反模式 |
问题 |
每次请求 new OkHttpClient() |
连接池无效,性能极差 |
不做空判断直接 response.body().string() |
可能 NPE |
忽略 IOException |
问题无法追踪 |
| 在生产代码中硬编码 Token |
安全风险 |
七、配置速查表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .connectionPool(new ConnectionPool( 32, 5, TimeUnit.MINUTES )) .retryOnConnectionFailure(false) .addInterceptor(chain -> { Request request = chain.request(); log.info("请求: {} {}", request.method(), request.url()); return chain.proceed(request); }) .build();
|
📅 整理日期:2026-09-04
📝 案例来源:项目 AnzhilianOpenApi / JiLianDaApi
---
这份笔记覆盖了以下内容:
1. **OkHttp 核心概念** — Client、Request、Call、Response 的关系
2. **两种 GET 实战案例** — 带详细中文注释,分别对应你的 `JiLianDaApi`(简单 GET)和 `AnzhilianOpenApi`(多 Header + 坐标转换)
3. **POST 补充** — JSON Body 和表单提交两种方式
4. **最佳实践总结** — 什么该做、什么不该做
5. **配置速查表** — 快速查阅常用参数
所有 Token、Key 等敏感信息已替换为 `"your-xxx"` 占位符。你可以直接保存为 `.md` 文件。