淺談設(shè)計模式——責(zé)任鏈模式(OKHttp中的責(zé)任鏈模式)

一、 什么是責(zé)任鏈模式

責(zé)任鏈掐暮, 顧名思義是將多個節(jié)點通過鏈條的方式連接起來滋饲,每一個節(jié)點相當(dāng)于一個對象,而每一個對象層層相關(guān)查库,直接或者間接引用下一個對象(節(jié)點);直到鏈條中有一個節(jié)點處理頭節(jié)點傳下來的事件截止黄琼。

二樊销、責(zé)任鏈模式使用場景(以下摘自 Android源碼設(shè)計模式)

有一事件,可以被多個對象同時處理脏款,但是由哪個對象處理則在運(yùn)行時動態(tài)決定围苫!
在請求處理者不明確時向多個對象中提交一個請求。
動態(tài)指定一組對象處理請求

三撤师、責(zé)任鏈模式UML圖

責(zé)任鏈模式.png

客戶端發(fā)出請求剂府,調(diào)用抽象類Handler中的方法處理邏輯業(yè)務(wù)。對象ConcreteHandler1與ConcreteHandler2繼承Handler剃盾,其中ConcreteHandler1中持有下一個節(jié)點ConcreteHandler2的引用腺占;事件由1對象發(fā)出淤袜,如果其處理不了,則交由2對象處理衰伯! 這是簡單的責(zé)任鏈模式結(jié)構(gòu)圖铡羡,下面使用代碼的方式展現(xiàn):

Handler.class

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

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

    /**
     * 每一個實現(xiàn)類處理
     * 
     * @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");
    }

// 運(yùn)行結(jié)果:
第二個處理者處理
Process finished with exit code 0

四、OKHttp中的責(zé)任鏈模式

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

在使用OKHttp之前意鲸,我們可能用到更多的網(wǎng)絡(luò)請求是Async-Http烦周,一種用于異步處理網(wǎng)絡(luò)的框架,或者更加直接的是使用android自帶的HttpUrlConnection 和 HttpClient 怎顾,對其進(jìn)行簡單的封裝读慎; OKHttp開源出之后,幾乎大部分項目都使用到這個開源框架槐雾,它有如下有點:

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

// 同時能進(jìn)行的最大請求數(shù)
private int maxRequests = 64;
// 同時請求的相同HOST的最大個數(shù)
private int maxRequestsPerHost = 5;

okhttp3.Dispatcher.class 中定義了這兩個變量夭委,并發(fā)數(shù)可以支持到64,當(dāng)然這兩個數(shù)值是可以自定義的蚜退,這說明OKHttp是支持SPDY的(<font color="#666666" size=1>谷歌開發(fā)的基于TCP的應(yīng)用層協(xié)議,用于最小化網(wǎng)絡(luò)延遲,提升網(wǎng)絡(luò)速度,優(yōu)化用戶的網(wǎng)絡(luò)使用體驗. SPDY并不是一種替代http的協(xié)議,只是對http的一種增強(qiáng)</font>)

—— OKHttp的使用 同步獲取

private final OkHttpClient client = new OkHttpClient();
 
public void run() throws Exception {
    // 創(chuàng)建Request
    Request request = new Request.Builder()
        .url("http://www.baidu.com/")
        .build();
 
    // 獲取到結(jié)果
    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());
      }
    });
}

使用過程很簡單闰靴,創(chuàng)建一個OKHttpClient, 創(chuàng)建Request對象钻注,使用同步方法順序獲取或者使用回調(diào)CallBack方法異步獲取數(shù)據(jù);執(zhí)行的方法主要是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對象使用了構(gòu)建者模式將請求方法,請求體泵肄,請求頭進(jìn)行了封裝; newCall 獲取到了Call 這個接口:

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

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

  void enqueue(Callback responseCallback);

  void cancel();

  boolean isExecuted();

  boolean isCanceled();

  Call clone();

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

而這個接口的 實現(xiàn)類只有 <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 {
      // 將本次請求添加到事件調(diào)度器中
      client.dispatcher().executed(this);
        
      // 今天的主角腐巢, 責(zé)任鏈獲取到Response結(jié)果
      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

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

可以知道冯丙,它有三個雙端隊列肉瓦,

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

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

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

隊列中線程使用線程池:

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

而上面client執(zhí)行調(diào)度器中的excute方法泞莉,實際上就是將當(dāng)前請求直接添加到這個同步的雙端隊列中,等待線程池中的隊列被執(zhí)行船殉!

—— getResponseWithInterceptorChain()

接下來就要執(zhí)行攔截器了鲫趁,而攔截器中就是使用了我們今天所知道的責(zé)任鏈模式,上面的責(zé)任鏈模式已經(jīng)說的很清晰了利虫,一環(huán)接著一環(huán)挨厚,一個對象持有下個對象的引用堡僻;我們看OKHttp中的責(zé)任鏈模式是怎樣寫的,點擊進(jìn)入該方法:

  Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    //責(zé)任鏈 實際上是像遞歸一樣倒敘執(zhí)行
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    //5疫剃、重試與重定向
    interceptors.add(retryAndFollowUpInterceptor);
    // 4钉疫、請求頭等信息
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //3、緩存配置 根據(jù)條件(存在響應(yīng)緩存并被設(shè)置為不變的或者響應(yīng)在有效期內(nèi))返回緩存響應(yīng)設(shè)置請求頭(If-None-Match慌申、If-Modified-Since等) 服務(wù)器可能返回304(未修改)
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //2陌选、連接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    //1、流操作(寫出請求體蹄溉、獲得響應(yīng)數(shù)據(jù))進(jìn)行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 類役电,然后執(zhí)行了我們責(zé)任鏈中抽象處理類的處理方法 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);
  }
}

所以棉胀,責(zé)任現(xiàn)在都交給了RealInterceptorChain法瑟, 上面直接調(diào)用了Interceptor.Chain接口中的 proceed方法,我們看下他的實現(xiàn):

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

    // 創(chuàng)建新的攔截鏈唁奢,鏈中的攔截器集合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);
    //執(zhí)行當(dāng)前的攔截器 默認(rèn)是:retryAndFollowUpInterceptor
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    ......
    
    return response;
  }

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

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

    Request request = chain.request();
    // 首先拿到當(dāng)前真實的Interceptor 實現(xiàn)類
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();
    // 核心 協(xié)調(diào)連接、請求/響應(yīng)以及復(fù)用
    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 {
        //執(zhí)行到一半脊奋,又去執(zhí)行了RealInterceptorChain中的proceed方法
        //實際上就是下一個攔截器
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        .....
      }
   }

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


OKHttp中的責(zé)任鏈.jpg
總結(jié):

OKHttp中使用攔截器對不同的業(yè)務(wù)進(jìn)行區(qū)分,我們也可以使用自己的自定義攔截器
其中的責(zé)任鏈模式其實和我們設(shè)計模式中的有區(qū)別久又,這里是將分發(fā)處理給了接口巫延,讓其去處理

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市籽孙,隨后出現(xiàn)的幾起案子烈评,更是在濱河造成了極大的恐慌,老刑警劉巖犯建,帶你破解...
    沈念sama閱讀 222,865評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件讲冠,死亡現(xiàn)場離奇詭異,居然都是意外死亡适瓦,警方通過查閱死者的電腦和手機(jī)竿开,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評論 3 399
  • 文/潘曉璐 我一進(jìn)店門谱仪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人否彩,你說我怎么就攤上這事疯攒。” “怎么了列荔?”我有些...
    開封第一講書人閱讀 169,631評論 0 364
  • 文/不壞的土叔 我叫張陵敬尺,是天一觀的道長。 經(jīng)常有香客問我贴浙,道長砂吞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,199評論 1 300
  • 正文 為了忘掉前任崎溃,我火速辦了婚禮蜻直,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘袁串。我一直安慰自己概而,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,196評論 6 398
  • 文/花漫 我一把揭開白布囱修。 她就那樣靜靜地躺著赎瑰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪破镰。 梳的紋絲不亂的頭發(fā)上乡范,一...
    開封第一講書人閱讀 52,793評論 1 314
  • 那天,我揣著相機(jī)與錄音啤咽,去河邊找鬼。 笑死渠脉,一個胖子當(dāng)著我的面吹牛宇整,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芋膘,決...
    沈念sama閱讀 41,221評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼鳞青,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了为朋?” 一聲冷哼從身側(cè)響起臂拓,我...
    開封第一講書人閱讀 40,174評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎习寸,沒想到半個月后胶惰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,699評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡霞溪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,770評論 3 343
  • 正文 我和宋清朗相戀三年孵滞,在試婚紗的時候發(fā)現(xiàn)自己被綠了中捆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,918評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡坊饶,死狀恐怖泄伪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情匿级,我是刑警寧澤蟋滴,帶...
    沈念sama閱讀 36,573評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站痘绎,受9級特大地震影響津函,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜简逮,卻給世界環(huán)境...
    茶點故事閱讀 42,255評論 3 336
  • 文/蒙蒙 一球散、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧散庶,春花似錦蕉堰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至须教,卻和暖如春皿渗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背轻腺。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評論 1 274
  • 我被黑心中介騙來泰國打工乐疆, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贬养。 一個月前我還...
    沈念sama閱讀 49,364評論 3 379
  • 正文 我出身青樓挤土,卻偏偏與公主長得像,于是被迫代替她去往敵國和親误算。 傳聞我的和親對象是個殘疾皇子仰美,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,926評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 設(shè)計模式概述 在學(xué)習(xí)面向?qū)ο笃叽笤O(shè)計原則時需要注意以下幾點:a) 高內(nèi)聚、低耦合和單一職能的“沖突”實際上儿礼,這兩者...
    彥幀閱讀 3,752評論 0 14
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理咖杂,服務(wù)發(fā)現(xiàn),斷路器蚊夫,智...
    卡卡羅2017閱讀 134,719評論 18 139
  • 用兩張圖告訴你诉字,為什么你的 App 會卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 12,754評論 2 59
  • 大蔥漠趁、小蔥扁凛、白洋蔥、紅洋蔥闯传,都是廚房必備谨朝,作用卻天差地別,而它們的區(qū)別你清楚么甥绿。 烹飪方法 大蔥 大蔥別名和事草字币,...
    每日養(yǎng)生妙招閱讀 2,339評論 0 0
  • 上一章 第三十二章 韓秋香屈辱告知強(qiáng)與朝 仨兄弟商議擺治呂二蛋 呂二蛋走了以后,韓秋香并沒有象...
    林木成蔭閱讀 383評論 2 6