责任链模式及其在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阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容

  • 设计模式概述 在学习面向对象七大设计原则时需要注意以下几点:a) 高内聚、低耦合和单一职能的“冲突”实际上,这两者...
    彦帧阅读 3,737评论 0 14
  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,915评论 2 89
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,639评论 18 139
  • 2009年的平安夜,我收到了他的苹果,我记得那晚我很开心,我期待着圣诞节我们的开场白。果真,那天出乎意料的美好,我...
    张小坏zh阅读 209评论 0 0
  • 正则:/^(13[0-9]|14[5-9]|15[012356789]|166|17[0-8]|18[0-9]|1...
    _韩小妖阅读 499评论 0 0