BPMN 2.0 概述
BPMN 2.0(Business Process Model and Notation)
-
是一套业务流程模型与符号建模标准
-
精准执行的语义来描述元素操作
-
以XML为载体,以符号可视化业务
BPMN 2.0 元素
流对象 (Flow Object)
- 活动(Activities)【User Task ,Service Task...】
- 事件 (Events) 【Start Event, End Event...】
- 网关(GateWays 【Exclusive GateWay...】
案例(电商购物工作流程模型)
BPMN 2.0 -业务过程模型和符号
BPMN 2.0 流程事件-事件分类
事件分类
- 位置分类
- 特性分类
- 事件定义分类
事件分类-按照位置分类
- 开始事件
- 中间事件/边界事件
- 结束事件
事件分类-按照特性分类
- 捕获事件(Catching)
- 抛出事件(Throwing)
事件分类-按照定义分类
- 定时事件
- 错误事件
- 信号事件
- 消息事件
定时事件定义
- 指定时间(timeDate)
- 指定持续时间(timeDuration)
- 周期执行(timeCycle)
定时开始事件
定时边界事件
- Java测试代码
private static final Logger LOGGER = LoggerFactory.getLogger(TimerEventTests.class);
@Rule
// public ActivitiRule activitiRule = new ActivitiRule("activiti-mysql.cfg.xml");
public ActivitiRule activitiRule = new ActivitiRule();
@Test
@Deployment(resources = {"my-process-timer-boundary.bpmn20.xml"})
public void testTimerBoundary() throws InterruptedException {
ProcessInstance processInstance = activitiRule.getRuntimeService()
.startProcessInstanceByKey("my-process");
List<Task> tasks = activitiRule.getTaskService().createTaskQuery().listPage(0, 100);
for (Task task : tasks) {
LOGGER.info("task.name = {}", task.getName());
}
LOGGER.info("task.size() = {}", tasks.size());
Thread.sleep(1000 * 15);
List<Task> tasks1 = activitiRule.getTaskService().createTaskQuery().listPage(0, 100);
for (Task task : tasks1) {
LOGGER.info("task.name = {}", task.getName());
}
LOGGER.info("task.size() = {}", tasks1.size());
}
-
my-process-timer-boundary.bpmn20.xml
<process id="my-process"> <startEvent id="startEvent" name="startEvent"/> <userTask id="commonTask" name="Common Task"/> <boundaryEvent attachedToRef="commonTask" id="boundaryEvent" name="Timer" cancelActivity="true"> <timerEventDefinition> <timeDuration>PT5S</timeDuration><!--流程部署完5S之后--> </timerEventDefinition> </boundaryEvent> <userTask id="timeoutTask" name="Timeout Task"></userTask> <endEvent id="end1"></endEvent> <endEvent id="end2"></endEvent> <sequenceFlow sourceRef="startEvent" targetRef="commonTask"/> <sequenceFlow sourceRef="commonTask" targetRef="end1"/> <sequenceFlow sourceRef="boundaryEvent" targetRef="timeoutTask"/> <sequenceFlow sourceRef="timeoutTask" targetRef="end2"/> </process>
-
activiti.cfg.xml
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration"> <property name="jdbcUrl" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000;MVCC=TRUE" /> <property name="jdbcDriver" value="org.h2.Driver" /> <property name="jdbcUsername" value="sa" /> <property name="jdbcPassword" value="" /> <property name="asyncExecutorActivate" value="true"/> <property name="enableVerboseExecutionTreeLogging" value="true"/> </bean>
-
执行结果
BPMN 2.0 流程事件-错误事件
错误事件定义
错误边界事件(reviewSalesLead.bpmn20.xml)
-
reviewSalesLead.bpmn20.xml
<error id="notEnoughInfoError" errorCode="not_enough_info"/> <process id="reviewSaledLead" name="Review sales lead"> <startEvent id="theStart" activiti:initiator="initiator"/> <sequenceFlow id="flow1" sourceRef="theStart" targetRef="provideNewSalesLead"/> <userTask id="provideNewSalesLead" name="Provide new sales lead" activiti:assignee="${initiator}"> <extensionElements> <activiti:formProperty id="customerName" name="Customer name" type="string" required="true"/> <activiti:formProperty id="potentialProfit" name="Potential profit" type="long"/> <activiti:formProperty id="details" name="Details" type="string"/> </extensionElements> </userTask> <sequenceFlow id="flow2" sourceRef="provideNewSalesLead" targetRef="reviewSalesLeadSubProcess"/> <subProcess id="reviewSalesLeadSubProcess" name="Review sales lead"> <startEvent id="subProcessStart"/> <sequenceFlow id="flow3" sourceRef="subProcessStart" targetRef="fork"/> <sequenceFlow id="flow4" sourceRef="fork" targetRef="reviewProfitability"/> <parallelGateway id="fork"/> <sequenceFlow id="flow5" sourceRef="fork" targetRef="reviewCustomerRating"/> <userTask id="reviewCustomerRating" name="Review customer rating" activiti:candidateGroups="accountancy"/> <sequenceFlow id="flow6" sourceRef="reviewCustomerRating" targetRef="subProcessEnd1"/> <endEvent id="subProcessEnd1"/> <userTask id="reviewProfitability" name="Review profitability" activiti:candidateGroups="management"> <documentation> ${initiator} has published a new sales lead: ${customerName}. Details: ${details} </documentation> <extensionElements> <activiti:formProperty id="notEnoughInformation" name="Do you believe this customer is profitable?" type="enum" required="true"> <activiti:value id="false" name="Yes"/> <activiti:value id="true" name="No (= request more info)"/> </activiti:formProperty> </extensionElements> </userTask> <sequenceFlow id="flow7" sourceRef="reviewProfitability" targetRef="enoughInformationCheck"/> <exclusiveGateway id="enoughInformationCheck" name="Enough information?"/> <sequenceFlow id="flow8" sourceRef="enoughInformationCheck" targetRef="notEnoughInformationEnd"> <conditionExpression>${notEnoughInformation == 'true'}</conditionExpression> </sequenceFlow> <sequenceFlow id="flow9" sourceRef="enoughInformationCheck" targetRef="subProcessEnd2"> <conditionExpression>${notEnoughInformation == 'false'}</conditionExpression> </sequenceFlow> <endEvent id="subProcessEnd2"/> <endEvent id="notEnoughInformationEnd"> <errorEventDefinition errorRef="notEnoughInfoError"/> </endEvent> </subProcess> <sequenceFlow id="flow10" sourceRef="reviewSalesLeadSubProcess" targetRef="storeLeadInCrmSystem"/> <boundaryEvent attachedToRef="reviewSalesLeadSubProcess" cancelActivity="true" id="catchNotEnoughInformationError"> <errorEventDefinition errorRef="notEnoughInfoError"/> </boundaryEvent> <sequenceFlow id="flow11" sourceRef="catchNotEnoughInformationError" targetRef="provideAdditionalDetails"/> <userTask id="provideAdditionalDetails" name="Provide additional details" activiti:assignee="${initiator}"> <documentation>Provide additional details for ${customerName}.</documentation> <extensionElements> <activiti:formProperty id="details" name="Additional details" type="string" required="true"/> </extensionElements> </userTask> <sequenceFlow id="flow12" sourceRef="provideAdditionalDetails" targetRef="reviewSalesLeadSubProcess"/> <task id="storeLeadInCrmSystem" name="Store lead in CRM system"/> <sequenceFlow id="flow13" sourceRef="storeLeadInCrmSystem" targetRef="processEnd"/> <endEvent id="processEnd"/> </process>
-
BoundaryErrorEventTest
public class BoundaryErrorEventTest extends PluggableActivitiTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
// Normally the UI will do this automatically for us
Authentication.setAuthenticatedUserId("kermit");
}
@Override
protected void tearDown() throws Exception {
Authentication.setAuthenticatedUserId(null);
super.tearDown();
}
@Deployment(resources = { "reviewSalesLead.bpmn20.xml" })
public void testReviewSalesLeadProcess() {
// After starting the process, a task should be assigned to the
// 'initiator' (normally set by GUI)
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("details", "very interesting");
variables.put("customerName", "Alfresco");
String procId = runtimeService.startProcessInstanceByKey("reviewSaledLead", variables).getId();
Task task = taskService.createTaskQuery().taskAssignee("kermit").singleResult();
assertEquals("Provide new sales lead", task.getName());
// After completing the task, the review subprocess will be active
taskService.complete(task.getId());
Task ratingTask = taskService.createTaskQuery().taskCandidateGroup("accountancy").singleResult();
assertEquals("Review customer rating", ratingTask.getName());
Task profitabilityTask = taskService.createTaskQuery().taskCandidateGroup("management").singleResult();
assertEquals("Review profitability", profitabilityTask.getName());
// Complete the management task by stating that not enough info was
// provided
// This should throw the error event, which closes the subprocess
variables = new HashMap<String, Object>();
variables.put("notEnoughInformation", true);
taskService.complete(profitabilityTask.getId(), variables);
// The 'provide additional details' task should now be active
Task provideDetailsTask = taskService.createTaskQuery().taskAssignee("kermit").singleResult();
assertEquals("Provide additional details", provideDetailsTask.getName());
// Providing more details (ie. completing the task), will activate the
// subprocess again
taskService.complete(provideDetailsTask.getId());
List<Task> reviewTasks = taskService.createTaskQuery().orderByTaskName().asc().list();
assertEquals("Review customer rating", reviewTasks.get(0).getName());
assertEquals("Review profitability", reviewTasks.get(1).getName());
// Completing both tasks normally ends the process
taskService.complete(reviewTasks.get(0).getId());
variables.put("notEnoughInformation", false);
taskService.complete(reviewTasks.get(1).getId(), variables);
assertProcessEnded(procId);
}
}
BPMN2.0流程事件-信号消息事件
信号开始事件
信号中间事件
消息事件定义
BPMN2.0 -流程任务
核心流程任务
用户任务(User Task)定义
用户任务(User Task)候选人/组配置
用户任务(User Task)代理人配置
通过任务监听自定义配置
- 实现
- Java代码
UserTaskTests
public class UserTaskTests {
private static final Logger LOGGER = LoggerFactory.getLogger(UserTaskTests.class);
@Rule
public ActivitiRule activitiRule = new ActivitiRule();
@Test
@Deployment(resources = {"my-process-usertask.bpmn20.xml"})
public void testUsertTask() {
ProcessInstance processInstance = activitiRule.getRuntimeService().startProcessInstanceByKey("my-process");
TaskService taskService = activitiRule.getTaskService();
Task task = taskService.createTaskQuery().taskCandidateUser("user1").singleResult();
LOGGER.info("user = {}", task);
task = taskService.createTaskQuery().taskCandidateUser("user2").singleResult();
LOGGER.info("user2 = {}", task);
task = taskService.createTaskQuery().taskCandidateGroup("group1").singleResult();
LOGGER.info("group1 = {}", task);
taskService.claim(task.getId(), "user2");
LOGGER.info("claim task.id() = {} by user2", task.getId());
task = taskService.createTaskQuery().taskCandidateOrAssigned("user1").singleResult();
LOGGER.info("find by user1 task = {}", task);
task = taskService.createTaskQuery().taskCandidateOrAssigned("user1").singleResult();
LOGGER.info("find by user2 task = {}", task);
}
@Test
@Deployment(resources = {"my-process-usertask2.bpmn20.xml"})
public void testUserTask2() {
ProcessInstance processInstance = activitiRule.getRuntimeService().startProcessInstanceByKey("my-process");
TaskService taskService = activitiRule.getTaskService();
Task task = taskService.createTaskQuery().taskCandidateUser("user1").singleResult();
LOGGER.info("load by user task = {}", task);
taskService.complete(task.getId());
}
}
my-process-usertask2.bpmn20.xml
<process id="my-process">
<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="someTask"/>
<userTask id="someTask" name="User Task">
<extensionElements>
<activiti:taskListener event="create"
class="com.imooc.activiti.example.MyTaskListener"/>
<activiti:taskListener event="complete"
class="com.imooc.activiti.example.MyTaskListener"/>
</extensionElements>
</userTask>
<sequenceFlow id="flow2" sourceRef="someTask" targetRef="end"/>
<endEvent id="end"/>
</process>
MyTaskListener.java
public class MyTaskListener implements TaskListener {
private static final Logger LOGGER = LoggerFactory.getLogger(MyTaskListener.class);
@Override
public void notify(DelegateTask delegateTask) {
String eventName = delegateTask.getEventName();
if (StringUtils.equals("create", eventName)) {
LOGGER.info("config by listener");
delegateTask.addCandidateUsers(Lists.newArrayList("user1", "user2"));
delegateTask.addCandidateGroup("group1");
delegateTask.setVariable("key1", "value1");
delegateTask.setDueDate(DateTime.now().plusDays(3).toDate());
} else if (StringUtils.equals("complete", eventName)) {
LOGGER.info("task complete");
}
}
}
执行结果
BPMN2.0脚本任务
脚本任务(Script Task)
- JUEL 脚本(默认)
- Groovy脚本 (依赖 groovy-all.jar)
- JavaScript 脚本
脚本任务(Script Task)内置变量
脚本任务(Script Task)设置返回值
使用groovy脚本
my-process-scripttask1.bpmn20.xml
<process id="my-process">
<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="someTask"/>
<scriptTask id="someTask" name="Script Task" scriptFormat="groovy">
<script>
def myValue = "value123"
execution.setVariable("myKey",myValue)
</script>
</scriptTask>
<sequenceFlow id="flow2" sourceRef="someTask" targetRef="end"/>
<endEvent id="end"/>
</process>
testScriptTask
@Test
@Deployment(resources = {"my-process-scripttask1.bpmn20.xml"})
public void testScriptTask() {
ProcessInstance processInstance = activitiRule.getRuntimeService().startProcessInstanceByKey("my-process");
List<HistoricVariableInstance> historicVariableInstanceList = activitiRule.getHistoryService().createHistoricVariableInstanceQuery()
.processInstanceId(processInstance.getId())
.orderByVariableName().asc().listPage(0, 100);
for (HistoricVariableInstance historicVariableInstance : historicVariableInstanceList) {
LOGGER.info("variable = {}", historicVariableInstance);
}
LOGGER.info("variables.size() = {}", historicVariableInstanceList.size());
}
运行结果
my-process-scripttask2.bpmn20.xml
<process id="my-process">
<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="someTask"/>
<scriptTask id="someTask" name="Script Task" scriptFormat="juel" activiti:resultVariable="mySum">
<script>
#{key1 + key2}
</script>
</scriptTask>
<sequenceFlow id="flow2" sourceRef="someTask" targetRef="end"/>
<endEvent id="end"/>
</process>
testScriptTask2
@Test
@Deployment(resources = {"my-process-scripttask2.bpmn20.xml"})
public void testScriptTask2() {
Map<String, Object> variables = Maps.newHashMap();
variables.put("key1", 3);
variables.put("key2", 5);
ProcessInstance processInstance = activitiRule.getRuntimeService()
.startProcessInstanceByKey("my-process", variables);
List<HistoricVariableInstance> historicVariableInstanceList = activitiRule.getHistoryService().createHistoricVariableInstanceQuery()
.processInstanceId(processInstance.getId())
.orderByVariableName().asc().listPage(0, 100);
for (HistoricVariableInstance historicVariableInstance : historicVariableInstanceList) {
LOGGER.info("variable = {}", historicVariableInstance);
}
LOGGER.info("variables.size() = {}", historicVariableInstanceList.size());
}
运行结果
my-process-scripttask3.bpmn20.xml
<process id="my-process">
<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="someTask"/>
<scriptTask id="someTask" name="Script Task" scriptFormat="javascript"
activiti:resultVariable="mySum">
<script>
key1 + key2
</script>
</scriptTask>
<sequenceFlow id="flow2" sourceRef="someTask" targetRef="end"/>
<endEvent id="end"/>
</process>
testScriptTask3
@Test
@Deployment(resources = {"my-process-scripttask3.bpmn20.xml"})
public void testScriptTask3() {
Map<String, Object> variables = Maps.newHashMap();
variables.put("key1", 3);
variables.put("key2", 5);
ProcessInstance processInstance = activitiRule.getRuntimeService()
.startProcessInstanceByKey("my-process", variables);
List<HistoricVariableInstance> historicVariableInstanceList = activitiRule.getHistoryService().createHistoricVariableInstanceQuery()
.processInstanceId(processInstance.getId())
.orderByVariableName().asc().listPage(0, 100);
for (HistoricVariableInstance historicVariableInstance : historicVariableInstanceList) {
LOGGER.info("variable = {}", historicVariableInstance);
}
LOGGER.info("variables.size() = {}", historicVariableInstanceList.size());
}
运行结果
BPMN 2.0 服务任务 (Java Service Task)
服务任务(Java Service Task)执行Java程序的方法
- 执行实现JavaDelegate或ActivitiBehavior的类
- 执行JavaDelegate对象表达式,通常是Spring配置Bean
- 执行调用方法表达式和值表达式
执行实现JavaDelegate或ActivitiBehavior的类
my-process-servicetask1.bpmn20.xml
<process id="my-process">
<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="someTask"/>
<serviceTask id="someTask" name="User Task"
activiti:class="com.imooc.activiti.example.MyJavaDelegate"/>
<sequenceFlow id="flow2" sourceRef="someTask" targetRef="end"/>
<endEvent id="end"/>
</process>
testServiceTask
@Test
@Deployment(resources = {"my-process-servicetask1.bpmn20.xml"})
public void testServiceTask() {
ProcessInstance processInstance = activitiRule.getRuntimeService()
.startProcessInstanceByKey("my-process");
List<HistoricActivityInstance> historicActivityInstances = activitiRule.getHistoryService()
.createHistoricActivityInstanceQuery()
.orderByHistoricActivityInstanceEndTime()
.asc().listPage(0, 100);
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
LOGGER.info("activiti = {}", historicActivityInstance);
}
}
运行结果
testServiceTask2
@Test
@Deployment(resources = {"my-process-servicetask2.bpmn20.xml"})
public void testServiceTask2() {
ProcessInstance processInstance = activitiRule.getRuntimeService()
.startProcessInstanceByKey("mu-process");
List<HistoricActivityInstance> historicActivityInstances = activitiRule.getHistoryService()
.createHistoricActivityInstanceQuery()
.orderByHistoricActivityInstanceEndTime()
.asc().listPage(0, 100);
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
LOGGER.info("activiti = {}", historicActivityInstance);
}
Execution execution = activitiRule.getRuntimeService()
.createExecutionQuery()
.activityId("someTask")
.singleResult();
LOGGER.info("execution = {}", execution);
ManagementService managementService = activitiRule.getManagementService();
managementService.executeCommand((CommandContext commandContext) -> {
ActivitiEngineAgenda agenda = commandContext.getAgenda();
agenda.planTakeOutgoingSequenceFlowsOperation(
(ExecutionEntity) execution, false);
return null;
});
historicActivityInstances = activitiRule.getHistoryService()
.createHistoricActivityInstanceQuery()
.orderByHistoricActivityInstanceEndTime()
.asc().listPage(0, 100);
for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
LOGGER.info("activiti = {}", historicActivityInstance);
}
}
my-process-servicetask2.bpmn20.xml
<process id="my-process">
<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="someTask"/>
<serviceTask id="someTask" name="User Task"
activiti:class="com.imooc.activiti.example.MyActivitiBehavior"/>
<sequenceFlow id="flow2" sourceRef="someTask" targetRef="end"/>
<endEvent id="end"/>
</process>
JavaDelegate注入属性
BPMN2.0顺序流和网关
my-process-exclusiveGateway1.bpmn20.xml
<process id="my-process">
<startEvent id="start"></startEvent>
<exclusiveGateway id="gateway"/>
<userTask id="task1" name="精英"/>
<userTask id="task2" name="优秀"/>
<userTask id="task3" name="普通"/>
<endEvent id="end"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="gateway"/>
<sequenceFlow sourceRef="gateway" targetRef="task1">
<conditionExpression>
<![CDATA[${score>= 90}]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="gateway" targetRef="task2">
<conditionExpression>
<![CDATA[${score>=75 && score<90}]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="gateway" targetRef="task3"/>
<sequenceFlow sourceRef="task1" targetRef="end"/>
<sequenceFlow sourceRef="task2" targetRef="end"/>
<sequenceFlow sourceRef="task3" targetRef="end"/>
</process>
testExclusiveGatewayTask
@Rule
public ActivitiRule activitiRule = new ActivitiRule();
@Test
@Deployment(resources = {"my-process-exclusiveGateway1.bpmn20.xml"})
public void testExclusiveGatewayTask() {
Map<String, Object> variables = Maps.newHashMap();
variables.put("score", 93);
ProcessInstance processInstance = activitiRule.getRuntimeService()
.startProcessInstanceByKey("my-process", variables);
Task task = activitiRule.getTaskService().createTaskQuery().singleResult();
LOGGER.info("task.name = {}", task.getName());
}
输出结果为
testExclusiveGatewayTask1
@Test
@Deployment(resources = {"my-process-exclusiveGateway1.bpmn20.xml"})
public void testExclusiveGatewayTask1() {
Map<String, Object> variables = Maps.newHashMap();
variables.put("score", 70);
ProcessInstance processInstance = activitiRule.getRuntimeService()
.startProcessInstanceByKey("my-process", variables);
Task task = activitiRule.getTaskService().createTaskQuery().singleResult();
LOGGER.info("task.name = {}", task.getName());
}
输出结果
并行网关
my-process-parallelGateway1.bpmn20.xml
<process id="my-process">
<startEvent id="start"/>
<parallelGateway id="parallelStart"/>
<userTask id="task1" name="确认支付"/>
<userTask id="task2" name="确认收货"/>
<parallelGateway id="parallelEnd"/>
<userTask id="task3" name="订单完成"/>
<endEvent id="end"/>
<sequenceFlow sourceRef="start" targetRef="parallelStart"/>
<sequenceFlow sourceRef="parallelStart" targetRef="task1"/>
<sequenceFlow sourceRef="parallelStart" targetRef="task2"/>
<sequenceFlow sourceRef="task1" targetRef="parallelEnd"/>
<sequenceFlow sourceRef="task2" targetRef="parallelEnd"/>
<sequenceFlow sourceRef="parallelEnd" targetRef="task3"/>
<sequenceFlow sourceRef="task3" targetRef="end"/>
</process>
testParallelGatewayTask1
@Test
@Deployment(resources = {"my-process-parallelGateway1.bpmn20.xml"})
public void testParallelGatewayTask1() {
ProcessInstance processInstance = activitiRule.getRuntimeService()
.startProcessInstanceByKey("my-process");
List<Task> taskList = activitiRule.getTaskService().createTaskQuery()
.processInstanceId(processInstance.getId())
.listPage(0, 100);
for (Task task : taskList) {
LOGGER.info("task.name = {}", task.getName());
}
LOGGER.info("taskList.size() = {}", taskList.size());
}