Activiti工做流學習筆記(四)——工做流引擎中責任鏈模式的創建與應用原理

原創/朱季謙html

本文須要必定責任鏈模式的基礎,主要分紅三部分講解:java

1、簡單理解責任鏈模式概念
2、Activiti工做流裏責任鏈模式的創建
3、Activiti工做流裏責任鏈模式的應用mysql


1、簡單理解責任鏈模式概念

網上關於責任鏈模式的介紹不少,菜鳥教程上是這樣說的:責任鏈模式(Chain of Responsibility Pattern)爲請求建立了一個接收者對象的鏈。在這種模式中,一般每一個接收者都包含對另外一個接收者的引用。若是一個對象不能處理該請求,那麼它會把相同的請求傳給下一個接收者,依此類推。面試

這個概念術語比較抽象。sql

我曾經在 深刻理解Spring Security受權機制原理 一文中提到Spring Security在受權過程當中有使用到過濾器的概念,過濾器鏈就像一條鐵鏈,中間的每一個過濾器都包含對另外一個過濾器的引用,從而把相關的過濾器連接起來,像一條鏈的樣子。這時請求線程就如螞蟻同樣,會沿着這條鏈一直爬過去-----即,經過各過濾器調用另外一個過濾器引用方法chain.doFilter(request, response),實現一層嵌套一層地將請求傳遞下去,當該請求傳遞到能被處理的的過濾器時,就會被處理,處理完成後轉發返回。經過過濾器鏈,可實如今不一樣的過濾器當中對請求request作處理,且過濾器之間彼此互不干擾。數據庫

整個流程大體以下:設計模式

image

這個過濾器鏈的概念,其實就是責任鏈設計模式在Spring Security中的體現。架構

摘錄一段網上關於職責鏈模式介紹,其主要包含如下角色:框架

  1. 抽象處理者(Handler)角色:定義一個處理請求的接口,包含抽象處理方法和一個後繼鏈接。
  2. 具體處理者(Concrete Handler)角色:實現抽象處理者的處理方法,判斷可否處理本次請求,若是能夠處理請求則處理,不然將該請求轉給它的後繼者。
  3. 客戶類(Client)角色:建立處理鏈,並向鏈頭的具體處理者對象提交請求,它不關心處理細節和請求的傳遞過程。

2、Activiti工做流裏責任鏈模式的建立

最近在研究Activiti工做流框架,發現其全部實現都是採用命令模式實現,而命令模式當中的Invoker角色又是採用攔截器鏈式模式,即相似上面提到的過濾器鏈,即設計模式裏的責任鏈模式。post

這裏的Activiti工做流版本是6.0。

CommandInterceptor是一個攔截器接口,包含三個方法:

  • setNext()方法是在初始化時,設置每一個攔截器對象中包含了下一個攔截器對象,最後造成一條攔截器鏈;
  • getNext()可在每一個攔截器對象中調用下一個攔截器對象;
  • execute()是每一個攔截器對請求的處理。若在上一個攔截器鏈式裏不能處理該請求話,就會經過next.execute(CommandConfig var1, Command var2)將請求傳遞到下一個攔截器作處理,相似上面過濾器裏調用下一個過濾器的chain.doFilter(request, response)方法,將請求進行傳遞;
public interface CommandInterceptor {
    <T> T execute(CommandConfig var1, Command<T> var2);

    CommandInterceptor getNext();

    void setNext(CommandInterceptor var1);
}

抽象類AbstractCommandInterceptor實現了CommandInterceptor攔截器接口,在責任鏈模式當中充當抽象處理者(Handler)角色。該類最主要的屬性是 protected CommandInterceptor next,在同一包下,直接經過next便可調用下一個攔截器對象。

public abstract class AbstractCommandInterceptor implements CommandInterceptor {
    protected CommandInterceptor next;
    public AbstractCommandInterceptor() {
    }
    public CommandInterceptor getNext() {
        return this.next;
    }
    public void setNext(CommandInterceptor next) {
        this.next = next;
    }
}

接下來,將會分析攔截器鏈是如何初始化與工做的。

SpringBoot集成Activiti配置以下:

@Configuration
  public class SpringBootActivitiConfig {
      @Bean
      public ProcessEngine processEngine() {
          ProcessEngineConfiguration pro = ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
          pro.setJdbcDriver("com.mysql.jdbc.Driver");
          pro.setJdbcUrl("xxxx");
          pro.setJdbcUsername("xxxx");
          pro.setJdbcPassword("xxx");
          //避免發佈的圖片和xml中文出現亂碼
          pro.setActivityFontName("宋體");
          pro.setLabelFontName("宋體");
          pro.setAnnotationFontName("宋體");
          //數據庫更更新策略
          pro.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
          return pro.buildProcessEngine();
      }
  }

這時,啓動項目後,pro.buildProcessEngine()這行代碼會初始化Activiti框架,進入裏面,會發現它有三種實現,默認是第二種,即ProcessEngineConfigurationImpl。

image

點進去,Activiti框架具體構建buildProcessEngine方法以下,其中 this.init()的做用是環境初始化,包括配置設置、JDBC鏈接、bean裝載等的:

public ProcessEngine buildProcessEngine() {
    this.init();
    ProcessEngineImpl processEngine = new ProcessEngineImpl(this);
    if (this.isActiviti5CompatibilityEnabled && this.activiti5CompatibilityHandler != null) {
        Context.setProcessEngineConfiguration(processEngine.getProcessEngineConfiguration());
        this.activiti5CompatibilityHandler.getRawProcessEngine();
    }

    this.postProcessEngineInitialisation();
    return processEngine;
}

在this.init()方法裏,涉及到責任鏈模式初始化的方法是this.initCommandExecutors(),裏面詳情以下:

public void initCommandExecutors() {
    this.initDefaultCommandConfig();
    this.initSchemaCommandConfig();
    //初始化命令調用器
    this.initCommandInvoker();
    //List存放進涉及到的攔截器
    this.initCommandInterceptors();
    //初始化命令執行器
    this.initCommandExecutor();
}

這裏只須要關注最後三個方法——

  1. this.initCommandInvoker()

    initCommandInvoker()初始化構建了一個CommandInvoker攔截器,它繼承上邊提到的攔截器抽象類AbstractCommandInterceptor。這個攔截器在整條過濾器鏈中是最重要和關鍵,它排在了整條鏈的最後,其實,它纔是最終執行請求的,前邊幾個攔截器都是傳遞請求而已。

    public void initCommandInvoker() {
        if (this.commandInvoker == null) {
            if (this.enableVerboseExecutionTreeLogging) {
                this.commandInvoker = new DebugCommandInvoker();
            } else {
                //初始化執行該行代碼
                this.commandInvoker = new CommandInvoker();
            }
        }
    }

    這裏 new CommandInvoker()一個對象,而後將地址複製給this.commandInvoker對象引用,注意,該引用將會用在接下來的initCommandInterceptors()方法裏——

  2. this.initCommandInterceptors();

    initCommandInterceptors方法主要做用是建立一個List集合,而後將須要用到的攔截器都保存到該List集合裏——

    public void initCommandInterceptors() {
        if (this.commandInterceptors == null) {
            this.commandInterceptors = new ArrayList();
            if (this.customPreCommandInterceptors != null) {
                //用戶自定義前置攔截器
                this.commandInterceptors.addAll(this.customPreCommandInterceptors);
            }
            //框架自帶默認的攔截器
            this.commandInterceptors.addAll(this.getDefaultCommandInterceptors());
            if (this.customPostCommandInterceptors != null) {
                this.commandInterceptors.addAll(this.customPostCommandInterceptors);
            }
            //命令調用器,在攔截器鏈最後一個
            this.commandInterceptors.add(this.commandInvoker);
        }
    }

    this.getDefaultCommandInterceptors()的代碼以下:

    public Collection<? extends CommandInterceptor> getDefaultCommandInterceptors() {
        List<CommandInterceptor> interceptors = new ArrayList();
        //日誌攔截器
        interceptors.add(new LogInterceptor());
        CommandInterceptor transactionInterceptor = this.createTransactionInterceptor();
        if (transactionInterceptor != null) {
            interceptors.add(transactionInterceptor);
        }
    	//
        if (this.commandContextFactory != null) {
            interceptors.add(new CommandContextInterceptor(this.commandContextFactory, this));
        }
        //事務攔截器
        if (this.transactionContextFactory != null) {
            interceptors.add(new TransactionContextInterceptor(this.transactionContextFactory));
        }
    
        return interceptors;
    }

    可見,方法裏的 this.commandInterceptors 就是一個專門儲存攔截器對象的List集合——

    protected List<CommandInterceptor> commandInterceptors;

    這裏只須要重點關注this.commandInterceptors.add(this.commandInvoker)這行代碼,就是將上邊建立的CommandInvoker攔截器對象存儲到List裏,它是放在initCommandInterceptors()方法最後,某種程度也就意味着,這個攔截器在整條鏈當中處在最後面的位置。

    執行完該this.initCommandInterceptors()方法後,就可獲取到全部的攔截器對象,到這一步時,各攔截器仍是互相獨立的,仍沒法經過next()來進行調用傳遞,那麼,到底是如何將它們串起來造成一條鏈呢?

    接下來的this.initCommandExecutor()方法,就是實現將各攔截器串起來造成一條長鏈。

  3. this.initCommandExecutor();

該方法有兩個做用,一個是生成Interceptor攔截器鏈,一個是建立命令執行器commandExecutor。

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

this.initInterceptorChain(this.commandInterceptors)是將集合裏的攔截器初始化生成一條攔截器鏈,先循環獲取List集合裏的攔截器對象chain.get(i),而後經過setNext()方法在該攔截器對象chain.get(i)裏設置下一個攔截器引用,這樣,就可實現責任鏈裏所謂每一個接收者都包含對另外一個接收者的引用的功能。

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

那麼,這條攔截器鏈當中,都有哪些攔截器呢?

直接debug到這裏,能夠看到,總共有4個攔截器對象,按照順序排,包括LogInterceptor,CommandContextInterceptor,TransactionContextInterceptor,CommandInvoker(在命令模式裏,該類至關Invoker角色)。這四個攔截器對象在責任鏈模式當中充當了具體處理者(Concrete Handler)角色。

image

責任鏈模式裏剩餘客戶類(Client)角色應該是命令執行器this.commandExecutor。

所以,工做流引擎當中的責任鏈模式結構圖以下:

image

組成一條攔截器鏈以下圖所示——

image

生成攔截器鏈後,會返回一個(CommandInterceptor)chain.get(0),即攔截器LogInterceptor,爲何只返回第一個攔截器呢,這是一個很巧妙的地方,由於該攔截器裏已經一層一層地嵌套進其餘攔截器了,所以,只須要返回第一個攔截器,賦值給first便可。

接下來,就會建立命令執行器——

this.commandExecutor = new CommandExecutorImpl(this.getDefaultCommandConfig(), first);

這個命令執行器是整個引擎的底層靈魂,經過它,能夠實現責任鏈模式與命令模式——

攔截器鏈初始化介紹完成後,接下來開始介紹攔截器鏈在引擎裏的應用方式。


3、Activiti工做流裏責任鏈模式的應用

Activiti引擎的各操做方法其底層基本都是以命令模式來實現的,即調用上面建立的命令執行器this.commandExecutor的execute方法來實現的,例如自動生成28張數據庫表的方法,就是經過命令模式去作具體實現的——

this.commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), new SchemaOperationsProcessEngineBuild());

進入到commandExecutor方法裏,會發現前邊new CommandExecutorImpl(this.getDefaultCommandConfig(), first)創建命令執行器時,已將配置對象和嵌套其餘攔截器的LogInterceptor攔截器對象,經過構造器CommandExecutorImpl(CommandConfig defaultConfig, CommandInterceptor first)生成對象時,傳參賦值給了相應的對象屬性,其中first引用指向LogInterceptor,即攔截器鏈上的第一個攔截器——

public class CommandExecutorImpl implements CommandExecutor {
    protected CommandConfig defaultConfig;
    protected CommandInterceptor first;

    public CommandExecutorImpl(CommandConfig defaultConfig, CommandInterceptor first) {
        this.defaultConfig = defaultConfig;
        this.first = first;
    }

    public CommandInterceptor getFirst() {
        return this.first;
    }

    public void setFirst(CommandInterceptor commandInterceptor) {
        this.first = commandInterceptor;
    }

    public CommandConfig getDefaultConfig() {
        return this.defaultConfig;
    }

    public <T> T execute(Command<T> command) {
        return this.execute(this.defaultConfig, command);
    }

    public <T> T execute(CommandConfig config, Command<T> command) {
        return this.first.execute(config, command);
    }
}

當引擎執行this.commandExecutor.execute(xxx,xxx))相似方法時,實際上是執行了this.first.execute(config, command)方法,這裏的this.first在構建命令執行器時是經過LogInterceptor傳進來的,所以,執行代碼實際上是調用了LogInterceptor內部的execute()方法,也就是說,開始攔截器鏈上的第一個LogInterceptor攔截器傳遞方法execute()請求——

image

進入到攔截器鏈上的第一個攔截器LogInterceptor。

根據其內部代碼能夠看出,這是一個跟日誌有關的攔截器,內部並無多少加強功能,只是作了一個判斷是否須要debug日誌打印。若須要,則進行debug打印,若不須要,直接進入到 if (!log.isDebugEnabled()) 爲true的做用域內部,進而執行this.next.execute(config, command)用以將請求傳遞給下一個攔截器作處理。

public class LogInterceptor extends AbstractCommandInterceptor {
    private static Logger log = LoggerFactory.getLogger(LogInterceptor.class);
    public LogInterceptor() {
    }
    public <T> T execute(CommandConfig config, Command<T> command) {
        if (!log.isDebugEnabled()) {
            return this.next.execute(config, command);
        } else {
            log.debug("\n");
            log.debug("--- starting {} --------------------------------------------------------", command.getClass().getSimpleName());
            Object var3;
            try {
                var3 = this.next.execute(config, command);
            } finally {
                log.debug("--- {} finished --------------------------------------------------------", command.getClass().getSimpleName());
                log.debug("\n");
            }

            return var3;
        }
    }
}

這裏有一個小地方值得稍微打斷說下,就這個 if (!log.isDebugEnabled())判斷。衆生周知,若集成第三方日誌插件如logback之類,若其配置裏去除debug的打印,即時代碼裏 存在log.debug("xxxxx")也不會打印到控制檯,那麼,這裏增長一個判斷 if (!log.isDebugEnabled())是否屢次一舉呢?

事實上,這裏並不是畫蛇添足,增長這個判斷,是能夠提高代碼執行效率的。由於log.debug("xxxxx")裏的字符串拼接早於log.debug("xxxxx")方法執行的,也就是說,即便該log.debug("xxxxx")不會打印,但其內部的字符串仍然會進行拼接,而拼接,是須要時間的,雖然很細微,但一樣屬於影響性能範疇內的。所以,增長一個if判斷,若無須要打印debug日誌時,那麼就無需讓其內部的字符串進行自動拼接。

這是一個很小的知識點,但面試過程當中實際上是有可能會遇到這類與日誌相關的面試題的。

接下來,讓咱們繼續回到攔截器鏈的傳遞上來。

LogInterceptor攔截器調用this.next.execute(config, command),意味着將請求傳遞到下一個攔截器上進行處理,根據前邊分析,可知下一個攔截器是CommandContextInterceptor,根據代碼大概可知,這個攔截器內主要是獲取上下文配置對象和信息相關的,這些都是在工做流引擎初始化時生成的,它們被保存在Stack棧裏,具體都保存了哪些信息暫不展開分析——

public class CommandContextInterceptor extends AbstractCommandInterceptor {
    ......
public <T> T execute(CommandConfig config, Command<T> command) {
    CommandContext context = Context.getCommandContext();
    boolean contextReused = false;
    if (config.isContextReusePossible() && context != null && context.getException() == null) {
        contextReused = true;
        context.setReused(true);
    } else {
        context = this.commandContextFactory.createCommandContext(command);
    }

    try {
        Context.setCommandContext(context);
        Context.setProcessEngineConfiguration(this.processEngineConfiguration);
        if (this.processEngineConfiguration.getActiviti5CompatibilityHandler() != null) {
            Context.setActiviti5CompatibilityHandler(this.processEngineConfiguration.getActiviti5CompatibilityHandler());
        }
        //繼續將命令請求傳遞到下一個攔截器
        Object var5 = this.next.execute(config, command);
        return var5;
    } catch (Exception var31) {
        context.exception(var31);
    } finally {
     ......
    }

    return null;
}
}

CommandContextInterceptor攔截器沒有對命令請求作處理,它繼續將請求傳遞到下一個攔截器TransactionContextInterceptor,根據名字就大概能夠猜到,這個攔截器主要是增長與事務有關的功能——

public <T> T execute(CommandConfig config, Command<T> command) {
    CommandContext commandContext = Context.getCommandContext();
    boolean isReused = commandContext.isReused();
    Object var9;
    try {
        if (this.transactionContextFactory != null && !isReused) {
            TransactionContext transactionContext = this.transactionContextFactory.openTransactionContext(commandContext);
            Context.setTransactionContext(transactionContext);
            commandContext.addCloseListener(new TransactionCommandContextCloseListener(transactionContext));
        }
        var9 = this.next.execute(config, command);
    } finally {
    ......
    }
    return var9;
}

TransactionContextInterceptor攔截器一樣沒有對命令請求作處理,而是繼續傳遞到下一個攔截器,也就是最後一個攔截器CommandInvoker,根據名字能夠大概得知,這是一個與命令請求有關的攔截器,傳遞過來的請求將會在這個攔截器裏處理——

public class CommandInvoker extends AbstractCommandInterceptor {
    ......
    public <T> T execute(CommandConfig config, final Command<T> command) {
        final CommandContext commandContext = Context.getCommandContext();
        commandContext.getAgenda().planOperation(new Runnable() {
            public void run() {
                commandContext.setResult(command.execute(commandContext));
            }
        });
        this.executeOperations(commandContext);
        if (commandContext.hasInvolvedExecutions()) {
            Context.getAgenda().planExecuteInactiveBehaviorsOperation();
            this.executeOperations(commandContext);
        }
        return commandContext.getResult();
    }
}

進入到其內部,能夠發現,這裏沒有再繼續調用this.next.execute(config, command)這樣的請求進行傳遞,而是直接執行command.execute(commandContext),而後將返回值進行返回,其中,command是請求參數當中的第二個參數,讓咱們回過頭看下該請求案例最開始的調用——

this.commandExecutor.execute(processEngineConfiguration.getSchemaCommandConfig(), new SchemaOperationsProcessEngineBuild());

這裏的第二個參數是new SchemaOperationsProcessEngineBuild(),不妨進入到SchemaOperationsProcessEngineBuild類中,是吧,其內部一樣有一個execute方法——

public final class SchemaOperationsProcessEngineBuild implements Command<Object> {
    public SchemaOperationsProcessEngineBuild() {
    }

    public Object execute(CommandContext commandContext) {
        DbSqlSession dbSqlSession = commandContext.getDbSqlSession();
        if (dbSqlSession != null) {
            dbSqlSession.performSchemaOperationsProcessEngineBuild();
        }

        return null;
    }
}

可見,CommandInvoker攔截器內部執行command.execute(commandContext),就至關於執行了new SchemaOperationsProcessEngineBuild().execute(commandContext),也就是——

public Object execute(CommandContext commandContext) {
        DbSqlSession dbSqlSession = commandContext.getDbSqlSession();
        if (dbSqlSession != null) {
            dbSqlSession.performSchemaOperationsProcessEngineBuild();
        }
        return null;
    }

這是一種命令模式的實現。

本文主要是分析責任鏈模式在Activiti框架中的實踐,故暫不展開分析框架中的其餘設計模式,有興趣的童鞋能夠自行深刻研究,在Activiti框架當中,其操做功能底層基本都是以命令模式來實現的。

至此,就大概分析完了責任鏈模式在Activiti框架的建立和應用,學習完這塊內容,我對責任鏈模式有了更好理解,相對於看網上那些簡單以小例子來介紹設計模式的方法,我更喜歡去深刻框架當中學習其設計模式,這更能讓我明白,這種設計模式在什麼場景下適合應用,同時,能潛移默化地影響到我,讓我在設計系統架構時,能明白各設計模式的落地場景具體都是怎樣的。

相關文章
相關標籤/搜索