利用責任鏈模式設計一個攔截器

前言

近期在作 Cicada 的攔截器功能,正好用到了責任鏈模式。前端

這個設計模式在平常使用中頻率仍是挺高的,藉此機會來分析分析。java

責任鏈模式

先來看看什麼是責任鏈模式。git

引用一段維基百科對其的解釋:github

責任鏈模式在面向對象程式設計裏是一種軟件設計模式,它包含了一些命令對象和一系列的處理對象。每個處理對象決定它能處理哪些命令對象,它也知道如何將它不能處理的命令對象傳遞給該鏈中的下一個處理對象。該模式還描述了往該處理鏈的末尾添加新的處理對象的方法。設計模式

光看這段描述可能你們會以爲懵,簡單來講就是該設計模式用於對某個對象或者請求進行一系列的處理,這些處理邏輯正好組成一個鏈條。app

下面來簡單演示使用與不使用責任鏈模式有什麼區別和優點。ide

責任鏈模式的應用

傳統實現

假設這樣的場景:傳入了一段內容,須要對這段文本進行加工;好比過濾敏感詞、錯別字修改、最後署上版權等操做。函數

常見的寫法以下:this

public class Main {
    public static void main(String[] args) {
        String msg = "內容內容內容" ;

        String result = Process.sensitiveWord()
                .typo()
                .copyright();
    }
}
複製代碼

這樣看似沒啥問題也能解決需求,但若是我還須要爲爲內容加上一個統一的標題呢?在現有的方式下就不得不新增處理方法,而且是在這個客戶端(Process)的基礎上進行新增。spa

顯然這樣的擴展性很差。

責任鏈模式實現

這時候就到了責任鏈模式發揮做用了。

該需求很是的符合對某一個對象、請求進行一系列處理的特徵。

因而咱們將代碼修改:

這時 Process 就是一個接口了,用於定義真正的處理函數。

public interface Process {

    /** * 執行處理 * @param msg */
    void doProcess(String msg) ;
}
複製代碼

同時以前對內容的各類處理只須要實現該接口便可:

public class SensitiveWordProcess implements Process {
    @Override
    public void doProcess(String msg) {
        System.out.println(msg + "敏感詞處理");
    }
}

public class CopyrightProcess implements Process {

    @Override
    public void doProcess(String msg) {
        System.out.println(msg + "版權處理");
    }
}

public class CopyrightProcess implements Process {

    @Override
    public void doProcess(String msg) {
        System.out.println(msg + "版權處理");
    }
}
複製代碼

而後只須要給客戶端提供一個執行入口以及添加責任鏈的入口便可:

public class MsgProcessChain {

    private List<Process> chains = new ArrayList<>() ;

    /** * 添加責任鏈 * @param process * @return */
    public MsgProcessChain addChain(Process process){
        chains.add(process) ;
        return this ;
    }

    /** * 執行處理 * @param msg */
    public void process(String msg){
        for (Process chain : chains) {
            chain.doProcess(msg);
        }
    }
}
複製代碼

這樣使用起來就很是簡單:

public class Main {
    public static void main(String[] args) {
        String msg = "內容內容內容==" ;

        MsgProcessChain chain = new MsgProcessChain()
                .addChain(new SensitiveWordProcess())
                .addChain(new TypoProcess())
                .addChain(new CopyrightProcess()) ;

        chain.process(msg) ;
    }
}
複製代碼

當我須要再增長一個處理邏輯時只須要添加一個處理單元便可(addChain(Process process)),並對客戶端 chain.process(msg) 是無感知的,不須要作任何的改動。

可能你們沒有直接寫過責任鏈模式的相關代碼,但不經意間使用到的卻很多。

好比 Netty 中的 pipeline 就是一個典型的責任鏈模式,它可讓一個請求在整個管道中進行流轉。

經過官方圖就能夠很是清楚的看出是一個責任鏈模式:

用責任鏈模式設計一個攔截器

對於攔截器來講使用責任鏈模式再好不過了。

下面來看看在 Cicada 中的實現:

首先是定義了和上文 Process 接口相似的 CicadaInterceptor 抽象類:

public abstract class CicadaInterceptor {

    public boolean before(CicadaContext context,Param param) throws Exception{
        return true;
    }

    public void after(CicadaContext context,Param param) throws Exception{}
}
複製代碼

同時定義了一個 InterceptProcess 的客戶端:

其中的 loadInterceptors() 會將全部的攔截器加入到責任鏈中。

再提供了兩個函數分別對應了攔截前和攔截後的入口:

實際應用

如今來看看具體是怎麼使用的吧。

在請求的 handle 中首先進行加載(loadInterceptors(AppConfig appConfig)),也就是初始化責任鏈。

接下來則是客戶端的入口;調用攔截先後的入口方法便可。

因爲是攔截器,那麼在 before 函數中是能夠對請求進行攔截的。只要返回 false 就不會繼續向後處理。因此這裏作了一個返回值的判斷。

同時對於使用者來講只須要建立攔截器類繼承 CicadaInterceptor 類便可。

這裏作了一個演示,分別有兩個攔截器:

  1. 記錄一個業務 handle 的執行時間。
  2. after 裏打印了請求參數。
  3. 同時可在第一個攔截器中返回 false 讓請求被攔截。

先來作前兩個試驗:


這樣當我請求其中一個接口時會將剛纔的日誌打印出來:


接下來我讓打印執行時間的攔截器中攔截請求,同時輸入向前端輸入一段文本:


請求接口能夠看到以下內容:

同時後面的請求參數也沒有打印出來,說明請求確實被攔截下來。


同時我也能夠調整攔截順序,只須要在@Interceptor(order = 1) 註解中定義這個 order 屬性便可(默認值是 0,越小越先執行)。

以前是打印請求參數的攔截器先執行,此次我手動將它的 order 調整爲 2,而打印時間的 order 爲 1 。

再次請求接口觀察後臺日誌:

發現打印執行時間的攔截器先執行。

那這個執行執行順序如何實現自定義配置的呢?

其實也比較簡單,有如下幾步:

  • 在加載攔截器時將註解裏的 order 保存起來。
  • 設置攔截器到責任鏈中時經過反射將 order 的值保存到各個攔截器中。
  • 最終經過排序從新排列這個責任鏈的順序。

貼一些核心代碼。

掃描攔截器時保存 order 值:


保存 order 值到攔截器中:


從新對責任鏈排序:

總結

整個責任鏈模式已經講完,但願對這個設計模式還不瞭解的朋友帶來些幫助。

上文中的源碼以下:

歡迎關注公衆號一塊兒交流:

相關文章
相關標籤/搜索