六十分鐘入門Activiti框架原理

本文基于一個簡單的Demo流程介紹了Activiti框架啟動、部署、運行過程。

Demo準備

流程圖文件:

<?xml version="1.0" encoding="UTF-8"?>
<definitions  xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
   <process id="hello" name="hello" isExecutable="true">
        <!-- 流程開始節點 -->
        <startEvent id="start" name="Start" ></startEvent>
        <!-- serviceTask:執行me.likeyao.activiti.demo.HelloWorld的execute方法,打印hello world -->
        <serviceTask id="helloworld" name="helloworld" activiti:class="me.likeyao.activiti.demo.HelloWorld"/>
        <!-- 流程結束節點 -->
        <endEvent id="end" name="End"></endEvent>
        <!-- 流程遷移線:開始節點到serviceTask節點 -->
        <sequenceFlow id="sid-1" sourceRef="start" targetRef="helloworld"></sequenceFlow>
        <!-- 流程遷移線:serviceTask節點到結束節點 -->
        <sequenceFlow id="sid-3" sourceRef="helloworld" targetRef="end"></sequenceFlow>
    </process>
</definitions>

流程圖:

demo流程

代碼:

public class App {
    public static void main(String[] args) {
        //創建流程引擎
        ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
        //部署流程圖
        processEngine.getRepositoryService().createDeployment().addClasspathResource("hello.bpmn20.xml").deploy();
        //發起流程
        processEngine.getRuntimeService().startProcessInstanceByKey("hello");
    }
}


public class HelloWorld implements JavaDelegate{
    public void execute(DelegateExecution execution) throws Exception {
        System.out.println("Hello world!");
    }
}

Demo實現的功能是發起一個流程,執行到流程的serviceTask節點時,打印Hello world!,然后流程結束。

源碼版本:5.22.0

框架初始化

ProcessEngine類圖

ProcessEngine

ProcessEngine是Activiti框架的門面,ProcessEngine本身不提供任何功能,通過getXXXService方法可以獲取到對應的Service對象執行操作。Demo中涉及到的兩個Service:

  • RepositoryService:流程定義和流程部署相關功能。
  • RuntimeService:流程實例相關功能(發起流程、獲取流程實例變量)。

ProcessEngineConfiguration

ProcessEngineConfiguration負責Activiti框架的屬性配置、初始化工作,初始化入口是buildProcessEngine方法,所有Activiti框架運行時需要用到的組件基本都在這里初始化:

public ProcessEngine buildProcessEngine() {
    init();
    return new ProcessEngineImpl(this);
}

protected void init() {
    initConfigurators();
    configuratorsBeforeInit();
    initProcessDiagramGenerator();
    initHistoryLevel();
    initExpressionManager();
    initDataSource();
    initVariableTypes();
    initBeans();
    initFormEngines();
    initFormTypes();
    initScriptingEngines();
    initClock();
    initBusinessCalendarManager();
    initCommandContextFactory();
    initTransactionContextFactory();
    initCommandExecutors();
    initServices();
    initIdGenerator();
    initDeployers();
    initJobHandlers();
    initJobExecutor();
    initAsyncExecutor();
    initTransactionFactory();
    initSqlSessionFactory();
    initSessionFactories();
    initJpa();
    initDelegateInterceptor();
    initEventHandlers();
    initFailedJobCommandFactory();
    initEventDispatcher();
    initProcessValidator();
    initDatabaseEventLogging();
    configuratorsAfterInit();
}

這里有一個擴展點:ProcessEngineConfigurator。

public interface ProcessEngineConfigurator {
    //組件初始化前
    void beforeInit(ProcessEngineConfigurationImpl processEngineConfiguration);
    //組件初始化后
    void configure(ProcessEngineConfigurationImpl processEngineConfiguration);
    //優先級
    int getPriority();
}

在init初始化方法中,initConfigurators方法通過ServiceLoader加載ProcessEngineConfigurator。隨后在configuratorsBeforeInit和configuratorsAfterInit方法中分別調用ProcessEngineConfigurator的beforeInit和configure方法,使用戶可以在ProcessEngineConfiguration初始化前后編程式的修改屬性,替換Activiti默認組件。

流程部署

流程部署實現的功能是將xml格式的流程圖,轉化為Activiti框架運行時依賴的流程定義對象。

RepositoryService

RepositoryService類圖

Demo中通過以下代碼部署了一個流程:

processEngine.getRepositoryService().createDeployment().addClasspathResource("hello.bpmn20.xml").deploy();

createDeployment方法中創建了DeploymentBuilder對象,DeploymentBuilder對象負責讀取指定路徑的流程圖xml文件的內容(byte數組),并緩存在DeploymentEntity對象中:

public DeploymentBuilder addInputStream(String resourceName, InputStream inputStream) {
    ...
    byte[] bytes = IoUtil.readInputStream(inputStream, resourceName);
    ResourceEntity resource = new ResourceEntity();
    resource.setName(resourceName);
    resource.setBytes(bytes);
    deployment.addResource(resource);
    return this;
}

最終DeploymentBuilder的deploy方法會調用RepositoryService的deploy方法,完成流程部署:

public Deployment deploy() {
    return repositoryService.deploy(this);
}

CommandExecutor

在RepositoryService的deploy方法中,使用了CommandExecutor對象:

public Deployment deploy(DeploymentBuilderImpl deploymentBuilder) {
    return commandExecutor.execute(new DeployCmd<Deployment>(deploymentBuilder));
}
CommandExecutor類圖

在Activiti中,大部分操作都以Command模式實現,例如部署流程圖的DeployCmd。CommandExecutor封裝了一系列的CommandInterceptor,在內部形成CommandInterceptor鏈,在命令執行前后做了攔截。Activiti框架提供了一些
CommandInterceptor實現:

名稱 作用
CommandContextInterceptor 用于生成命令執行的上下文(CommandContext)。
LogInterceptor 開啟日志Debug級別后,打印日志。
JtaTransactionInterceptor 開啟Jta事務

引入activiti-spring包,通過SpringTransactionInterceptor引入Spring的事務支持。

CommandExecutor在ProcessEngineConfigurationImpl的initCommandExecutors方法中初始化:

protected void initCommandExecutors() {
    initDefaultCommandConfig();
    initSchemaCommandConfig();
    initCommandInvoker();
    initCommandInterceptors();
    initCommandExecutor();
}

可以設置ProcessEngineConfigurationImpl的customPreCommandInterceptors和customPostCommandInterceptors屬性,添加自定義的CommandInterceptor:

protected 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);
    }
}

這里的pre和post是指Activiti框架getDefaultCommandInterceptors()的前后。

CommandInvoker是CommandInterceptor鏈的最后一個對象,負責調用Command:

public class CommandInvoker extends AbstractCommandInterceptor {
    @Override
    public <T> T execute(CommandConfig config, Command<T> command) {
        return command.execute(Context.getCommandContext());
    }
}

CommandContext

CommandContext類圖

CommandContext是Activit框架Command執行的上下文,主要包含各種SessionFactory:

sessionFactories = processEngineConfiguration.getSessionFactories();

SessionFactory負責生成Session,Session是Activiti操作持久化對象的統一接口:

名稱 作用
ProcessDefinitionEntityManager 流程定義相關讀寫操作。
ExecutionEntityManager 流程實例相關讀寫操作。
DefaultHistoryManager 歷史記錄相關讀寫操作

CommandContext的生命周期

CommandConext在CommandContextInterceptor中創建,在finally代碼塊中銷毀:

public <T> T execute(CommandConfig config, Command<T> command) {
    //首先嘗試從線程上下文的棧中獲取CommandContext
    CommandContext context = Context.getCommandContext();
    boolean contextReused = false;
    //什么時候創建新的CommandContext?
    //1、CommandConfig中指定了不復用CommandContext
    //2、當前線程上下文中不存在CommandConext
    //3、當前線程上下文中的CommandConext已經拋出異常
    if (!config.isContextReusePossible() || context == null || context.getException() != null) { 
        context = commandContextFactory.createCommandContext(command);      }  
    else {
        contextReused = true;
    }

    try {
        //將前面獲取到的CommandContext入棧
        Context.setCommandContext(context);
        Context.setProcessEngineConfiguration(processEngineConfiguration);
        //執行下一個interceptor,在CommandInvoker中可以通過Context.getCommandContext()獲取線程上下文中的CommandContext
        return next.execute(config, command);
    } catch (Exception e) {
        //記錄異常信息
        context.exception(e);
    } finally {
        try {
            //如果CommandContext不可復用,用完直接關閉
            if (!contextReused) {
                context.close();
            }
        } finally {
            //出棧操作
            Context.removeCommandContext();
            Context.removeProcessEngineConfiguration();
            Context.removeBpmnOverrideContext();
        }
    }
    
    return null;
}

Activiti的框架可以在一個Command的執行過程中,調用另外一個Command,所以會出現是否需要復用CommandContext的選項,默認值為true。

流程的解析

在DeployCmd中,首先調用DeploymentEntityManager持久化存儲DeploymentEntity對象:

commandContext.getDeploymentEntityManager().insertDeployment(deployment);

然后調用DeploymentManager部署流程(流程解析):

commandContext.getProcessEngineConfiguration().getDeploymentManager().deploy(deployment, deploymentSettings);
DeploymentEntityManager
DeploymentEntityManager類圖

DeploymentEntityManager的deploy方法中循環調用Deployer對象的deploy方法,Activiti默認的Deployer是BpmnDeployer。

另外DeploymentEntityManager中還緩存了解析好的流程定義對象和Bpmn模型對象。

Activiti持久化的是流程圖xml文件,每次系統重新啟動都要執行一次“deploy”操作,生成ProcessDefinitionEntity對象。

BpmnDeployer
BpmnDeployer類圖

BpmnDeployer的deploy方法中包含幾個操作(代碼縮略版):

public void deploy(DeploymentEntity deployment, Map<String, Object> deploymentSettings) {
    ...
    BpmnParse bpmnParse = bpmnParser.createParse().sourceInputStream(inputStream).setSourceSystemId(resourceName).deployment(deployment).name(resourceName);
    bpmnParse.execute();
    for (ProcessDefinitionEntity processDefinition: bpmnParse.getProcessDefinitions()) {
        if (deployment.isNew()) {
            ProcessDefinitionEntity latestProcessDefinition = ...
            if (latestProcessDefinition != null) {
                processDefinitionVersion = latestProcessDefinition.getVersion() + 1;
            }else{
                processDefinitionVersion = 1;
            }
            processDefinition.setId(idGenerator.getNextId());
            dbSqlSession.insert(processDefinition);
        }
        ...
    }
}
  • 通過BpmnParser對象創建BpmnParse。
  • 調用BpmnParse的execute方法,將inputStream中的流程圖轉化為ProcessDefinitionEntity。
  • 持久化ProcessDefinitionEntity對象。
BpmnParse
BpmnParse類圖

在BpmnParse的execute中完成了xml文件到ProcessDefinitionEntity對象的轉化:

public BpmnParse execute() {
    //xml->bpmnModel
    bpmnModel = converter.convertToBpmnModel(streamSource, validateSchema, enableSafeBpmnXml, encoding);
    //bpmnModel-> ProcessDefinitionEntity
    transformProcessDefinitions();
}

protected void transformProcessDefinitions() {
    for (Process process : bpmnModel.getProcesses()) {
        bpmnParserHandlers.parseElement(this, process);
    }
}

在流程定義解析過程中,會涉及到兩套模型:

  • Bpmn模型(由BpmnXMLConverter完成轉換)
  • PVM模型(由BpmnParseHandlers完成轉換)

Bpmn模型

Bpmn模型

PVM模型

PVM模型

Bpmn模型更偏向于xml節點的描述,PVM模型是運行時模型。Bpmn模型中的ServiceTask、StartEvent等會統一映射轉換為PVM的ActivityImpl對象,ServiceTask和StartEvent等節點行為上的差別,體現在ActivityImpl對象持有的不同的ActivityBehavior上。

運行流程

創建流程實例

在demo中通過RuntimeService發起流程實例:

processEngine.getRuntimeService().startProcessInstanceByKey("hello");

在startProcessInstanceByKey方法中執行StartProcessInstanceCmd命令:

public class StartProcessInstanceCmd<T> implements Command<ProcessInstance>, Serializable {
    ...
    public ProcessInstance execute(CommandContext commandContext) {
        //獲取流程定義
        ProcessDefinitionEntity processDefinition = ...
        //創建流程實例
        ExecutionEntity processInstance = processDefinition.createProcessInstance(businessKey);
        //開始流程
        processInstance.start();
        return processInstance;
    }
    ...
}

在StartProcessInstanceCmd方中通過流程定義ProcessDefinitionEntity創建了流程實例ExecutionEntity:

ExecutionEntity

ExecutionEntity實現了一些重要接口:

  • PVM相關的接口,賦予了ExecutionEntity流程驅動的能力,例如single、start方法。
  • 實現VariableScope接口讓ExecutionEntity可以持久上下文變量。
  • ProcessInstance接口暴露了ExecutionEntity關聯的ProcessDefinitionEntity的信息。
  • PersistentObject接口代表ExecutionEntity對象是需要持久化。

在ExecutionEntity中維護類一個屬性:activity。activity屬性代表當前執行到哪個節點,在創建ExecutionEntity過程中會設置activity,使流程從某一個節點開始,默認是開始節點。

最后StartProcessInstanceCmd還調用ExecutionEntity的start方法開始驅動流程:

public void start() {
    performOperation(AtomicOperation.PROCESS_START);
}

驅動流程

Activiti框架的流程運行于PVM模型之上,在流程運行時主要涉及到PVM中幾個對象:ActivityImpl、TransitionImpl和ActivityBehavior。

  • ActivityImpl:ActivityImpl是流程節點的抽象,ActivityImpl維護流程圖中節點的連線,包括有哪些進線,有哪些出線。另外還包含節點同步/異步執行等信息。
  • TransitionImpl:TransitionImpl包含source和target兩個屬性,連接了兩個流程節點。
  • ActivityBehavior:每一個ActivityImpl對象都擁有一個ActivityBehavior對象,ActivityBehavior代表節點的行為。

ActivityImpl、TransitionImpl和ActivityBehavior只是描述了流程的節點、遷移線和節點行為,真正要讓ExecutionEntity流轉起來,還需要AtomicOperation的驅動:

AtomicOperation PROCESS_START = new AtomicOperationProcessStart();
AtomicOperation PROCESS_START_INITIAL = new AtomicOperationProcessStartInitial();
AtomicOperation PROCESS_END = new AtomicOperationProcessEnd();
AtomicOperation ACTIVITY_START = new AtomicOperationActivityStart();
AtomicOperation ACTIVITY_EXECUTE = new AtomicOperationActivityExecute();
AtomicOperation ACTIVITY_END = new AtomicOperationActivityEnd();
AtomicOperation TRANSITION_NOTIFY_LISTENER_END = new AtomicOperationTransitionNotifyListenerEnd();
AtomicOperation TRANSITION_DESTROY_SCOPE = new AtomicOperationTransitionDestroyScope();
AtomicOperation TRANSITION_NOTIFY_LISTENER_TAKE = new AtomicOperationTransitionNotifyListenerTake();
AtomicOperation TRANSITION_CREATE_SCOPE = new AtomicOperationTransitionCreateScope();
AtomicOperation TRANSITION_NOTIFY_LISTENER_START = new AtomicOperationTransitionNotifyListenerStart();
    
AtomicOperation DELETE_CASCADE = new AtomicOperationDeleteCascade();
AtomicOperation DELETE_CASCADE_FIRE_ACTIVITY_END = new AtomicOperationDeleteCascadeFireActivityEnd();

在ExecutionEntity的start方法中,調用了PROCESS_START,PROCESS_START做了幾件事:

  • 獲取流程定義級別定義的監聽start事件的ExecutionListener,調用notify方法。
  • 如果開啟了事件功能,發布ActivitiEntityWithVariablesEvent和ActivitiProcessStartedEvent。
  • 調用PROCESS_START_INITIAL。

PROCESS_START_INITIAL也實現了類似的功能:

  • 獲取初始節點上定義的監聽start事件的ExecutionListener,調用notify方法。
  • 調用ACTIVITY_EXECUTE。

在Demo流程執行中涉及的AtomicOperation的鏈路主要包括:

  • ACTIVITY_EXECUTE:調用當前activity的behavior。
  • TRANSITION_NOTIFY_LISTENER_END:某個activity節點執行完畢,調用節點上聲明的監聽end事件的ExecutionListener。
  • TRANSITION_NOTIFY_LISTENER_TAKE:觸發線上的ExecutionListener。
  • TRANSITION_NOTIFY_LISTENER_START:某個activity節點即將開始執行,調用節點上的監聽start事件的ExecutionListener。

以Demo流程中的ServiceTask節點helloworld為例,在執行ACTIVITY_EXECUTE時,會獲取activity關聯的behavior:

public class AtomicOperationActivityExecute implements AtomicOperation {
    public void execute(InterpretableExecution execution) {
        ...
        ActivityImpl activity = (ActivityImpl) execution.getActivity();
        ActivityBehavior activityBehavior = activity.getActivityBehavior();
        activityBehavior.execute(execution);
        ...
    }
}

ServiceTask解析時關聯的是ServiceTaskJavaDelegateActivityBehavior,execution方法:

public void execute(ActivityExecution execution) throws Exception {
    //execution中調用了me.likeyao.activiti.demo.HelloWorld
    execute((DelegateExecution) execution);
    //離開當前節點
    leave(execution);
}

在leave方法中調用了:

bpmnActivityBehavior.performDefaultOutgoingBehavior(execution);

performDefaultOutgoingBehavior方法會在當前activity的
出線中選擇一條,使流程流向下一個節點。在Demo中只有一條線存在:

protected void performOutgoingBehavior(ActivityExecution execution, 
          boolean checkConditions, boolean throwExceptionIfExecutionStuck, List<ActivityExecution> reusableExecutions) {

    if (transitionsToTake.size() == 1) {
        execution.take(transitionsToTake.get(0));
    }         
          
}

最終take方法會將流程驅動權交還到AtomicOperation中:

public class ExecutionEntity{
    ...
    public void take(PvmTransition transition, boolean fireActivityCompletionEvent) {
        ...
        setActivity((ActivityImpl)transition.getSource());
        setTransition((TransitionImpl) transition);
        performOperation(AtomicOperation.TRANSITION_NOTIFY_LISTENER_END);
        ...
    }
    ...
}

AtomicOperation的問題

按照AtomicOperation的驅動模式,只有當遇到UserTask等需要等待single信號的節點,調用才會返回。這意味著當調用RuntimeService啟動一個流程實例時,要一直等到流程運行到一個UserTask節點調用才會返回,如果流程比較長耗時非常驗證。

另一個問題是當流程圖比較復雜,ExecutionListener數量比較多時,AtomicOperation之間的互相調用會導致調用棧非常深。

AtomicOperation驅動模式與ExecutionEntity、Behavior等綁定的比較緊密,暫時沒有特別好的辦法替換掉。

小結

本文主要介紹了Activiti框架的啟動、部署、運行的主鏈路,并沒有深入BPMN規范和Activit功能的具體實現,后續打算根據Activiti的用戶手冊,詳細分析每個功能的使用和實現。

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

推薦閱讀更多精彩內容