淺談設計模式——責任鏈模式(OKHttp中的責任鏈模式)

一、 什么是責任鏈模式

責任鏈, 顧名思義是將多個節點通過鏈條的方式連接起來,每一個節點相當于一個對象,而每一個對象層層相關,直接或者間接引用下一個對象(節點);直到鏈條中有一個節點處理頭節點傳下來的事件截止。

二、責任鏈模式使用場景(以下摘自 Android源碼設計模式)

有一事件,可以被多個對象同時處理,但是由哪個對象處理則在運行時動態決定!
在請求處理者不明確時向多個對象中提交一個請求。
動態指定一組對象處理請求

三、責任鏈模式UML圖

責任鏈模式.png

客戶端發出請求,調用抽象類Handler中的方法處理邏輯業務。對象ConcreteHandler1與ConcreteHandler2繼承Handler,其中ConcreteHandler1中持有下一個節點ConcreteHandler2的引用;事件由1對象發出,如果其處理不了,則交由2對象處理! 這是簡單的責任鏈模式結構圖,下面使用代碼的方式展現:

Handler.class

/**
 * 抽象類
 */
public abstract class Handler {

    /**
     * 下一代處理者
     */
    public Handler nextProcessor;

    /**
     * 每一個實現類處理
     * 
     * @param msg
     */
    public abstract void handleRequest(String msg);
    
}

Processor1.class


/**
 * 處理者1
 */
public class Processor1 extends Handler {
    @Override
    public void handleRequest(String msg) {
        if(msg.equals("Processor1")) {
            System.out.println("第一個處理者處理");
        } else {
            nextProcessor.handleRequest(msg);
        }
    }
}

Processor2.class


/**
 * 處理者2
 */
public class Processor2 extends Handler {
    @Override
    public void handleRequest(String msg) {
        if(msg.equals("Processor2")) {
            System.out.println("第二個處理者處理");
        } else {
            nextProcessor.handleRequest(msg);
        }
    }
}

測試方法:

@Test
public void testProcessor() {
    Processor1 processor1 = new Processor1();
    Processor2 processor2 = new Processor2();

    processor1.nextProcessor = processor2;
    processor2.nextProcessor = processor1;

    processor1.handleRequest("Processor2");
    }

// 運行結果:
第二個處理者處理
Process finished with exit code 0

四、OKHttp中的責任鏈模式

<font color="red" size=1> 摘自百度百科</font>
android網絡框架之OKhttp
一個處理網絡請求的開源項目,是安卓端最火熱的輕量級框架,由移動支付Square公司貢獻(該公司還貢獻了Picasso)
用于替代HttpUrlConnection和Apache HttpClient(android API23 6.0里已移除HttpClient,現在已經打不出來)

在使用OKHttp之前,我們可能用到更多的網絡請求是Async-Http,一種用于異步處理網絡的框架,或者更加直接的是使用android自帶的HttpUrlConnection 和 HttpClient ,對其進行簡單的封裝; OKHttp開源出之后,幾乎大部分項目都使用到這個開源框架,它有如下有點:

1. 官方在6.0以后添加了OKHttp
2. okHttp支持SPDY

// 同時能進行的最大請求數
private int maxRequests = 64;
// 同時請求的相同HOST的最大個數
private int maxRequestsPerHost = 5;

okhttp3.Dispatcher.class 中定義了這兩個變量,并發數可以支持到64,當然這兩個數值是可以自定義的,這說明OKHttp是支持SPDY的(<font color="#666666" size=1>谷歌開發的基于TCP的應用層協議,用于最小化網絡延遲,提升網絡速度,優化用戶的網絡使用體驗. SPDY并不是一種替代http的協議,只是對http的一種增強</font>)

—— OKHttp的使用 同步獲取

private final OkHttpClient client = new OkHttpClient();
 
public void run() throws Exception {
    // 創建Request
    Request request = new Request.Builder()
        .url("http://www.baidu.com/")
        .build();
 
    // 獲取到結果
    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
    System.out.println(response.body().string());
}
—— 異步獲取
private final OkHttpClient client = new OkHttpClient();
 
public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://www.baidu.com/")
        .build();
 
    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Request request, Throwable throwable) {
        throwable.printStackTrace();
      }
 
      @Override public void onResponse(Response response) throws IOException {
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
 
        Headers responseHeaders = response.headers();
        System.out.println(response.body().string());
      }
    });
}

使用過程很簡單,創建一個OKHttpClient, 創建Request對象,使用同步方法順序獲取或者使用回調CallBack方法異步獲取數據;執行的方法主要是client中newCall方法和enqueue方法,

——下面我們分析其中的源碼:
/**
* 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 */);
}

newCall 方法需要傳一個Request,Request對象使用了構建者模式將請求方法,請求體,請求頭進行了封裝; newCall 獲取到了Call 這個接口:

public interface Call extends Cloneable {
  /** 獲取到最開始的request */
  Request request();

  /** 執行請求,獲取到Response */
  Response execute() throws IOException;

  void enqueue(Callback responseCallback);

  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  Call clone();

  interface Factory {
    Call newCall(Request request);
  }
}

而這個接口的 實現類只有 <font color="red">okhttp3.RealCall.class</font> ,接下來我們看下他的excute() 方法:


  @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結果
      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);
    }
  }
—— Dispatcher

在講解責任鏈之前,我們先看下Dispatcher調度器中有些什么?
[圖片上傳失敗...(image-10e627-1536636467207)]

可以知道,它有三個雙端隊列,

// 雙端隊列,支持首尾兩端 雙向開口可進可出
    
    /**
     * 準備運行的異步隊列
     * 
     */
    private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

    // 正在運行的異步
    private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

    // 正在執行的同步隊列
    private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

隊列中線程使用線程池:

    /**
     * 線程池的方式啟動線程,使用懶加載的方式
     */
    private @Nullable ExecutorService executorService;
    
    public synchronized ExecutorService executorService() {
        if (executorService == null) {
            //TODO 線程池
            //TODO 核心線程 最大線程 非核心線程閑置60秒回收 任務隊列
            executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
                    new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher",
                    false));
        }
        return executorService;
    }

而上面client執行調度器中的excute方法,實際上就是將當前請求直接添加到這個同步的雙端隊列中,等待線程池中的隊列被執行!

—— getResponseWithInterceptorChain()

接下來就要執行攔截器了,而攔截器中就是使用了我們今天所知道的責任鏈模式,上面的責任鏈模式已經說的很清晰了,一環接著一環,一個對象持有下個對象的引用;我們看OKHttp中的責任鏈模式是怎樣寫的,點擊進入該方法:

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    //責任鏈 實際上是像遞歸一樣倒敘執行
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    //5、重試與重定向
    interceptors.add(retryAndFollowUpInterceptor);
    // 4、請求頭等信息
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //3、緩存配置 根據條件(存在響應緩存并被設置為不變的或者響應在有效期內)返回緩存響應設置請求頭(If-None-Match、If-Modified-Since等) 服務器可能返回304(未修改)
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //2、連接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    //1、流操作(寫出請求體、獲得響應數據)進行http請求報文的封裝與請求報文的解析
    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);
  }

可以看到,getResponseWithInterceptorChain() 方法,是將5個攔截器添加到鏈表中,再新建了一個RealInterceptorChain.class 類,然后執行了我們責任鏈中抽象處理類的處理方法 proceed,這里是使用了接口的形式:

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);
  }
}

所以,責任現在都交給了RealInterceptorChain, 上面直接調用了Interceptor.Chain接口中的 proceed方法,我們看下他的實現:

 public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
      
      .....

    // 創建新的攔截鏈,鏈中的攔截器集合index+1
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    //執行當前的攔截器 默認是:retryAndFollowUpInterceptor
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    ......
    
    return response;
  }

主要是上面三行代碼,首先拿到下一個 攔截器,上面添加的第一個攔截器是 retryAndFollowUpInterceptor (重試與重定向)攔截器,然后將下一個攔截器傳入到重試與重定向攔截器中,看看intercept這個方法在實現類中做的操作:

@Override public Response intercept(Chain chain) throws IOException {

    Request request = chain.request();
    // 首先拿到當前真實的Interceptor 實現類
    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 {
        //執行到一半,又去執行了RealInterceptorChain中的proceed方法
        //實際上就是下一個攔截器
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        .....
      }
   }

這個過程其實就是遞歸的過程,而底就是CallServerInterceptor ,這里不對攔截器作詳細的講解,每個攔截器做的處理邏輯都差不多,下面我們看下這個過程的圖解:


OKHttp中的責任鏈.jpg
總結:

OKHttp中使用攔截器對不同的業務進行區分,我們也可以使用自己的自定義攔截器
其中的責任鏈模式其實和我們設計模式中的有區別,這里是將分發處理給了接口,讓其去處理

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容

  • 設計模式概述 在學習面向對象七大設計原則時需要注意以下幾點:a) 高內聚、低耦合和單一職能的“沖突”實際上,這兩者...
    彥幀閱讀 3,770評論 0 14
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,826評論 18 139
  • 用兩張圖告訴你,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 12,812評論 2 59
  • 大蔥、小蔥、白洋蔥、紅洋蔥,都是廚房必備,作用卻天差地別,而它們的區別你清楚么。 烹飪方法 大蔥 大蔥別名和事草,...
    每日養生妙招閱讀 2,353評論 0 0
  • 上一章 第三十二章 韓秋香屈辱告知強與朝 仨兄弟商議擺治呂二蛋 呂二蛋走了以后,韓秋香并沒有象...
    林木成蔭閱讀 391評論 2 6