在之前的文章《Spring Boot 项目高效 HTTP 通信:常用客户端大比拼!》里,我们提到了RestTemplat和OkHttp3等Springboot项目开发中最常用的Http客户端。前一篇文章深入 Spring RestTemplate 源码:掌握 HTTP 通信核心技术剖析了RestTemplate客户端的源码,今天,我们就来深入探究一下 OkHttp3 的源码。
在OkHttp3在处理Http请求的过程中,用到了池技术和责任链模式实现的拦截器机制。
一、连接池管理
在 HTTP 通信中,建立连接是一个相对耗时的操作。为了提高性能,OkHttp3 通过连接池复用已经建立的 TCP 连接,减少连接建立和销毁的开销。
1. 连接池的实现
OkHttp3使用了RealConnectionPool和Dispatcher实现连接池的调度。
其内部维护了四个双端队列
- connections:用于管理连接池中的连接
- runningAsyncCalls:存储正在运行的异步调用,包括未完成的已取消调用。
- runningSyncCalls:存储正在运行的同步调用,同样包括未完成的已取消调用。
- readyAsyncCalls:存储待处理的异步调用,按照执行顺序排列。
2. 连接的获取和释放
当发起一个 HTTP 请求时,OkHttpClient 会将请求委托给 Dispatcher 进行调度。
void enqueue(AsyncCall call) {
synchronized (this) {
readyAsyncCalls.add(call);
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to
// the same host.
if (!call.get().forWebSocket) {
AsyncCall existingCall = findExistingCallWithHost(call.host());
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
}
}
promoteAndExecute();
}
Dispatcher 在调度请求时,会将请求放入 readyAsyncCalls 队列。然后通过 promoteAndExecute 方法将符合条件的请求提升到 runningAsyncCalls 队列,并执行这些请求。
private boolean promoteAndExecute() {
assert (!Thread.holdsLock(this));
List<AsyncCall> executableCalls = new ArrayList<>();
boolean isRunning;
synchronized (this) {
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall asyncCall = i.next();
if (runningAsyncCalls.size() >= maxRequests) break; // Max capacity.
if (asyncCall.callsPerHost().get() >= maxRequestsPerHost) continue; // Host max capacity.
i.remove();
asyncCall.callsPerHost().incrementAndGet();
executableCalls.add(asyncCall);
// 满足条件的会被加入runningAsyncCalls队列中
runningAsyncCalls.add(asyncCall);
}
isRunning = runningCallsCount() > 0;
}
for (int i = 0, size = executableCalls.size(); i < size; i++) {
AsyncCall asyncCall = executableCalls.get(i);
asyncCall.executeOn(executorService());
}
return isRunning;
}
请求实际执行时,AsyncCall 对象会调用 RealCall 的executeOn方法来执行。
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
// 核心方法
executorService.execute(this);
success = true;
} catch (RejectedExecutionException e) {
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
transmitter.noMoreExchanges(ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
而RealConnectionPool会定期清理空闲和过期的连接,这个操作是通过cleanupRunnable完成的。它是一个在特定时间间隔运行的线程,它遍历连接,检查是否有连接已经不再需要或者过期,然后将其清除。
private final Runnable cleanupRunnable = () -> {
while (true) {
// 核心方法
long waitNanos = cleanup(System.nanoTime());
if (waitNanos == -1) return;
if (waitNanos > 0) {
long waitMillis = waitNanos / 1000000L;
waitNanos -= (waitMillis * 1000000L);
synchronized (RealConnectionPool.this) {
try {
RealConnectionPool.this.wait(waitMillis, (int) waitNanos);
} catch (InterruptedException ignored) {
}
}
}
}
};
核心方法是cleanup(long now), 用于清理闲置的连接, 返回值指示下一次执行清理的时间或立即再次执行。
主要功能如下:
- 统计正在使用和闲置的连接数。
- 找出最久未使用的连接。
- 同步处理:
- 若有连接超过存活期限或闲置连接数过多,则移除并关闭该连接。
- 否则,计算下次检查时间。
- 若移除连接,则立即再次执行清理。
long cleanup(long now) {
int inUseConnectionCount = 0;
int idleConnectionCount = 0;
RealConnection longestIdleConnection = null;
long longestIdleDurationNs = Long.MIN_VALUE;
// Find either a connection to evict, or the time that the next eviction is due.
synchronized (this) {
for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
RealConnection connection = i.next();
// If the connection is in use, keep searching.
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++;
continue;
}
idleConnectionCount++;
// If the connection is ready to be evicted, we're done.
long idleDurationNs = now - connection.idleAtNanos;
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs;
longestIdleConnection = connection;
}
}
if (longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections) {
// We've found a connection to evict. Remove it from the list, then close it below (outside
// of the synchronized block).
connections.remove(longestIdleConnection);
} else if (idleConnectionCount > 0) {
// A connection will be ready to evict soon.
return keepAliveDurationNs - longestIdleDurationNs;
} else if (inUseConnectionCount > 0) {
// All connections are in use. It'll be at least the keep alive duration 'til we run again.
return keepAliveDurationNs;
} else {
// No connections, idle or in use.
cleanupRunning = false;
return -1;
}
}
closeQuietly(longestIdleConnection.socket());
// Cleanup again immediately.
return 0;
}
3. 连接池的优势
- 减少连接建立和关闭的开销,提升性能;
- 控制连接数量,防止服务器过载;
- 提高连接利用率,降低资源消耗。
二、拦截器
OkHttp3 使用责任链模式实现了灵活的拦截器机制,允许开发者在不修改核心代码的情况下,自定义请求和响应处理逻辑。
1. 拦截器类型
OkHttp3 中定义了以下两种类型的拦截器
- 应用程序拦截器(Application Interceptor): 由开发者自定义,用于添加业务逻辑,例如日志记录、请求重试等。
- 网络拦截器(Network Interceptor): 由 OkHttp3 内部实现,用于处理网络请求的具体细节,例如缓存、重定向等。
2. 拦截器执行流程
每个拦截器负责处理请求的一部分,并将处理结果传递给下一个拦截器。所有拦截器构成一个链条,按照顺序依次执行。
- 请求阶段: 拦截器链按照添加顺序依次执行 intercept() 方法,每个拦截器可以选择修改请求、添加请求头等操作。
- 响应阶段: 最后一个拦截器执行完毕后,响应会逆序经过拦截器链,每个拦截器可以读取响应内容、修改响应头等。
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
// 添加
interceptors.addAll(client.interceptors());
interceptors.add(new RetryAndFollowUpInterceptor(client));
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
originalRequest, this, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
boolean calledNoMoreExchanges = false;
try {
// 责任链
Response response = chain.proceed(originalRequest);
if (transmitter.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
} catch (IOException e) {
calledNoMoreExchanges = true;
throw transmitter.noMoreExchanges(e);
} finally {
if (!calledNoMoreExchanges) {
transmitter.noMoreExchanges(null);
}
}
}
3. 常用拦截器
OkHttp3 提供了一些常用的拦截器,例如:
- RetryAndFollowUpInterceptor: 处理重试和重定向;
- BridgeInterceptor: 将请求转换为网络请求,例如设置请求头、处理 Cookie 等;
- CacheInterceptor: 处理缓存;
- ConnectInterceptor: 建立 TCP 连接。
4. 自定义拦截器
我们可以自定义拦截器,只需实现 Interceptor 接口,并在 intercept() 方法中添加自定义逻辑即可。例如:我们实现一个log的拦截器,类似于切面的角色,在请求前后分别打印一行日志。
public class LoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
logger.info(String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers()));
Response response = chain.proceed(request);
long t2 = System.nanoTime();
logger.info(String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers()));
return response;
}
}
5. 拦截器的优势
- 提高代码复用性: 将通用的网络逻辑封装成拦截器,方便复用。
- 增强代码可扩展性: 通过添加自定义拦截器,轻松扩展功能,无需修改核心代码。
- 提高代码可维护性: 将不同的网络逻辑隔离在不同的拦截器中,降低代码耦合度,方便维护。
三、总结
OkHttp3 通过连接池和拦截器机制实现了高效、灵活的 HTTP 通信。连接池提升了性能,拦截器增强了代码的复用性、可扩展性和可维护性。这些精妙的设计使得OkHttp3能够在保持简洁的API的同时提供强大的功能,从而成为SpringBoot项目开发中最受欢迎的HTTP客户端之一。