詳細解析責任鏈設計模式和相關運用,比如事件分發機制,okHttp的攔截器運用。
別的先不說,上圖,個人覺的這張簡單的圖可以說清楚責任鏈模式的相關,即對象A產生了一個問題,它交給對象B去處理,而B其實只是一個抽象Process 定義了相關處理問題抽象方法, 不具備真正的處理能力,而其子類可以實際真正的去處理,可能B有多個子類B1,B2.,B3.... 到底這些哪個可以處理其實不知道,於是就先交給B1(處理數學問題),B1無法解決就給B2(處理化學問題),依次類推,這樣就形成了一個鏈條,而且B1,B2,B3 各司其職都有自己擅長的領域,都只能處理各自領域的問題,所以就相當於各自必須處理各自領域的問題,相對於有了責任,所以,整體看來就是形成了一個責任鏈條,於是就叫了責任鏈模式。
那我們用僞代碼描述就可以
public abstract class Process {
private boolean isConsume;
public void handleRequest() {
if (isConsume) {
// 如果當前節點可以處理,直接處理
doSomething();
} else {
// 如果當前節點不能處理,並且有下個節點,交由下個節點處理
if (null != nextProcess) {
nextProcess.handleRequest();
}
}
}
------------------------------------------------------------------------------------
// 下一個責任節點
private Process nextProcess;
public void setNextProcess(Process nextProcess) {
this.nextProcess = nextProcess;
}
abstract protected void doSomething();
}
此時,我們歸納一下責任鏈模式的兩點:
- 處理機制(各個處理者處理事件規則)
- 如何把事件向下傳(事件在處理者之間如何流轉)
處理機制(各個處理者處理事件規則)
public void handleRequest() {
if (isConsume) {
// 如果當前節點可以處理,直接處理
doSomething();
} else {
// 如果當前節點不能處理,並且有下個節點,交由下個節點處理
if (null != nextProcess) {
nextProcess.handleRequest();
}
}
}
顯然,上面代碼就是處理規則。看到僞代碼分割線以上的部分其實很容易想到Android事件分發的僞代碼,其實二者是一樣的,所以我們說Android的事件傳遞機制也是責任鏈設計模式。
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean consume = false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
}else {
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
對比一看,確實是一樣的。就是第一個處理者接到請求後,進行判斷自己是否能夠處理,不能則向下一個處理者傳遞,否則自己處理掉。
另外,還要說明一下,責任鏈模式核心就是同一個請求可以給多個具有不同處理能力的處理者,每個處理者只能處理自己能力範圍之內的請求,滿足這個描述其實都可以看作爲責任鏈,這樣就會出現很多的變體,比如說處理者是否有序、請求被處理後則流程結束還是繼續被其他的處理者處理,再不同的場景可以用不同的方式,靈活看待。
如何把事件向下傳(事件在處理者之間如何流轉)
對於這點,上面也提到了——就是持有下一個處理器的引用,並去調用下一個處理者的處理方法
else {
// 如果當前節點不能處理,並且有下個節點,交由下個節點處理
if (null != nextProcess) {
nextProcess.handleRequest();
}
}
// 下一個責任節點
private Process nextProcess;
public void setNextProcess(Process nextProcess) {
this.nextProcess = nextProcess;
}
這是一種思路,我們看看okhttp的攔截器的責任鏈怎麼設計的?對於如何使用,大家都知道:
private MyOkhttpClient(){
//加入過濾器 及公共參數
//實現日誌打印
okHttpClientBuilder = new OkHttpClient.Builder()
//.addInterceptor(new RequestInterceptor(httpClientType & 2))
.connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.MILLISECONDS)
.addInterceptor(new RequestInterceptor(1));
if (HttpUtil.HTTP_LOG){
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new MyLogging());
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
okHttpClientBuilder.addInterceptor(httpLoggingInterceptor);
//okHttpClientBuilder.addInterceptor(new LoggingInterceptor());
}
}
顯然,使用很簡單,不知道大家有沒有想過爲什麼對於OkhttpClinet.builder 添加這樣一個Interceptor就可以實現日誌打印功能呢?對此,我們從使用okhttp 開始研究:
OkHttpClient client = new OkHttpClient();//創建OkHttpClient對象
Request request = new Request.Builder()
.url("http://www.baidu.com")//請求接口。如果需要傳參拼接到接口後面。
.build();//創建Request 對象
Response response = null;
response = client.newCall(request).execute();
ok, 我們看到最後一行代碼 client.newCall(request).execute(); 這裏點擊execute方法,
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
關注 getResponseWithInterceptorChain();
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 (!forWebSocket) {
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);
}
這裏就對okhttp裏面內置的攔截器(處理器)進行添加,並構造了一個鏈條chain,然後調用chain.proceed(request);再點擊該方法會發現到了一個 接口定義類
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();
.......
}
在繼續跟Reponse proceed(Request request) 方法,到了 RealIntercepteorChain ,見明知義,這個類纔是真正的處理者,定義處理請求的規則。繼續跟進:
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().
.......
// 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;
}
看到這個方法,我們知道這就是真正我們想要找的方法了,這個類包含之前的攔截器鏈條 interceptors ,第一行的代碼含義是攔截器數量檢查,第二行call ++ 意思是當前攔截器的計數器,通過計數器的方式替代 interceptors for循環調用攔截器的proceed() 方法,當你看到
Interceptor interceptor = interceptors.get(index);
這句代碼的時候你就知道這個計數器的作用了。其實我之前一直再找for循環,但一直都沒有找到,欸。固化思維!好了到此就分析完okhttp攔截器的攔截的原理了(後續補一個圖....)
對於okhttp攔截器的其他方面,看官網 ,有各種攔截器的詳細介紹。
至此,我們還可以繼續深挖Okhttp的責任鏈模式,比如對於請求公共參數的添加是怎麼實現的? 添加多個攔截器會怎麼樣,攔截器之間會有影響嗎?這些都是可以繼續研究,研究的越多,你理解的就越深。