01 啟動流程實例

前言

? ? ? ? 本文基于 Activiti 7.0.0.GA 源碼,研究?Activiti 如何啟動一個流程實例。

審批流程圖

如下圖,在此流程圖中,存在兩個UserTask節點,第一個節點是主管審批,第二個節點是產品經理審批,兩個節點中間有一個排他網關,此網關用來對主管審批的結果進行判斷,如果主管審批通過,則流程走到產品經理審批節點,如果主管審批拒絕,則流程走到結束節點。

主管審批節點通過UEL表達式${assignManager}動態賦值,產品經理審批節點通過UEL表達式${assignProductLineManager}動態賦值,網關節點通過UEL表達式${isPass}動態賦值。

啟動流程實例

以RuntimeService接口的startProcessInstanceById(String, Map)為源碼入口,研究Activiti框架如何啟動一個流程實例。

ProcessInstance startProcessInstanceById(String processDefinitionId, Map<String, Object> variables)

RuntimeService接口的實現類RuntimeServiceImpl實現如下,把傳入的processDefinitionId和variable封裝成StartProcessInstanceCmd,然后把StartProcessInstanceCmd放入commandExecutor命令執行器的execute方法中,待后續調用。

public ProcessInstance startProcessInstanceById(String processDefinitionId, Map<String, Object> variables) {return commandExecutor.execute(new StartProcessInstanceCmd<ProcessInstance>(null, processDefinitionId, null, variables));
}

CommandExecutor內部持有一個命令攔截器LogInterceptor,LogInterceptor是CommandInteceptor命令攔截器鏈中的第一個攔截器,StartProcessInstanceCmd在執行前會經過攔截器鏈中的所有攔截器。

攔截器鏈中的各個攔截器的前后順序如下,CommandInvoker是攔截器鏈中的處于最末端的攔截器,專用于調用實現了Command接口的各種CMD類。

StartProcessInstanceCmd實現了Command接口,該接口只有一個execute方法,此方法會被CommandInvoker攔截器調用,從而開始執行StartProcessInstanceCmd實現的execute方法,用以啟動一個流程實例。

@Internal
public interface Command<T> {T execute(CommandContext commandContext);
}

StartProcessInstanceCmd實現execute方法如下:

package org.activiti.engine.impl.cmd;import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;import org.activiti.bpmn.model.ValuedDataObject;
import org.activiti.engine.ActivitiIllegalArgumentException;
import org.activiti.engine.ActivitiObjectNotFoundException;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.impl.interceptor.Command;
import org.activiti.engine.impl.interceptor.CommandContext;
import org.activiti.engine.impl.persistence.deploy.DeploymentManager;
import org.activiti.engine.impl.runtime.ProcessInstanceBuilderImpl;
import org.activiti.engine.impl.util.ProcessInstanceHelper;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;/*** 開啟流程實例-命令類*/
public class StartProcessInstanceCmd<T> implements Command<ProcessInstance>, Serializable {private static final long serialVersionUID = 1L;protected String processDefinitionKey;protected String processDefinitionId;protected Map<String, Object> variables;protected Map<String, Object> transientVariables;protected String businessKey;protected String tenantId;protected String processInstanceName;protected ProcessInstanceHelper processInstanceHelper;public StartProcessInstanceCmd(String processDefinitionKey, String processDefinitionId, String businessKey, Map<String, Object> variables) {this.processDefinitionKey = processDefinitionKey;this.processDefinitionId = processDefinitionId;this.businessKey = businessKey;this.variables = variables;}public StartProcessInstanceCmd(String processDefinitionKey, String processDefinitionId, String businessKey, Map<String, Object> variables, String tenantId) {this(processDefinitionKey, processDefinitionId, businessKey, variables);this.tenantId = tenantId;}public StartProcessInstanceCmd(ProcessInstanceBuilderImpl processInstanceBuilder) {this(processInstanceBuilder.getProcessDefinitionKey(), processInstanceBuilder.getProcessDefinitionId(), processInstanceBuilder.getBusinessKey(), processInstanceBuilder.getVariables(), processInstanceBuilder.getTenantId());this.processInstanceName = processInstanceBuilder.getProcessInstanceName();this.transientVariables = processInstanceBuilder.getTransientVariables();}/*** 從 CommandInvoker 調用下面這個方法* @param commandContext* @return*/public ProcessInstance execute(CommandContext commandContext) {// 取出已經部署好的流程信息DeploymentManager deploymentCache = commandContext.getProcessEngineConfiguration().getDeploymentManager();// Find the process definitionProcessDefinition processDefinition = null;// 傳入了processDefinitionId,所以進入到這個if中,if (processDefinitionId != null) {// 通過 processDefinitionId 從緩存中找到 processDefinition 流程定義processDefinition = deploymentCache.findDeployedProcessDefinitionById(processDefinitionId);if (processDefinition == null) {throw new ActivitiObjectNotFoundException("No process definition found for id = '" + processDefinitionId + "'", ProcessDefinition.class);}} else if (processDefinitionKey != null && (tenantId == null || ProcessEngineConfiguration.NO_TENANT_ID.equals(tenantId))) {processDefinition = deploymentCache.findDeployedLatestProcessDefinitionByKey(processDefinitionKey);if (processDefinition == null) {throw new ActivitiObjectNotFoundException("No process definition found for key '" + processDefinitionKey + "'", ProcessDefinition.class);}} else if (processDefinitionKey != null && tenantId != null && !ProcessEngineConfiguration.NO_TENANT_ID.equals(tenantId)) {processDefinition = deploymentCache.findDeployedLatestProcessDefinitionByKeyAndTenantId(processDefinitionKey, tenantId);if (processDefinition == null) {throw new ActivitiObjectNotFoundException("No process definition found for key '" + processDefinitionKey + "' for tenant identifier " + tenantId, ProcessDefinition.class);}} else {throw new ActivitiIllegalArgumentException("processDefinitionKey and processDefinitionId are null");}// 獲取一個 ProcessInstanceHelper,用于啟動流程processInstanceHelper = commandContext.getProcessEngineConfiguration().getProcessInstanceHelper();ProcessInstance processInstance = createAndStartProcessInstance(processDefinition, businessKey, processInstanceName, variables, transientVariables);return processInstance;}protected ProcessInstance createAndStartProcessInstance(ProcessDefinition processDefinition, String businessKey, String processInstanceName, Map<String,Object> variables, Map<String, Object> transientVariables) {return processInstanceHelper.createAndStartProcessInstance(processDefinition, businessKey, processInstanceName, variables, transientVariables);}protected Map<String, Object> processDataObjects(Collection<ValuedDataObject> dataObjects) {Map<String, Object> variablesMap = new HashMap<String, Object>();// convert data objects to process variablesif (dataObjects != null) {for (ValuedDataObject dataObject : dataObjects) {variablesMap.put(dataObject.getName(), dataObject.getValue());}}return variablesMap;}
}

ProcessInstanceHelper的createAndStartProcessInstance中用 processDefinition 的 id 查找出 Process 流程模型。

protected ProcessInstance createAndStartProcessInstance(ProcessDefinition processDefinition,String businessKey, String processInstanceName,Map<String, Object> variables, Map<String, Object> transientVariables, boolean startProcessInstance) {CommandContext commandContext = Context.getCommandContext(); // Todo: ideally, context should be passed here// Do not start process a process instance if the process definition is suspendedif (ProcessDefinitionUtil.isProcessDefinitionSuspended(processDefinition.getId())) {throw new ActivitiException("Cannot start process instance. Process definition " + processDefinition.getName() + " (id = " + processDefinition.getId() + ") is suspended");}// Get model from cache// 用 processDefinition 的 id 查找出 Process 流程模型Process process = ProcessDefinitionUtil.getProcess(processDefinition.getId());if (process == null) {throw new ActivitiException("Cannot start process instance. Process model " + processDefinition.getName() + " (id = " + processDefinition.getId() + ") could not be found");}// 獲取流程定義中的第一個節點(初始節點)FlowElement initialFlowElement = process.getInitialFlowElement();if (initialFlowElement == null) {throw new ActivitiException("No start element found for process definition " + processDefinition.getId());}return createAndStartProcessInstanceWithInitialFlowElement(processDefinition, businessKey,processInstanceName, initialFlowElement, process, variables, transientVariables, startProcessInstance);
}

在ProcessInstanceHelper的createAndStartProcessInstanceWithInitialFlowElement方法中,創建了 ExecutionEntity 流程實例,此實例的生命周期貫穿整個流程實例,從StartEvent開始,到EndEvent結束。Process流程模型就好比一條人工運河,而ExecutionEntity就是這條河流上的一條船。

public ProcessInstance createAndStartProcessInstanceWithInitialFlowElement(ProcessDefinition processDefinition,String businessKey, String processInstanceName, FlowElement initialFlowElement,Process process, Map<String, Object> variables, Map<String, Object> transientVariables, boolean startProcessInstance) {CommandContext commandContext = Context.getCommandContext();// Create the process instanceString initiatorVariableName = null;// 如果初始節點是StartEvent,就獲取 Initiatorif (initialFlowElement instanceof StartEvent) {initiatorVariableName = ((StartEvent) initialFlowElement).getInitiator();}// 創建 ExecutionEntity,后續會有很多地方用到。ExecutionEntity processInstance = commandContext.getExecutionEntityManager().createProcessInstanceExecution(processDefinition, businessKey, processDefinition.getTenantId(), initiatorVariableName);// 創建完成,記錄流程已經開始commandContext.getHistoryManager().recordProcessInstanceStart(processInstance, initialFlowElement);processInstance.setVariables(processDataObjects(process.getDataObjects()));// Set the variables passed into the start commandif (variables != null) {for (String varName : variables.keySet()) {// 把傳入的流程變量綁定到 ProcessInstance 上processInstance.setVariable(varName, variables.get(varName));}}if (transientVariables != null) {for (String varName : transientVariables.keySet()) {processInstance.setTransientVariable(varName, transientVariables.get(varName));}}// Set processInstance nameif (processInstanceName != null) {processInstance.setName(processInstanceName);commandContext.getHistoryManager().recordProcessInstanceNameChange(processInstance.getId(), processInstanceName);}// Fire eventsif (Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {Context.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(ActivitiEventBuilder.createEntityWithVariablesEvent(ActivitiEventType.ENTITY_INITIALIZED, processInstance, variables, false));}// Create the first execution that will visit all the process definition elements// 用父級ExecutionEntity(processInstance)創建一個子級ExecutionEntity// 后續父級ExecutionEntity(processInstance)通過getExecutions().get(0)訪問ExecutionEntity execution = commandContext.getExecutionEntityManager().createChildExecution(processInstance);// 這個 setCurrentFlowElement 操作非常重要,它表示 execution 當前活躍在哪個 FlowNode 或者 SequenceFlow 上。// execution 每到一個新的節點上,都會調用這個方法更新一下活躍的節點,在這里這個 initialFlowElement 是 StartEventexecution.setCurrentFlowElement(initialFlowElement);if (startProcessInstance) {// 已經成功啟動一個新流程,從這里流程將流向下個節點startProcessInstance(processInstance, commandContext, variables);}return processInstance;
}

ProcessInstanceHelper的startProcessInstance方法中有關于如何處理Process流程模型中含有子流程的邏輯,但這里為方便學習不做研究,知道即可。在此方法中,流程實例將通過commandContext.getAgenda().planContinueProcessOperation(execution);前往下一個節點。

public void startProcessInstance(ExecutionEntity processInstance, CommandContext commandContext, Map<String, Object> variables) {// 通過 processDefinitionId 獲取流程詳情 ProcessProcess process = ProcessDefinitionUtil.getProcess(processInstance.getProcessDefinitionId());// Event sub process handlingList<MessageEventSubscriptionEntity> messageEventSubscriptions = new LinkedList<>();for (FlowElement flowElement : process.getFlowElements()) {// 處理流程中含有子流程的審批流if (flowElement instanceof EventSubProcess) {EventSubProcess eventSubProcess = (EventSubProcess) flowElement;for (FlowElement subElement : eventSubProcess.getFlowElements()) {if (subElement instanceof StartEvent) {StartEvent startEvent = (StartEvent) subElement;if (CollectionUtil.isNotEmpty(startEvent.getEventDefinitions())) {EventDefinition eventDefinition = startEvent.getEventDefinitions().get(0);if (eventDefinition instanceof MessageEventDefinition) {MessageEventDefinition messageEventDefinition = (MessageEventDefinition) eventDefinition;BpmnModel bpmnModel = ProcessDefinitionUtil.getBpmnModel(processInstance.getProcessDefinitionId());if (bpmnModel.containsMessageId(messageEventDefinition.getMessageRef())) {messageEventDefinition.setMessageRef(bpmnModel.getMessage(messageEventDefinition.getMessageRef()).getName());}ExecutionEntity messageExecution = commandContext.getExecutionEntityManager().createChildExecution(processInstance);messageExecution.setCurrentFlowElement(startEvent);messageExecution.setEventScope(true);messageEventSubscriptions.add(commandContext.getEventSubscriptionEntityManager().insertMessageEvent(messageEventDefinition.getMessageRef(), messageExecution));}}}}}}// 取出父級ExecutionEntity(processInstance)的子executionExecutionEntity execution = processInstance.getExecutions().get(0); // There will always be one child execution created// 將通過 planContinueProcessOperation 繼續走向流程中的下一個節點。commandContext.getAgenda().planContinueProcessOperation(execution);// 分發事件if (Context.getProcessEngineConfiguration().getEventDispatcher().isEnabled()) {ActivitiEventDispatcher eventDispatcher = Context.getProcessEngineConfiguration().getEventDispatcher();// 創建并分發流程開始事件eventDispatcher.dispatchEvent(ActivitiEventBuilder.createProcessStartedEvent(execution, variables, false));for (MessageEventSubscriptionEntity messageEventSubscription : messageEventSubscriptions) {commandContext.getProcessEngineConfiguration().getEventDispatcher().dispatchEvent(ActivitiEventBuilder.createMessageEvent(ActivitiEventType.ACTIVITY_MESSAGE_WAITING, messageEventSubscription.getActivityId(),messageEventSubscription.getEventName(), null, messageEventSubscription.getExecution().getId(),messageEventSubscription.getProcessInstanceId(), messageEventSubscription.getProcessDefinitionId()));}}
}

至此,流程實例啟動完成。

后續將通過 commandContext.getAgenda().planContinueProcessOperation(execution) 流轉到下一個節點。

總結

流程啟動過程中主要涉及到命令執行器(CommandExecutor)、命令攔截器(CommandInteceptor)、流程開始命令(StartProcessInstanceCmd)、流程定義(ProcessDefinition)、流程模型(Process)以及流程實例(ProcessInstance,即ExecutionEntity),這些元素共構成了啟動一個流程實例的必需條件。

在啟動流程實例過程的源碼中,我個人感覺非常精妙的設計之處在于攔截器,得益于攔截器的使用,Activiti抽出了業務中的公共代碼(比如內置的日志記錄、CommandContext、Transaction、CommandInvoker),使得主業務與附屬業務邊界感更強,啟動過程逐漸遞進,前后邏輯順序清晰明了,源碼學習者更容易掌握業務邏輯。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/89094.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/89094.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/89094.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

LeetCode--47.全排列 II

解題思路&#xff1a;1.獲取信息&#xff1a;給定一個可包含重復數字的序列&#xff0c;按任意順序返回所有不重復的全排列提示信息&#xff1a;1 < nums.length < 8-10 < nums[i] < 102.分析題目&#xff1a;相較于46題&#xff0c;它多限制了一個條件&#xff0c…

vue3 服務端渲染時請求接口沒有等到數據,但是客戶端渲染是請求接口又可以得到數據

原因是: 服務端請求 后端接收到 請求 ‘Content-Type’: ‘application/x-www-form-urlencoded; charsetUTF-8’ 直接返回錯誤的code 200000 增加 data: {} 服務端請求 后端接收到 請求 ‘Content-Type’: ‘application/json; charsetUTF-8’ 服務端請求就可以得到數據 expo…

Linux 文件操作命令大全:從入門到精通的實用指南

Linux 文件操作命令大全&#xff1a;從入門到精通的實用指南 在 Linux 系統中&#xff0c;文件操作是日常工作的核心內容之一。無論是開發者、運維工程師還是 Linux 愛好者&#xff0c;掌握常用的文件操作命令都能極大提升工作效率。本文將詳細介紹 Linux 系統中最常用的文件操…

Linux開發利器:探秘開源,構建高效——基礎開發工具指南(上)【包管理器/Vim】

???~~~~~~歡迎光臨知星小度博客空間~~~~~~??? ???零星地變得優秀~也能拼湊出星河~??? ???我們一起努力成為更好的自己~??? ???如果這一篇博客對你有幫助~別忘了點贊分享哦~??? ???如果有什么問題可以評論區留言或者私信我哦~??? ??????個人…

基于遷移學習的培養基配方開發方法

本文為學習筆記&#xff0c;原文專利&#xff1a; 中國專利公布公告 然后輸入 202110622279.7 概覽 一、問題背景 傳統培養基開發痛點&#xff1a; 數據依賴&#xff1a;需大量細胞實驗&#xff08;1000配方&#xff09;訓練專用模型 遷移性差&#xff1a;A細胞模型無法直接…

Web3.0與元宇宙:重構數字文明的技術范式與社會變革

一、技術融合&#xff1a;Web3.0與元宇宙的底層架構互補1.1 區塊鏈與智能合約&#xff1a;構建信任基石去中心化信任機制&#xff1a;Web3.0的區塊鏈技術為元宇宙提供去中心化信任框架&#xff0c;虛擬資產&#xff08;如土地、道具&#xff09;通過NFT&#xff08;非同質化代幣…

Java: OracleHelper

/*** encoding: utf-8* 版權所有 2025 ©涂聚文有限公司 * 許可信息查看&#xff1a;言語成了邀功盡責的功臣&#xff0c;還需要行爲每日來值班嗎* 描述&#xff1a; https://www.oracle.com/database/technologies/appdev/jdbc-downloads.html ojdbc11* Author : geovi…

OSPFv3-一二類LSA

文章目錄OSPFv3 LSA類型Router LSANetwork LSA&#x1f3e1;作者主頁&#xff1a;點擊&#xff01; &#x1f916;Datacom專欄&#xff1a;點擊&#xff01; ??創作時間&#xff1a;2025年07月12日20點01分 OSPFv3 LSA類型 Router LSA 不再包含地址信息&#xff0c;使能 OS…

HugeGraph 【圖數據庫】JAVA調用SDK

1.引入依賴<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>28.0-jre</version> </dependency><dependency><groupId>com.squareup.okhttp3</groupId><artifac…

軟考中級【網絡工程師】第6版教材 第2章 數據通信基礎(中)

考點分析&#xff1a;重要程度&#xff1a;???&#xff0c;本章可能是全書最難的章節&#xff0c;偏理論&#xff0c;公式多除了傳輸介質&#xff0c;其他知識點只考選擇題&#xff0c;考試一般占3 ~ 5分高頻考點&#xff1a;PCM、奈奎斯特定理、曼徹斯特編碼&#xff1b;難…

單片機(STM32-中斷)

一、中斷基礎知識 1.概念 中斷&#xff08;Interrupt&#xff09;是一種特殊的事件處理機制。當CPU正在執行主程序時&#xff0c;如果出現了某些緊急或重要的事件&#xff08;如外設請求、定時器溢出等&#xff09;&#xff0c;可以暫時中止當前的程序&#xff0c;轉而去處理…

gitlab-ci.yml

.gitlab-ci.yml 文件的位置 該文件應放置在 GitLab 項目的代碼倉庫的根目錄 下&#xff0c;具體說明如下&#xff1a;存儲庫根目錄 .gitlab-ci.yml 是 GitLab 持續集成&#xff08;CI&#xff09;的配置文件&#xff0c;需直接放在項目的代碼倉庫的根目錄&#xff08;與 .git 目…

使用JS編寫一個購物車界面

使用JS編寫一個購物車界面 今天我們來剖析一個精心設計的家具商店購物車頁面&#xff0c;這個頁面不僅美觀大方&#xff0c;還具備豐富的交互功能。讓我們一步步拆解它的設計理念和技術實現&#xff01; 頁面展示 頁面整體結構 這個購物車頁面采用了經典的電商布局模式&…

零信任安全架構:如何在云環境中重構網絡邊界?

一、云原生時代&#xff1a;傳統防火墻為何轟然倒塌&#xff1f; 當業務碎片化散落在AWS、阿里云、私有IDC&#xff0c;當員工隨手在咖啡廳WiFi連接生產數據庫&#xff0c;“內網可信”的基石瞬間崩塌&#xff0c;傳統防火墻徹底淪為馬奇諾防線&#xff1a; 邊界消亡&#xff1…

css實現燒香效果

效果&#xff1a;代碼&#xff1a;<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>動態香燭效果&…

硬件產品的技術資料管控是確保研發可追溯、生產可復制、質量可控制的核心環節。

硬件產品的技術資料管控是確保研發可追溯、生產可復制、質量可控制的核心環節。以下針對BOM單、PCB文件、程序代碼、原理圖四大核心要素&#xff0c;結合行業實踐提出管控方向劃分及優化策略&#xff1a;&#x1f4cb; 一、硬件BOM單的精細化管控方向BOM單是硬件生產的“配方表…

Uniswap V2/V3/V4簡短說明

Uniswap 是以太坊上最知名的去中心化交易所&#xff08;DEX&#xff09;&#xff0c;它通過不同的版本&#xff08;V2、V3、V4&#xff09;不斷改進&#xff0c;變得更高效、更靈活。以下是用通俗易懂的方式介紹它們之間的異同&#xff1a; Uniswap V2&#xff1a;基礎版&#…

C++面向對象創建打印算術表達式樹

C面向對象&#xff0c;實現算術表達式樹的創建和打印的案例&#xff0c;來源于《C沉思錄》第八章&#xff0c;涉及數據抽象、繼承、多態&#xff08;動態綁定&#xff09;、句柄&#xff0c;其中句柄的使用是核心&#xff0c;關于句柄的較為簡單的文章鏈接點擊這里&#xff0c;…

力扣每日一題--2025.7.16

&#x1f4da; 力扣每日一題–2025.7.16 &#x1f4da; 3201. 找出有效子序列的最大長度 I&#xff08;中等&#xff09; 今天我們要解決的是力扣上的第 3201 題——找出有效子序列的最大長度 I。這道題雖然標記為中等難度&#xff0c;但只要掌握了正確的思路&#xff0c;就能…

SFT:大型語言模型專業化定制的核心技術體系——原理、創新與應用全景

本文由「大千AI助手」原創發布&#xff0c;專注用真話講AI&#xff0c;回歸技術本質。拒絕神話或妖魔化。搜索「大千AI助手」關注我&#xff0c;一起撕掉過度包裝&#xff0c;學習真實的AI技術&#xff01; 以下基于權威期刊、會議論文及技術報告&#xff0c;對監督微調&#x…