OkHttp3 攔截器源碼分析

來源:互聯網
上載者:User

標籤:資訊   assert   out   部落格   ide   非同步請求   List 集合   throw   lease   

OkHttp 內建攔截器

在這篇部落格 OkHttp3 攔截器(Interceptor) ,我們已經介紹了攔截器的作用,攔截器是 OkHttp 提供的對 Http 請求和響應進行統一處理的強大機制,它可以實現網路監聽、請求以及響應重寫、請求失敗充實等功能。
同時也瞭解了攔截器可以被連結起來使用,我們可以註冊自訂的攔截器(應用攔截器和網路攔截器)到攔截器鏈上,如:

實際上除了我們自訂的攔截器外,OkHttp 系統內部還提供了幾種其他的攔截器,就是中 OkHttp core 的部分。OkHttp 內部的攔截器各自負責不同的功能,每一個功能就是一個 Interceptor,這些攔截器串連起來形成了一個攔截器鏈,最終也就完成了一次網路請求。
具體如:

在上一篇部落格 OkHttp3 源碼分析 中,我們分析了 OkHttp 的同步和非同步請求的流程源碼,發現無論是同步請求還是非同步請求都是通過調用 RealCall 的 getResponseWithInterceptorChain() 方法來擷取 response 響應的。
RealCall. getResponseWithInterceptorChain()源碼:

Response getResponseWithInterceptorChain() throws IOException {    List<Interceptor> interceptors = new ArrayList();    //添加自訂的應用攔截器    interceptors.addAll(this.client.interceptors());    //負責重新導向和失敗重試的攔截器    interceptors.add(this.retryAndFollowUpInterceptor);    //橋接網路層和應用程式層,就是為使用者所建立的請求補充添加一些服務端還必需的 http 要求標頭等    interceptors.add(new BridgeInterceptor(this.client.cookieJar()));    //負責讀取緩衝,更新緩衝    interceptors.add(new CacheInterceptor(this.client.internalCache()));    //負責與服務端建立串連    interceptors.add(new ConnectInterceptor(this.client));    //配置自訂的網路攔截器    if (!this.forWebSocket) {        interceptors.addAll(this.client.networkInterceptors());    }    //向服務端發送請求,從服務端讀取響應資料    interceptors.add(new CallServerInterceptor(this.forWebSocket));    //建立 攔截器鏈chain 對象,這裡將各種攔截器的 List 集合傳了進去    Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest, this, this.eventListener, this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis());    //通過鏈式請求得到 response    return chain.proceed(this.originalRequest);}

在這個方法中,我們就發現了 OkHttp 內建的這幾種攔截器,這幾種攔截器的具體作用稍後再說,先來宏觀的分析一下 getResponseWithInterceptorChain() 做了些什麼工作:

  1. 建立了一系列的攔截器,並將其放入一個攔截器 List 集合中。
  2. 將攔截器的 List 集合傳入 RealInterceptorChain 的構造方法中,建立出一個攔截器鏈 RealInterceptorChain 。
  3. 執行攔截器鏈 chain 的 proceed() 方法來依次調用每個不同功能的攔截器,最終擷取響應。

那麼這個 Chain 對象到底是如何處理攔截器集合的呢,為什麼通過調用 chain.proceed 就能得到被攔截器鏈依次處理之後的 response 呢?
其實這個問題的答案就是責任鏈設計模式,建議先瞭解一下關於責任鏈模式的介紹,再回頭往下看。

在理解了責任鏈模式之後,我們就能比較容易的理解攔截器是如何工作的了。
首先來看一看 Interceptor 介面,很明顯的它就是責任鏈模式中的抽象處理者角色了,各種攔截器都需要實現它的 intercept 方法

/** * 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;    ...  }}

這裡我們注意到 Interceptor 還包含了一個內部介面 Chain,通過查看 Chain 介面,也可以大概瞭解它的功能:

  1. 通過 request() 方法來擷取 request 請求
  2. 通過 proceed(request) 方法來處理 request 請求,並返回 response 響應

剛剛也介紹了在 getResponseWithInterceptorChain() 方法中,正是由 Chain 來依次調用攔截器來擷取 response 的:

    //建立 攔截器鏈chain 對象,這裡將各種攔截器的 List 集合傳了進去    Chain chain = new RealInterceptorChain(interceptors, (StreamAllocation)null, (HttpCodec)null, (RealConnection)null, 0, this.originalRequest, this, this.eventListener, this.client.connectTimeoutMillis(), this.client.readTimeoutMillis(), this.client.writeTimeoutMillis());    //通過鏈式請求得到 response    return chain.proceed(this.originalRequest);

那麼它具體是怎麼工作的呢?我們先來看一下RealInterceptorChain 的構造方法

public final class RealInterceptorChain implements Interceptor.Chain {  private final List<Interceptor> interceptors;  private final StreamAllocation streamAllocation;  private final HttpCodec httpCodec;  private final RealConnection connection;  private final int index;  private final Request request;  private final Call call;  private final EventListener eventListener;  private final int connectTimeout;  private final int readTimeout;  private final int writeTimeout;  private int calls;  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,      HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,      EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {    this.interceptors = interceptors;    this.connection = connection;    this.streamAllocation = streamAllocation;    this.httpCodec = httpCodec;    this.index = index;    this.request = request;    this.call = call;    this.eventListener = eventListener;    this.connectTimeout = connectTimeout;    this.readTimeout = readTimeout;    this.writeTimeout = writeTimeout;  }

需要特別注意的是這個構造方法裡的 index 參數,傳入給構造方法的 index 最終被賦值給了一個全域變數 index(這個變數很重要,之後會被使用到)。在構造出了 RealInterceptorChain 對象之後,接著就調用它的 proceed 方法來執行攔截器了。
來看一下 chain.proceed(request) 方法的具體實現:
RealInterceptorChain#proceed:

  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,      RealConnection connection) throws IOException {    if (index >= interceptors.size()) throw new AssertionError();    calls++;        ...    // Call the next interceptor in the chain.    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,        writeTimeout);    //從攔截器集合中擷取當前攔截器    Interceptor interceptor = interceptors.get(index);    //調用當前的攔截器的 intercept 方法擷取 response    Response response = interceptor.intercept(next);    ...    return response;  }

這個方法的關鍵邏輯在這與這三行代碼
一, 在 chain.proceed 的方法中,又 new 了一個 RealInterceptorChain,不過這裡傳入的參數是 index + 1,也就是說,每次調用 proceed 方法,都會產生出一個 index成員變數 +1的 RealInterceptorChain 對象。而且該 chain 對象的名字為 next,所以我們大致也能猜測一下它代表的是下一個 chain 對象。

    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,        writeTimeout);

二, 根據 index 索引值擷取當前攔截器,這個 index 就是之前建立 chain 建構函式時的 index 值
大家應該還記得在 getResponseWithInterceptorChain 第一次建立 Chain 對象時,index被初始化為0。

Interceptor interceptor = interceptors.get(index);

三, 調用當前攔截器的 intercept(Chain chain) 方法

Response response = interceptor.intercept(next);

這裡我們就以 index 為 0 為例,擷取 interceptors 集合中的第一個攔截器 RetryAndFollowUpInterceptor(假設沒有添加使用者自訂的應用攔截器),來看一下它的 intercept 方法:

  @Override public Response intercept(Chain chain) throws IOException {    Request request = chain.request();    RealInterceptorChain realChain = (RealInterceptorChain) chain;    Call call = realChain.call();    EventListener eventListener = realChain.eventListener();    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),        createAddress(request.url()), call, eventListener, callStackTrace);    this.streamAllocation = streamAllocation;    int followUpCount = 0;    Response priorResponse = null;    while (true) {      if (canceled) {        streamAllocation.release();        throw new IOException("Canceled");      }      Response response;      boolean releaseConnection = true;      try {        response = realChain.proceed(request, streamAllocation, null, null);        releaseConnection = false;      } catch (RouteException e) {        // The attempt to connect via a route failed. The request will not have been sent.        if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {          throw e.getFirstConnectException();        }        releaseConnection = false;        continue;      } catch (IOException e) {        // An attempt to communicate with a server failed. The request may have been sent.        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);        if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;        releaseConnection = false;        continue;      } finally {        // We‘re throwing an unchecked exception. Release any resources.        if (releaseConnection) {          streamAllocation.streamFailed(null);          streamAllocation.release();        }      }    ...    }  }

這段代碼中最關鍵的地方是:

response = realChain.proceed(request, streamAllocation, null, null);

我們發現,原來在 intercept() 中又會調用 chain.proceed() 方法,而每次調用 proceed 方法中又會去擷取一個索引為 index + 1 的下一個攔截器,並執行該攔截器的 intercept() 方法,就是這樣相互的遞迴調用,實現了對攔截器的逐步調用。
這個過程流程圖如下:

到這裡也許我們會有一個疑問,那就是為什麼每次都需要建立一個新的 RealInterceptorChain 對象,只需要修改 index 變數的值不是也能實現同樣的效果嗎?這裡的原因是 RealInterceptorChain 對象中還包含了 request 請求資訊在內的其他資訊,而每次執行攔截器的 intercept 方法時,因為遞迴調用的緣故,本層 的 intercept 並沒有被執行完,如果複用 RealInterceptorChain 對象,則其他層次會對本層次 RealInterceptorChain 對象產生影響。

參考
79643123
79008433

OkHttp3 攔截器源碼分析

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.