OkHttp原始碼之責任鏈模式
對於okhttp來說,它的攔截器各位肯定是非常熟悉的,正是由於它的存在,okhttp的擴充套件性極強,對於呼叫方來說可謂是非常友好。而實現這個攔截器的就是大名鼎鼎的責任鏈模式了,其實整個okhttp的核心功能都是基於這個攔截器的,由各種不同的攔截器實現的。所以今天我們分析下okhttp的責任鏈模式。
基本原始碼
對於okhttp的Call來說,發起請求最終其實都呼叫了一個方法獲取到Response的,同步非同步都是一樣的:
Response getResponseWithInterceptorChain() throws IOException { // Build a full stack of interceptors. List<Interceptor> interceptors = new ArrayList<>(); interceptors.addAll(client.interceptors()); interceptors.add(retryAndFollowUpInterceptor); interceptors.add(new BridgeInterceptor(client.cookieJar())); interceptors.add(new CacheInterceptor(client.internalCache())); interceptors.add(new ConnectInterceptor(client)); if (!forSocket/">WebSocket) { interceptors.addAll(client.networkInterceptors()); } interceptors.add(new CallServerInterceptor(forWebSocket)); Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); return chain.proceed(originalRequest); }
前面只是加入一些必要的攔截器,是一些具體功能的實現,我們今天並不是分析這些具體功能,而是分析這種特殊的架構,所以這些攔截器就略過,我們重點看這裡
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); return chain.proceed(originalRequest);
核心就是構造了一個chain,這個chain我們重點關注的是interceptors和index(此時是第一個節點,傳入的是0),然後呼叫chain.proceed()方法獲取最終結果。
那麼我們繼續跟進去:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { //此處省略其他程式碼 // 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); Response response = interceptor.intercept(next); //此處省略其他程式碼 return response; })
可以看到,每一個chain都是鏈條上的一個節點,chain的proceed()方法首先是獲取下一個節點,然後獲取當前節點對應的攔截器,將下一個節點當做引數給了攔截器的intercept()方法,在interceptor中會手動呼叫下一個chain的chain.proceed方法,將這條鏈走下去。這裡有一點要注意,此處是先獲取的下一個chain,也就是說我們在第n個攔截器中會呼叫第n+1個chain的proceed()方法,這樣才能做到請求之前新增自定義行為(呼叫第n+1個chain的proceed()之前)和請求之後(呼叫第n+1個chain的proceed()之後)新增自定義行為。
現有結構疑問
現在,大家應該都知道這條鏈是怎麼傳下去的,但大家可能會感到很好奇,為什麼這裡會有兩個角色,interceptor和chain,而且這兩個結構似乎不是傳統意義上的client和handler的角色(經典的責任鏈模式主要是這兩個角色),因為這裡interceptor中會呼叫chain的方法,這一行為似乎有點反常。由於暴露給外界的介面其實是interceptor,這裡我們嘗試拋棄chain看會怎樣,下面是一個連結串列結構的經典責任鏈實現:
public class InterceptorChain { Response proceed(Request request){ //此處意味著拿到第一個節點,具體怎麼拿到不用管,僅僅做示意 Interceptor first = getFirstInterceptor(); return first.intercept(request); } public abstract classInterceptor{ protected Interceptor next; abstract Response intercept(Request request); void setNext(Interceptor interceptor){ next = interceptor; } } }
經典的責任鏈兩個角色,client(此處的InterceptorChain)和handler(此處的Interceptor)
此處我們嘗試去實現okhttp的日誌攔截器,主要實現:
- 請求引數獲取
- 請求結果獲取
public class LogInterceptor extends Interceptor{ @Override Response intercept(Request request) { //此處getRequestParams是虛擬碼,僅做示意 Map<String,String> requestParams = request.getRequestParams(); Response response= next.intercept(request); //此處getResult是虛擬碼,僅做示意 String result = response.getResult(); return response; } }
可以看到,沒有chain這個類,我們只是藉助interceptor也是能實現現有的結構的。那麼這兩種結構有本質區別嗎?我們再看下正常的okhttp的日誌攔截器:
public class LogInterceptor2 implements okhttp3.Interceptor{ @Override public Response intercept(Chain chain) throws IOException { //此處getRequestParams是虛擬碼,僅做示意 Request request = chain.request(); Map<String,String> requestParams = request.getRequestParams(); Response response= chain.proceed(request); //此處getResult是虛擬碼,僅做示意 String result = response.getResult(); return response; } }
可以看到程式碼幾乎一模一樣,只是一個直接呼叫interceptor實現,一個通過chain來實現的,可見本質區別和責任鏈中的handler角色沒有關係,那麼我們對比下client角色的實現,
首先是我們自己DIY的:
public class InterceptorChain { Response proceed(Request request){ //此處意味著拿到第一個節點,具體怎麼拿到不用管,僅僅做示意 Interceptor first = getFirstInterceptor(); return first.intercept(request); } }
然後看下okhttp的:
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException { if (index >= interceptors.size()) throw new AssertionError(); calls++; // If we already have a stream, confirm that the incoming request will use it. if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) { throw new IllegalStateException("network interceptor " + interceptors.get(index - 1) + " must retain the same host and port"); } // If we already have a stream, confirm that this is the only call to chain.proceed(). if (this.httpCodec != null && calls > 1) { throw new IllegalStateException("network interceptor " + interceptors.get(index - 1) + " must call proceed() exactly once"); } // 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); Response response = interceptor.intercept(next); // Confirm that the next interceptor made its required call to chain.proceed(). if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) { throw new IllegalStateException("network interceptor " + interceptor + " must call proceed() exactly once"); } // Confirm that the intercepted response isn't null. if (response == null) { throw new NullPointerException("interceptor " + interceptor + " returned null"); } if (response.body() == null) { throw new IllegalStateException( "interceptor " + interceptor + " returned a response with no body"); } return response; }
總體看下來,本質區別其實就一個,okhttp的proceed()方法會被執行多次(在每個interceptor中都會呼叫chain.proceed()獲取Response),每一個interceptor都會執行該方法,所以在該方法中我們可以對interceptor的實現做很多檢查,做出一些約束。然而我們自己DIY的,這些檢查和約束必須由interceptor的實現人員手動去做,然而開發人員素質參差不齊,如果漏了的話就會留下隱患。
總結
okhttp的這套責任鏈模式的實現,從程式碼上來看確實複雜了不少,理解起來沒那麼容易,但是,這套模式能夠對每個interceptor的行為做出一些基本的規範和檢查,而不是都扔給interceptor的實現人員去做,這點收益就大了,稍微難理解一點也是可以的。
最後,這些都是我自己揣測的,如果有錯誤,希望大家指出,共同進步