系列索引
本系列文章基于 OkHttp3.14
OkHttp 源碼剖析系列(一)——請求的發(fā)起及攔截器機制概述
OkHttp 源碼剖析系列(六)——連接復用機制及連接的建立
OkHttp 源碼剖析系列(七)——請求的發(fā)起及響應的讀取
前言
OkHttp 是一個我從學 Android 開始就接觸的網(wǎng)絡請求庫了,想想現(xiàn)在也陪伴它快兩年了,卻沒有系統(tǒng)性地對它進行過一次系統(tǒng)性的源碼解析。因此準備開設這樣一個系列,對 OkHttp 的源碼進行解析。
此篇源碼解析基于 OkHttp 3.14
OkHttpClient
我們都知道,使用 OkHttp 我們首先需要創(chuàng)建并獲得一個 OkHttpClient
,OkHttpClient
是 OkHttp 中十分重要的一個類,下面是官方在 Java Doc 中對它的介紹:
Factory for {@linkplain Call calls}, which can be used to send HTTP requests and read their
responses.OkHttpClients should be shared
OkHttp performs best when you create a single {@code OkHttpClient} instance and reuse it for
all of your HTTP calls. This is because each client holds its own connection pool and thread
pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a
client for each request wastes resources on idle pools.
根據(jù)官方對其的介紹可以看出,它是一個 Call
的工廠類,可以用它來生產(chǎn) Call
,從而通過 Call
來發(fā)起 HTTP Request 獲取 Response。
同時,官方推薦的使用方式是使用一個全局的 OkHttpClient
在多個類之間共享。因為每個 Client 都會有一個自己的連接池和線程池,復用 Client 可以減少資源的浪費。
它的構建采用了 Builder 模式,提供了許多可供我們配置的參數(shù):
public static final class Builder {
Dispatcher dispatcher;
@Nullable
Proxy proxy;
List<Protocol> protocols;
List<ConnectionSpec> connectionSpecs;
final List<Interceptor> interceptors = new ArrayList<>();
final List<Interceptor> networkInterceptors = new ArrayList<>();
EventListener.Factory eventListenerFactory;
ProxySelector proxySelector;
CookieJar cookieJar;
@Nullable
Cache cache;
@Nullable
InternalCache internalCache;
SocketFactory socketFactory;
@Nullable
SSLSocketFactory sslSocketFactory;
@Nullable
CertificateChainCleaner certificateChainCleaner;
HostnameVerifier hostnameVerifier;
CertificatePinner certificatePinner;
Authenticator proxyAuthenticator;
Authenticator authenticator;
ConnectionPool connectionPool;
Dns dns;
boolean followSslRedirects;
boolean followRedirects;
boolean retryOnConnectionFailure;
int callTimeout;
int connectTimeout;
int readTimeout;
int writeTimeout;
int pingInterval;
// ...
}
可以看到,它的可配置的參數(shù)還是非常多的。
構建了 OkHttpClient
之后,我們可以通過 OkHttpClient.newCall
方法根據(jù)我們傳入的 Request
創(chuàng)建對應的 Call
。
/**
* Prepares the {@code request} to be executed at some point in the future.
*/
@Override
public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
Request
Request
所對應的就是我們 HTTP 請求中的 Request,可以對它的 url、method、header 等在 Builder 中進行配置。
Request
的構建同樣采用了 Builder 模式進行構建:
public static class Builder {
@Nullable
HttpUrl url;
String method;
Headers.Builder headers;
@Nullable
RequestBody body;
// ...
}
構建完 Request
后,就可以調用 OkHttpClient.newCall
方法創(chuàng)建對應 Call
Call
構建
我們知道,newCall
方法中調用了 RealCall.newRealCall(this, request, false /* for web socket */);
,其中第三個參數(shù)代表是否使用 web socket。
我們看看 RealCall.newRealCall
方法:
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.transmitter = new Transmitter(client, call);
return call;
}
這里根據(jù)我們傳入的參數(shù)構建了一個 RealCall
對象,并根據(jù) client
構建了其 transmitter
。
RealCall
的構造函數(shù)中主要是一些賦值:
private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
this.client = client;
this.originalRequest = originalRequest;
this.forWebSocket = forWebSocket;
}
而 Transmitter
中也是一些賦值操作:
public Transmitter(OkHttpClient client, Call call) {
this.client = client;
this.connectionPool = Internal.instance.realConnectionPool(client.connectionPool());
this.call = call;
this.eventListener = client.eventListenerFactory().create(call);
this.timeout.timeout(client.callTimeoutMillis(), MILLISECONDS);
}
其中首先調用了 Internal.instance.realConnectionPool
方法,通過 client.connectionPool
獲取到了 RealConnectionPool
對象,之后調用了 client.eventListenerFactory().create(call)
方法構創(chuàng)建了其 eventListener
。
請求的發(fā)起
OkHttp 的執(zhí)行有兩種方式,enqueue
及 execute
,它們分別代表了異步請求與同步請求:
enqueue
:代表了異步請求,不會阻塞調用線程。需要我們傳入一個Callback
,當請求成功時,會回調其onResponse
方法,請求失敗時則會回調其onFailure
方法。execute
:代表了同步請求,會阻塞調用線程,請求結束后直接返回請求結果。
讓我們分別對其進行分析:
異步請求
我們先分析一下 enqueue
方法:
@Override
public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
// 通知eventListener
transmitter.callStart();
// 構建AsyncCall并分派任務
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
它首先調用了 transmitter.callStart
,最后調用到了之前構造的 eventListener
的 callStart
方法
之后它調用了 client.dispatcher().enqueue
方法,構建了一個 AsyncCall
對象后交給了 client.dispatcher
進行任務的分派。
executeOn
AsyncCall
類對外暴露了 executeOn
方法,Dispatcher
可以通過調用該方法并傳入 ExecutorService
使其在該線程池所提供的線程中發(fā)起 HTTP 請求,獲取 Response 并回調 Callback
的對應方法從而實現(xiàn)任務的調度。
void executeOn(ExecutorService executorService) {
assert (!Thread.holdsLock(client.dispatcher()));
boolean success = false;
try {
// 在對應的ExecutorService中執(zhí)行該AsyncCall
executorService.execute(this);
success = true;
} catch (RejectedExecutionException e) {
// 出現(xiàn)問題,調用Callback對應方法
InterruptedIOException ioException = new InterruptedIOException("executor rejected");
ioException.initCause(e);
transmitter.noMoreExchanges(ioException);
responseCallback.onFailure(RealCall.this, ioException);
} finally {
// 不論是否成功,通知Dispatcher請求完成
if (!success) {
client.dispatcher().finished(this); // This call is no longer running!
}
}
}
可以看出,AsyncCall
是一個 Runnable,我們看看它實現(xiàn)的 execute
方法:
@Override
protected void execute() {
boolean signalledCallback = false;
// 開始Timeout計時
transmitter.timeoutEnter();
try {
// 獲取Response
Response response = getResponseWithInterceptorChain();
signalledCallback = true;
// 請求成功,通知Callback
responseCallback.onResponse(RealCall.this, response);
} catch (IOException e) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
} else {
// 請求失敗,通知Callback
responseCallback.onFailure(RealCall.this, e);
}
} finally {
// 不論是否成功,通知Dispatcher請求完成
client.dispatcher().finished(this);
}
}
這里首先調用了 transmitter.timeoutEnter()
方法開始了 Timeout 的計時。
之后若請求成功,則會通過 getResponseWithInterceptorChain
方法獲取了 Response
,之后調用 Callback.onResponse
方法通知請求成功。
若請求失敗,會調用 Callback.onFailure
方法通知請求失敗。
看來網(wǎng)絡請求的核心實現(xiàn)在 getResponseWithInterceptorChain
方法中實現(xiàn),而 OkHttp 的超時機制與 transmitter.timeoutEnter
有關,我們暫時先不關注這些細節(jié)。
異步線程池
讓我們來看看 OkHttp 對異步請求采用了怎樣的線程池。調用者在 AsyncCall.executeOn
方法中傳入了 Dispatcher.executorService
方法的返回值,我們來到此方法:
public synchronized ExecutorService executorService() {
if (executorService == null) {
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
我們知道,這里的線程池是可以通過創(chuàng)建 Dispatcher
時指定的,若不指定,則這里會創(chuàng)建一個如上代碼中的線程池,我們來分析一下它的幾個參數(shù)。
- 核心線程數(shù)
corePoolSize
:保持在線程池中的線程數(shù),即使空閑后也不會保留。由于為 0,因此任何線程空閑時都不會被保留。 - 最大線程數(shù)
maximumPoolSize
:線程池最大支持創(chuàng)建的線程數(shù),這里指定了Integer.MAX_VALUE
。 - 線程存活時間
keepAliveTime
:線程空閑后所能存活的時間,若超過該時間就會被回收。這里指定的時間為 60 個時間單位(60s),也就是說線程在空閑超過 60s 后就會被回收。 - 時間單位
unit
:簽名的線程存活時間的單位,這里為TimeUnit.SECONDS
,也就是說秒 - 線程等待隊列
workQueue
:線程的等待隊列,里面的元素會按序排隊,依次執(zhí)行,這里和指定的是SynchronousQueue
。 - 線程工廠
threadFactory
:線程的創(chuàng)建工廠這里傳入的是Util.threadFactory
方法創(chuàng)建的線程工廠。
對于上面的幾個參數(shù),我們有幾個細節(jié)需要考慮一下:
為什么要采用 SynchronousQueue
首先我們先需要了解一下什么是 SynchronousQueue
,它雖然是一個隊列,但它內部不存在任何的容器,它采用了一種經(jīng)典的生產(chǎn)者-消費者模型,它有多個生產(chǎn)者和消費者,當一個生產(chǎn)線程進行生產(chǎn)操作(put)時,若沒有消費者線程進行消費(take),那么該線程會阻塞,直到有消費者進行消費。也就是說,它僅僅實現(xiàn)了一個傳遞的操作,這種傳遞功能由于沒有了中間的放入容器,再從容器中取出的過程,因此是一種快速傳遞元素的方式,這對于我們網(wǎng)絡請求這種高頻請求來說,是十分合適的。關于 SynchronousQueue
可以看這篇文章: java并發(fā)之SynchronousQueue實現(xiàn)原理
為什么線程池采用這種線程數(shù)量不設上限,每個線程空閑時只存活很短時間的策略
實際上在 OkHttp 的設計中,將線程的個數(shù)的維護工作不再交給線程池,而是由 Dispatcher
進行實現(xiàn),通過外部所設置的 maxRequests
及 maxRequestsPerHost
來調整等待隊列及執(zhí)行隊列,從而實現(xiàn)對線程最大數(shù)量的控制。具體 Dispatcher
的實現(xiàn)在本文后面會講到。
同步請求
execute
我們接著看到 execute
方法,看看同步請求的執(zhí)行:
@Override
public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
transmitter.timeoutEnter();
transmitter.callStart();
try {
// 通知Dispatcher
client.dispatcher().executed(this);
// 獲取Response
return getResponseWithInterceptorChain();
} finally {
// 不論是否成功,通知Dispatcher請求完成
client.dispatcher().finished(this);
}
}
它首先調用了 Dispatcher.executed
方法,通知 Dispatcher 該 Call 被執(zhí)行,之后調用到了 getResponseWithInterceptorChain
方法獲取 Response,不論是否成功都會調用 Dispatcher.finished
通知 Dispatcher 該 Call 執(zhí)行完成。
Dispatcher 任務調度
enqueue
我們看看 Dispatcher 是如何調度異步請求的,來到 Dispatcher.enqueue
方法:
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) {
// 尋找同一個host的Call
AsyncCall existingCall = findExistingCallWithHost(call.host());
// 復用Call的callsPerHost
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
}
}
// 嘗試執(zhí)行等待隊列中的任務
promoteAndExecute();
}
這里先將其加入了 readAsyncCalls
這一等待隊列中。
之后調用了 findExistingCallWithHost
方法嘗試尋找 host 相同的 Call
,它會遍歷 readyAsyncCalls
及 runningAsyncCalls
兩個隊列尋找 host 相同的 Call。
若找到了對應的 Call
,則會調用 call.reuseCallsPerHostFrom
來復用這個 Call 的 callsPerHost
,從而便于統(tǒng)計一個 host 對應的 Call
的個數(shù),它是一個 AtomicInteger
。
最后會調用 promoteAndExecute
方法,這個方法會嘗試將等待隊列中的任務執(zhí)行。
executed
我們繼續(xù)看看 Dispatcher 是如何調度同步請求的,來到 Dispatcher.executed
方法:
synchronized void executed(RealCall call) {
runningSyncCalls.add(call);
}
這里很簡單,直接將該同步任務加入了執(zhí)行隊列 runningSyncCalls
中。
promoteAndExecute
我們看到 promoteAndExecute
方法:
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();
// 增加host對應執(zhí)行中call的數(shù)量
asyncCall.callsPerHost().incrementAndGet();
executableCalls.add(asyncCall);
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;
}
在這個方法中遍歷了 readyAsyncCalls
隊列,不斷地尋找能夠執(zhí)行的 AsynCall
,若找到則會在最后統(tǒng)一調用 AsyncCall.executeOn
方法在自己的 executorService
線程池中執(zhí)行該 Call。其中,執(zhí)行中的任務不能超過 maxRequests
。
finished
我們從前面 AsyncCall
的實現(xiàn)可以看出,每次請求完成后,不論成功失敗,都會調用到 finished
方法通知 Dispatcher 請求結束:
void finished(AsyncCall call) {
// 減少host對應執(zhí)行中call的數(shù)量
call.callsPerHost().decrementAndGet();
finished(runningAsyncCalls, call);
}
它調用到了 finished
的另一個重載:
private <T> void finished(Deque<T> calls, T call) {
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
idleCallback = this.idleCallback;
}
// 嘗試執(zhí)行等待隊列中的任務
boolean isRunning = promoteAndExecute();
if (!isRunning && idleCallback != null) {
idleCallback.run();
}
}
可以看到,這里再次調用了 promoteAndExecute
方法嘗試執(zhí)行等待隊列中的任務,若當前等待隊列中沒有需要執(zhí)行的任務,說明目前還比較空閑,沒有到達設定的 maxRequests
。此時會調用 idleCallback.run
執(zhí)行一些空閑 Callback
(這種設計有點類似 Handler
的 IdleHandler
機制,充分利用了一些空閑資源,值得我們學習)。
小結
可以看出,OkHttp 的任務的調度器的設計將請求分別分至了兩個隊列中,分別是等待隊列及執(zhí)行隊列。
每次加入新的異步請求時,都會先將其加入等待隊列,之后遍歷等待隊列嘗試執(zhí)行等待任務。
每次加入新的同步請求時,都會直接將其加入執(zhí)行隊列。
而每當一個請求完成時,都會通知到 Dispatcher,Dispatcher 會遍歷準備隊列嘗試執(zhí)行任務,若沒有執(zhí)行則說明等待隊列是空的,則會調用 idleCallback.run
執(zhí)行一些空閑時的任務,類似 Handler 的 IdleHandler 機制。
(在多線程下載器中的任務調度器就用到了這里的 Dispatcher 的設計)
響應的獲取
從前面的同步和異步請求中都可以看出,響應的獲取的核心實現(xiàn)是 RealCall.getResponseWithInterceptorChain
方法:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
// 初始化攔截器列表
List<Interceptor> interceptors = new ArrayList<>();
// 用戶自定義的 Interceptor
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) {
// 用戶自定義的網(wǎng)絡 Interceptor
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);
}
}
}
這個方法非常重要,短短幾行代碼就實現(xiàn)了對請求的所有處理,它體現(xiàn)了 OkHttp 中一個很重要的核心設計——攔截器機制。
它首先在 interceptors
中加入了用戶自定義的攔截器,之后又按順序分別加入了各種系統(tǒng)內置的攔截器。
之后通過 RealInterceptorChain
的構造 函數(shù)構造了一個 Chain
對象,之后調用了其 proceed
方法,從而得到了該請求的 Response。
那么這個過程中究竟是如何獲取到 Response 的呢?讓我們先理解一下 OkHttp 的攔截器機制。
攔截器機制概述
OkHttp 的網(wǎng)絡請求的過程就是依賴于各種攔截器(Interceptor)實現(xiàn)的,我們先看看 Interceptor
的定義:
/**
* Observes, modifies, and potentially short-circuits requests going out and the corresponding
* responses coming back in. Typically interceptors add, remove, or transform headers on the request
* or response.
*/
public interface Interceptor {
Response intercept(Chain chain) throws IOException;
interface Chain {
Request request();
Response proceed(Request request) throws IOException;
/**
* Returns the connection the request will be executed on. This is only available in the chains
* of network interceptors; for application interceptors this is always null.
*/
@Nullable
Connection connection();
Call call();
int connectTimeoutMillis();
Chain withConnectTimeout(int timeout, TimeUnit unit);
int readTimeoutMillis();
Chain withReadTimeout(int timeout, TimeUnit unit);
int writeTimeoutMillis();
Chain withWriteTimeout(int timeout, TimeUnit unit);
}
}
Interceptor
實際上是一個接口,里面只有一個方法 intercept
以及一個接口 Chain
。
Interceptor
其中,intercept
方法往往是如下的結構:
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
// Request階段,該攔截器在Request階段負責做的事情
// 調用RealInterceptorChain.proceed(),其實是在遞歸調用下一個攔截器的intercept()方法
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
// Response階段,完成了該攔截器在Response階段負責做的事情,然后返回到上一層的攔截器。
return response;
}
這里先調用了 chain.request
方法獲取到了本次請求的 Request
對象,
之后調用了 chain.proceed
方法遞歸調用下一個攔截器的 interceptor
方法。
最后返回了 chain.proceed
方法所返回的 Response
。
上面簡單的三行代碼將整個 intercept
過程分為了兩個階段:
- Request 階段:執(zhí)行一些該攔截器在 Request 階段所負責的事情
- Response 階段:完成該攔截器在 Response 階段所負責的事情
這其實是采用了一種遞歸的設計,類似我們計算機網(wǎng)絡中的分層模型,將 OkHttp 的請求分為了幾個階段,分別代表了不同的攔截器,不同攔截器分別會在這個遞歸的過程中有兩次對該請求的處理的可能,一次是在 Request 之前,一次是在 Response 之后,中間的過程中若出現(xiàn)了錯誤,則通過拋出異常來通知上層。
預置的 Interceptor
有如下幾種:
- RetryAndFollowUpInterceptor:負責實現(xiàn)重定向功能
- BridgeInterceptor:將用戶構造的請求轉換為向服務器發(fā)送的請求,將服務器返回的響應轉換為對用戶友好的響應
- CacheInterceptor:讀取緩存、更新緩存
- ConnectInterceptor:建立與服務器的連接
- CallServerInterceptor:從服務器讀取響應
可以看出,整個網(wǎng)絡請求的過程由各個攔截器互相配合從而實現(xiàn),通過這種攔截器的機制,可以很方便地調節(jié)網(wǎng)絡請求的過程及先后順序,同時也能夠很方便地使用戶對其進行擴展。
其中用戶可以在兩個時機插入 Interceptor:
- 網(wǎng)絡請求前后:通過
OkHttpClient.addInterceptor
方法添加 - 讀取響應前后:通過
OkHttpClient.addNetworkInterceptor
方法添加
其整體流程如圖所示:
RealInterceptorChain
我們再看看是如何通過 RealInterceptorChain
將整個攔截器的調用過程連接起來的,我們先看看其構造過程:
public RealInterceptorChain(List<Interceptor> interceptors, Transmitter transmitter,
@Nullable Exchange exchange, int index, Request request, Call call,
int connectTimeout, int readTimeout, int writeTimeout) {
this.interceptors = interceptors;
this.transmitter = transmitter;
this.exchange = exchange;
this.index = index;
this.request = request;
this.call = call;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}
這里只是一些賦值過程,我們接著看到 chain.proceed
方法,看看它是如何執(zhí)行的:
public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
throws IOException {
// ...
// 構建下一個Interceptor的Chain
RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
// 獲取當前Interceptor并執(zhí)行intercept方法
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// ...
return response;
}
這里省略了一些異常處理,可以看到它首先構造了下一個攔截器對應的 Chain,之后獲取到了當前的攔截器并調用了其 intercept
方法獲取其結果,在 intercept
方法的參數(shù)中傳入的就是下一個攔截器對應的 Chain
。
通過這種遞歸的設計,從而實現(xiàn)了從上到下,再從下到上這樣一個遞與歸的過程,從而十分漂亮地實現(xiàn)了 HTTP 請求的全過程。
這是一種類似責任鏈模式的實現(xiàn),這樣的實現(xiàn)在網(wǎng)絡請求的過程中十分常見,也十分值得我們去學習。
小結
OkHttp 在讀取響應的過程中采用了一種責任鏈模式,預置了多個負責不同功能的攔截器,將它們通過責任鏈連接在一起,采用了一種遞歸的方式進行調用,從而使得每一層在請求前和響應后都能對本次請求作出不同的處理,通過各個攔截器的協(xié)調合作,最終完成了整個網(wǎng)絡請求的過程。