前言

        本文基于 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…