責任鏈模式及其在flowable源碼中的應用

責任鏈模式(Chain of Responsibility Pattern),簡而言之,就是把具有能力處理某種任務的類組合成這個鏈,這個鏈一般是有頭有尾的,在此我們稱鏈中的每一個類為節點,當需要處理的請求到達鏈頭時,就會執行節點的處理邏輯,每個節點的處理情況可分為三種:

  1. 當前節點處理完并直接返回(一般是到尾節點)
  2. 當前節點不作任何處理傳給下一個節點
  3. 當前節點進行相關處理后再傳給下一個節點

最近在閱讀開源工作流項目flowable的源碼時發現大量應用了責任鏈模式,本文先模擬日志級別處理來介紹一個簡單的責任鏈模式,然后再看flowable源碼中的實現。

1. 責任鏈模式之日志級別處理

先以我們比較熟悉的日志系統為例,假如現在我們要實現一個有Debug, Info, Warn, Error四個級別的日志系統,需要滿足以下條件:

a. 系統啟動時可以配置日志級別,打印日志時可以設置日志的級別。

b. 日志級別從小到大依次為Debug, Info, Warn, Error, 且只有當配置日志級別小于或等于某類日志級別時,才會打印相應級別的日志,否則不打印。

備注: 為了方便判斷,我們分別用1,2,3,4來表示這四個日志級別。

用責任鏈實現的日志系統類圖如下:

image-20181205201713155

首先定義一個接口,如下:

public interface Logger {
    /**
     * 打印日志的方法
     * @param message  具體內容
     * @param msgLevel 日志級別
     * @throws Exception 當打印消息的日志級別不在范圍內時拋出異常
     */
    void excute(String message, int msgLevel) throws Exception;

    /**
     * 獲取日志類的下一個節點
     * @return
     */
    Logger getNext();

    /**
     * 設置日志類的下一個節點,以形成責任鏈
     * @param logger
     * @throws Exception
     */
    void setNext(Logger logger) throws Exception;
}

然后定義一個實現上面接口的抽象類:

public abstract class AbstractLogger implements Logger{
    /**
     * 日志類的下一個節點
     */
    protected Logger next;

    /**
     * 系統的日志級別
     */
    protected int configLevel;

    public AbstractLogger(int configLevel) throws Exception {
        if(configLevel < 1 || configLevel > 4){
            throw new Exception("Unsupported logger config level, only 1 to 4 is allowed!");
        }
        this.configLevel = configLevel;
    }

    @Override
    public Logger getNext() {
        return next;
    }

    @Override
    public void setNext(Logger logger) throws Exception {
        this.next = logger;
    }

    /**
     * 提供給子類用的公共方法
     * @param message
     */
    protected void print(String message){
        System.out.println(message);
    }
}

然后是四個級別對應的日志實現類:

首先是責任鏈中第一個節點:

public class ErrorLogger extends AbstractLogger {
    public static final int level = 4;

    public ErrorLogger(int configLevel) throws Exception {
        super(configLevel);
    }

    @Override
    public void excute(String message, int msgLevel) throws Exception {
        if(msgLevel == level){
            print("Logger::Error: " + message);
        }else if(msgLevel >= configLevel){
            next.excute(message, msgLevel);
        }
    }
}

第二個節點:

public class InfoLogger extends AbstractLogger {
    public static final int level = 2;

    public InfoLogger(int configLevel) throws Exception {
        super(configLevel);
    }

    @Override
    public void excute(String message, int msgLevel) throws Exception {
        if(msgLevel == level){
            print("Logger::Info: " + message);
        }else if(msgLevel >= configLevel){
            next.excute(message, msgLevel);
        }
    }
}

第三個節點:

public class WarnLogger extends AbstractLogger {
    public static final int level = 3;

    public WarnLogger(int configLevel) throws Exception {
        super(configLevel);
    }

    @Override
    public void excute(String message, int msgLevel) throws Exception {
        if(msgLevel == level){
            print("Logger::Warn: " + message);
        }else if(msgLevel >= configLevel){
            next.excute(message, msgLevel);
        }
    }
}

最后一個節點:

public class DebugLogger extends AbstractLogger {
    public static final int level = 1;

    public DebugLogger(int configLevel) throws Exception {
        super(configLevel);
    }

    @Override
    public void excute(String message, int msgLevel) throws Exception {
        if(msgLevel == level){
            print("Logger::Debug: " + message);
        }
    }

    /**
     * Debug為最低的級別,在責任鏈中沒有下一個節點
     * @return
     */
    @Override
    public Logger getNext() {
        return null;
    }

    /**
     * 當嘗試給Debug日志類設置下一個節點時拋出異常
     * @param logger
     * @throws Exception
     */
    @Override
    public void setNext(Logger logger) throws Exception {
        throw new Exception("Next Logger in not Allowed in here!");
    }
}

測試類的代碼如下:

public class ChainPatternDemo {
    /**
     * 設置的系統的日志級別,組合日志處理責任鏈,返回責任鏈的首個節點
     * @param level 系統的日志級別
     * @return
     * @throws Exception
     */
    public static Logger initLogConfig(int level) throws Exception {
        try{
            Logger root = new ErrorLogger(level);
            Logger warnLogger = new WarnLogger(level);
            Logger infoLogger = new InfoLogger(level);
            Logger debugLogger = new DebugLogger(level);

            root.setNext(warnLogger);
            warnLogger.setNext(infoLogger);
            infoLogger.setNext(debugLogger);
            return root;
        }catch (Exception e){
            throw e;
        }
    }

    public static void main(String[] args) {
        try{
            Logger logger = initLogConfig(4);

            logger.excute("error message", 4);
            logger.excute("warn message", 3);
            logger.excute("info message", 2);
            logger.excute("debug message", 1);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
    }
}

通過Logger logger = initLogConfig(4)中的數字設置日志級別,然后嘗試打印所有級別的日志, 當設置為1即最低時,輸出如下, 打印所有日志,符合預期

Logger::Error: error message
Logger::Warn: warn message
Logger::Info: info message
Logger::Debug: debug message

當設置日志級別為2時,輸出如下,只打印info及以上級別的日志,符合預期:

Logger::Error: error message
Logger::Warn: warn message
Logger::Info: info message

當設置日志級別為4即最高時,輸出如下,只打印Error級別的日志,符合預期:

Logger::Error: error message

所有的日志處理都會通過定義的責任鏈進行處理,每個責任鏈中的節點只有在需要時才進行處理或者傳遞給下一個節點處理。

2. flowable中的責任鏈模式

如下,以RuntimeServiceImpl為例, 在flowable源碼中很多這樣的調用,這就是典型的責任鏈模式的應用。

image-20181202165433545

此處關鍵在于實現了ServiceImpl這個類,如下所示,flowable的幾大主要類都實現了該接口。

image-20181202165920687

相關的類結構圖如下:

?
image-20181202170818344

ServiceImpl類有CommandExecutor類型的成員變量,而CommandExecutor的實現類又有CommandInterceptor類型的成員變量,實現CommandInterceptor的抽象類中的成員變量next也是一個CommandInterceptor類,通過next既可以實現責任鏈模式,AbstractCommandInterceptor的如下實現類將作為責任鏈中的節點。

image.png

那么責任鏈的前后關系又是怎樣的呢,這里我們就要查看ProcessEngineConfigurationImpl的源碼了,這個類的initInterceptorChain方法即為責任鏈的初始處理邏輯,如下:

public CommandInterceptor initInterceptorChain(List<CommandInterceptor> chain) {
        if (chain == null || chain.isEmpty()) {
            throw new FlowableException("invalid command interceptor chain configuration: " + chain);
        }
        for (int i = 0; i < chain.size() - 1; i++) {
            chain.get(i).setNext(chain.get(i + 1));
        }
        return chain.get(0);
    }

可以看到責任鏈的前后關系是按照列表中的順序的,所以關鍵點在于傳參,找到調用這個方法的地方:

public void initCommandExecutor() {
        if (commandExecutor == null) {
            CommandInterceptor first = initInterceptorChain(commandInterceptors);
            commandExecutor = new CommandExecutorImpl(getDefaultCommandConfig(), first);
        }
    }

參數commandInterceptors是ProcessEngineConfigurationImpl類的一個成員變量,所以接下來要看看哪里初始化這個成員變量,如下:

public void initCommandInterceptors() {
        if (commandInterceptors == null) {
            commandInterceptors = new ArrayList<CommandInterceptor>();
            if (customPreCommandInterceptors != null) {
                commandInterceptors.addAll(customPreCommandInterceptors);
            }
            commandInterceptors.addAll(getDefaultCommandInterceptors());
            if (customPostCommandInterceptors != null) {
                commandInterceptors.addAll(customPostCommandInterceptors);
            }
            commandInterceptors.add(commandInvoker);
        }
    }

    public Collection<? extends CommandInterceptor> getDefaultCommandInterceptors() {
        List<CommandInterceptor> interceptors = new ArrayList<CommandInterceptor>();
        interceptors.add(new LogInterceptor());

        CommandInterceptor transactionInterceptor = createTransactionInterceptor();
        if (transactionInterceptor != null) {
            interceptors.add(transactionInterceptor);
        }

        if (commandContextFactory != null) {
            interceptors.add(new CommandContextInterceptor(commandContextFactory, this));
        }

        if (transactionContextFactory != null) {
            interceptors.add(new TransactionContextInterceptor(transactionContextFactory));
        }

        return interceptors;
    }

先添加自定義前置節點,再添加默認節點(先是LogInterceptor, 然后createTransactionInterceptor(),接著CommandContextInterceptor,還有一個成員變量transactionContextFactory),再添加自定義后置節點,最后還加加上CommandInvoker。所以我們如果沒有特殊配置的話這條鏈依次會有LogInterceptor, TransactionContextInterceptor(和使用環境有關,在Spring下為SpringTransactionInterceptor), CommandContextInterceptor,TransactionContextInterceptor, CommandInvoker這五個節點。通過調試源碼可以驗證我們的分析,如下:

image-20181202174009381

接著runtimeService也會進行相關初始化,其中責任鏈結構如下:

image-20181202174243229

查看前四個節點,如下以LogInterceptor為例,肯定會有next.execute(config, command);, 這也是保證責任鏈能夠往下走的決定因素。

image-20181202190717182

接下來再看看最后一個節點CommandInvoker的源碼,關鍵是執行了command的execute方法,沒有next.execute了。

image-20181202191155755

同時可以發現getNext()返回為null, 即獲取不到下一個節點,并且在嘗試設置下一個節點時會拋出異常,這也正是責任鏈鏈尾節點應有的特征。

image-20181202191355899

由此也可以看出所有業務邏輯的處理最終會通過comand.execute來完成,所以接下來就是要搞清楚Command的處理邏輯,具體請待下一篇分析。

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

推薦閱讀更多精彩內容

  • 設計模式概述 在學習面向對象七大設計原則時需要注意以下幾點:a) 高內聚、低耦合和單一職能的“沖突”實際上,這兩者...
    彥幀閱讀 3,762評論 0 14
  • 關于Mongodb的全面總結 MongoDB的內部構造《MongoDB The Definitive Guide》...
    中v中閱讀 31,985評論 2 89
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,775評論 18 139
  • 2009年的平安夜,我收到了他的蘋果,我記得那晚我很開心,我期待著圣誕節我們的開場白。果真,那天出乎意料的美好,我...
    張小壞zh閱讀 212評論 0 0
  • 正則:/^(13[0-9]|14[5-9]|15[012356789]|166|17[0-8]|18[0-9]|1...
    _韓小妖閱讀 507評論 0 0