摘要:本文详细说明了在工作流 Activiti 框架中的 BPMN 流程定义整个运行的生命周期。
本文分享自华为云社区《本文详细说明了在工作流Activiti框架中的BPMN流程定义整个运行的生命周期》,作者:攻城狮 Chova。
BPMN 2.0 介绍
业务流程模型注解(BusinessProcess Modeling Notation - BPMN)是业务流程模型的一种标准图形注解.这个标准是由对象管理组(Object Management Group - OMG)维护的
BPMN 规范的 2.0 版本允许添加精确的技术细节在 BPMN 的图形和元素中,同时制定 BPMN 元素的执行语法.通过使用 XML 语言来指定业务流程的可执行语法,BPMN 规范已经演变为业务流程的语言,可以执行在任何兼容 BPMN2 的流程引擎中,同时依然可以使用强大的图形注解
简单来说,BPMN 即图标与标签的结合
定义一个流程
创建一个新的 XML 文件并命名,确认文件后缀为 .bpmn20.xml 或 .bpmn, 否则引擎无法发布
BPMN 2.0 根节点是 definitions 节点. 这个元素中,可以定义多个流程定义(不过建议每个文件只包含一个流程定义, 可以简化开发过程中的维护难度)
一个空的流程定义如下所示:注意 definitions 元素最少也要包含 xmlns 和 targetNamespace 的声明 targetNamespace 可以是任意值,它用来对流程实例进行分类
<definitions
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples">
<process id="myProcess" name="My First Process">
..
</process>
</definitions>
复制代码
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL
http://www.omg.org/spec/BPMN/2.0/20100501/BPMN20.xsd
复制代码
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess");
复制代码
==注意:== 它和 startProcessInstanceById 方法不同:这个方法期望使用 Activiti 引擎在发布时自动生成的 id.可以通过调用 processDefinition.getId() 方法获得这个值,生成的 id 的格式为 key:version, 最大长度限制为 64 个字符, 如果在启动时抛出了一个 ActivitiException: 说明生成的 id 太长了,需要限制流程的 key 的长度
BPMN 流程示例前提
已经安装 Activiti 并且能够运行 Activiti Demo
使用了独立运行的 H2 服务器
修改 db.properties,设置其中的 jdbc.url=jdbc:h2:tcp://localhost/activiti,然后启动独立服务器
目标
学习 Activiti 和一些基本的 BPMN 2.0 概念
最终结果是一个简单的 Java SE 程序可以发布流程定义,通过 Activiti 引擎 API 操作流程
使用一些 Activiti 相关的工具,构建自己的业务流程 web 应用
用例
流程图
空开始事件(左侧圆圈),后面是两个用户任务:制作月度财报和验证月度财报,最后是空结束事件(右侧粗线圆圈)
XML 内容
(空)开始事件是流程的入口
用户任务是流程中与操作者相关的任务声明:
第一个任务分配给 accountancy 组
第二个任务分配给 management 组
当流程达到空结束事件就会结束
这些元素都使用连线连接,这些连线拥有 source 和 target 属性,定义了连线的方向
<definitions id="definitions"
targetNamespace="http://activiti.org/bpmn20"
xmlns:activiti="http://activiti.org/bpmn"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
<process id="financialReport" name="Monthly financial report reminder process">
<startEvent id="theStart" />
<sequenceFlow id='flow1' sourceRef='theStart' targetRef='writeReportTask' />
<userTask id="writeReportTask" name="Write monthly financial report" >
<documentation>
Write monthly financial report for publication to shareholders.
</documentation>
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>accountancy</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
<sequenceFlow id='flow2' sourceRef='writeReportTask' targetRef='verifyReportTask' />
<userTask id="verifyReportTask" name="Verify monthly financial report" >
<documentation>
Verify monthly financial report composed by the accountancy department.
This financial report is going to be sent to all the company shareholders.
</documentation>
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>management</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
<sequenceFlow id='flow3' sourceRef='verifyReportTask' targetRef='theEnd' />
<endEvent id="theEnd" />
</process>
</definitions>
复制代码
启动一个流程实例
创建好业务流程的流程定义,就可以创建流程实例
一个流程实例对应了特定月度财报的创建和审批,所有流程实例都共享同一个流程定义
为了使用流程定义创建流程实例,首先要发布业务流程:
流程定义会保存到持久化的数据存储里,是为 Activiti 引擎特别配置的.所以部署好业务流程,在引擎重启后还能找到流程定义
BPMN 2.0 流程文件会解析成内存对象模型, 可以通过 Activiti API 操作
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("FinancialReportProcess.bpmn20.xml")
.deploy();
复制代码
启动一个新流程实例,使用我们定义在流程定义里的 id(对应 XML 文件中的 process 元素).==注意这里的 id 对于 Activiti 来说,应该叫做 key==,一般在流程模型中使用的 ID,在 Activiti 中都是 Key:比如任务 ID
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("financialReport");
复制代码
首先进入开始事件
开始事件之后,它会沿着所有的外出连线执行,到达第一个任务(“制作月度财报”)
Activiti 会把一个任务保存到数据库里.这时,分配到这个任务的用户或群组会被解析,也会保存到数据库里
需要注意,Activiti 引擎会继续执行流程的环节,除非遇到一个 等待状态:比如用户任务
在等待状态下,当前的流程实例的状态会保存到数据库中.直到用户决定完成任务才能改变这个状态
这时,引擎会继续执行,直到遇到下一个等待状态,或流程结束
如果中间引擎重启或崩溃,流程状态也会安全的保存在数据库里
任务创建之后,startProcessInstanceByKey 会在到达用户任务这个等待状态之后才会返回.这时,任务分配给了一个组,这意味着这个组是执行这个任务的候选组
现在将所有东西都放在一起,来创建一个简单的 java 程序:
创建一个 Java 项目,把 Activiti 的 jar 和依赖放到 classpath 下:这些都可以在 Activiti 发布包的 libs 目录下找到
在调用 Activiti 服务之前,我们必须构造一个 ProcessEngine,可以让我们访问服务
这里我们使用[单独运行]的配置,这会使用 demo 安装时的数据库来构建 ProcessEngine
public static void main(String[] args) {
// Create Activiti process engine
ProcessEngine processEngine = ProcessEngineConfiguration
.createStandaloneProcessEngineConfiguration()
.buildProcessEngine();
// Get Activiti services
RepositoryService repositoryService = processEngine.getRepositoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();
// Deploy the process definition
repositoryService.createDeployment()
.addClasspathResource("FinancialReportProcess.bpmn20.xml")
.deploy();
// Start a process instance
runtimeService.startProcessInstanceByKey("financialReport");
}
复制代码
任务列表
List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("kermit").list();
复制代码
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>accountancy</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
复制代码
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list();
复制代码
登录
点击组
创建一个新组
点击用户
把组分配给 fozzie
使用 fozzie/fozzie 登录
流程会执行到第一个用户任务.因为我们以 kermit 登录,在启动流程实例之后,就可以看到有了一个新的待领任务.选择任务页来查看这条新任务.注意即使流程被其他人启动,任务还是会被会计组里的所有人作为一个候选任务看到
领取任务
taskService.claim(task.getId(), "fozzie");
复制代码
List<Task> tasks = taskService.createTaskQuery().taskAssignee("fozzie").list();
复制代码
完成任务
taskService.complete(task.getId());
复制代码
需要一个外部信息来让流程实例继续执行
任务会把自己从运行库中删除
流程会沿着单独一个外出连线执行,移动到第二个任务(审批报告)
与第一个任务相同的机制会使用到第二个任务上,不同的是任务是分配给 management 组
完成任务是通过点击任务列表中的完成按钮
因为 Fozzie 不是会计,我们先从 Activiti Explorer 注销
然后使用 kermit 登陆(经理),第二个任务会进入未分配任务列表
结束流程
审批任务像之前一样查询和领取.
完成第二个任务会让流程执行到结束事件,就会结束
流程实例流程实例和所有相关的运行数据都会从数据库中删除
登录 Activiti Explorer 就可以进行验证,可以看到保存流程运行数据的表中已经没有数据:
可以使用 historyService 判断流程是否已经结束:
HistoryService historyService = processEngine.getHistoryService();
HistoricProcessInstance historicProcessInstance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();
System.out.println("Process instance end time: " + historicProcessInstance.getEndTime());
复制代码
源码
考虑到你可能会在 Activiti Explorer UI 中启动一些流程实例,这样,它会获得多个任务,而不是一个,所以代码可以一直正常运行:
public class TenMinuteTutorial {
public static void main(String[] args) {
// Create Activiti process engine
ProcessEngine processEngine = ProcessEngineConfiguration
.createStandaloneProcessEngineConfiguration()
.buildProcessEngine();
// Get Activiti services
RepositoryService repositoryService = processEngine.getRepositoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();
// Deploy the process definition
repositoryService.createDeployment()
.addClasspathResource("FinancialReportProcess.bpmn20.xml")
.deploy();
// Start a process instance
String procId = runtimeService.startProcessInstanceByKey("financialReport").getId();
// Get the first task
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list();
for (Task task : tasks) {
System.out.println("Following task is available for accountancy group: " + task.getName());
// claim it
taskService.claim(task.getId(), "fozzie");
}
// Verify Fozzie can now retrieve the task
tasks = taskService.createTaskQuery().taskAssignee("fozzie").list();
for (Task task : tasks) {
System.out.println("Task for fozzie: " + task.getName());
// Complete the task
taskService.complete(task.getId());
}
System.out.println("Number of tasks for fozzie: "
+ taskService.createTaskQuery().taskAssignee("fozzie").count());
// Retrieve and claim the second task
tasks = taskService.createTaskQuery().taskCandidateGroup("management").list();
for (Task task : tasks) {
System.out.println("Following task is available for accountancy group: " + task.getName());
taskService.claim(task.getId(), "kermit");
}
// Completing the second task ends the process
for (Task task : tasks) {
taskService.complete(task.getId());
}
// verify that the process is actually finished
HistoryService historyService = processEngine.getHistoryService();
HistoricProcessInstance historicProcessInstance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();
System.out.println("Process instance end time: " + historicProcessInstance.getEndTime());
}
}
复制代码
总结
定义网关来实现决策环节: 经理可以驳回财报,重新给会计创建一个任务
考虑使用变量: 可以保存或引用报告,把它显示到表单中
在流程最后加入服务任务: 把报告发给每个领导
点击关注,第一时间了解华为云新鲜技术~
评论