流程任务是工作流中非常重要的元素,流程任务包括五种,每种元素都有特定的属性和功能。
(1)服务任务(ServiceTask):用户调用外部服务和自动执行程序。
(2)发送任务(SendTask):向外部流程参与者发送消息。
(3)接收任务(ReceiveTask):等待外部流程参与者发送消息。
(4)用户任务(UserTask):最重要的流程元素,表示是流程参与者要亲自参与任务。
(5)脚本任务(ScriptTask):自动执行脚本的任务,当流程走到该节点时会自动执行指定的脚本。
- 用户任务
当流程运行到该节点时,会被阻塞,直到用户对该任务进行处理并进行提交。我们可以给用户任务指定任务处理的候选人和候选组、执行的时间等相关信息。
流程定义文件如下所示:
<userTask id="someTask" activiti:candidateUsers="user1, user2" activiti:assignee="user1"
activiti:dueDate="${dataVariable}">
......
除了在xml方式配置相关属性,我们还可以通过监听器配置用户任务的相关属性,节点的事件类型包括四种 create、delete、complete、assigment,除此之外,我们还可以通过流程定义文件向监听器的指定字段进行赋值。
创建流程定义文件。
<process id="my-process">
<startEvent id="start" />
<sequenceFlow id="flow1" sourceRef="start" targetRef="someTask"/>
<userTask id="someTask" name="someTask">
<extensionElements>
<activiti:taskListener event="create" class="activiti.MyCustomListner">
<activiti:field name="name" stringValue="测试属性"></activiti:field>
</activiti:taskListener>
</extensionElements>
</userTask>
<sequenceFlow id="flow2" sourceRef="someTask" targetRef="end" />
<endEvent id="end" />
</process>
编写任务监听器监听用户任务节点的创建。
public class MyCustomListner implements TaskListener {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(MyCustomListner.class);
private FixedValue name;
@Override
public void notify(DelegateTask delegateTask) {
delegateTask.addCandidateUsers(Arrays.asList("user1", "user2"));
delegateTask.addCandidateGroup("g1");
logger.info("流程定义文件中的name = {}", name.getValue(delegateTask));
}
}
编写测试类,查看user1
用户的待办任务,看监听器的配置是否生效。
@Rule
public ActivitiRule activitiRule = new ActivitiRule();
@Test
@Deployment(resources = "my-process.bpmn20.xml")
public void test() throws InterruptedException {
activitiRule.getRuntimeService().startProcessInstanceByKey("my-process");
List<Task> tasks = activitiRule.getTaskService().createTaskQuery()
.taskCandidateUser("user1").listPage(0, 100);
for (Task task : tasks) {
logger.info("task = {}", task.getName());
}
}
- 脚本任务
activiti默认支持的脚本类型包括三种:JUEL脚本(默认,也就是el表达式)、Groovy脚本(需要加入对应的依赖)、JavaScript脚本。
<scriptTask id="st" name="脚本任务" activiti:resultVariable="myVar" scriptFormat="groovy">
<script><![CDATA[]]></script>
</scriptTask>
在编写脚本的过程中,activiti提供了一些内置变量,我们可以直接使用这些内置变量,包括execution
、xxxService
、processEngineConfigutaion
。
由于流程定义文件中不会有提示,所以为了节省时间可以现将脚本放到自己的环境中运行,没有异常在复制到文件中。我们也可以使用脚本引擎来帮助我们测试编写的脚本是否返回预期的结果。
@Test
public void testScript() throws ScriptException {
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine juel = scriptEngineManager.getEngineByName("juel");
System.out.println(juel.eval("${2+2}"));
}
- 服务任务
服务任务执行java程序的方式包括三种。
- 执行实现
JavaDelegate
和ActivityBehavior
的类,这两个接口唯一的不同是是否阻塞流程,前者的执行不会阻塞流程,当程序经过节点该节点时,会执行java程序,执行完成会进入下一个节点,而后者会阻塞当前的流程,直到手动推动流程的进行。 - 执行一个对象表达式,通常是spring配置的bean,也可以是通过参数传进来的bean。
- 执行调用方法表达式和值表达式。
我们通过代码来演示以上三种调用方式。
(1)首先创建四个Bean用来测试,其中后两个Bean交由spring容器管理。
public class MyActivityBehavior implements ActivityBehavior {
private static final Logger logger = LoggerFactory.getLogger(ConfigMDCTest.class);
@Override
public void execute(DelegateExecution delegateExecution) {
logger.info("执行activityBehavior");
}
}
public class MyJavaDelegate implements JavaDelegate {
private static final Logger logger = LoggerFactory.getLogger(ConfigMDCTest.class);
@Override
public void execute(DelegateExecution delegateExecution) {
logger.info("执行自定义的 javaDelegate ");
}
}
public class MySpringJavaDelegate implements JavaDelegate {
private static final Logger logger = LoggerFactory.getLogger(ConfigMDCTest.class);
@Override
public void execute(DelegateExecution delegateExecution) {
logger.info("通过对象表达式执行的 MySpringJavaDelegate ");
}
}
public class SpringHelloBean {
private static final Logger logger = LoggerFactory.getLogger(ConfigMDCTest.class);
private String name;
public void sayHello(){
logger.info("调用方法表达式执行sayHello()");
}
public String getName() {
logger.info("调用值表达式执行getName");
return name;
}
public void setName(String name) {
this.name = name;
}
}
(2)创建流程定义文件,创建多个服务节点,分别使用以上三种方式执行java程序,通过占位符不止可以获取spring工厂的bean,还可以获取启动流程时传入的参数中指定的bean。
<process id="my-process">
<startEvent id="start" />
<serviceTask id="myJavaDelegate" name="测试javaDelegate" activiti:class="activiti.servicedemo.MyJavaDelegate">
</serviceTask>
<serviceTask id="mySpringJavaDelegate" name="测试对象表达式" activiti:delegateExpression="${springJavaDelegate}">
</serviceTask>
<serviceTask id="myIvokeExpression" name="测试调用方法表达式" activiti:expression="${springHelloBean.sayHello()}">
</serviceTask>
<serviceTask id="myValueExpress" name="测试值表达式" activiti:expression="${springHelloBean.name}">
</serviceTask>
<serviceTask id="myActivityBeHavior" name="测试ActivityBehavior" activiti:class="activiti.servicedemo.MyActivityBehavior">
</serviceTask>
<sequenceFlow id="flow1" sourceRef="start" targetRef="myJavaDelegate"/>
<sequenceFlow id="flow2" sourceRef="myJavaDelegate" targetRef="mySpringJavaDelegate"/>
<sequenceFlow id="flow3" sourceRef="mySpringJavaDelegate" targetRef="myIvokeExpression"/>
<sequenceFlow id="flow4" sourceRef="myIvokeExpression" targetRef="myValueExpress"/>
<sequenceFlow id="flow5" sourceRef="myValueExpress" targetRef="myActivityBeHavior"/>
<sequenceFlow id="flow6" sourceRef="myActivityBeHavior" targetRef="end" />
<endEvent id="end" />
</process>
(3)编写测试类。
@Test
@Deployment(resources = {"my-process.bpmn20.xml"})
public void test() throws Exception {
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("my-process");
//1. 程序并没有出现意象中的阻塞。
Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
logger.info("当前的task = {}", task.getName());
//2. 阻塞,手动推动流程的进行
activitiRule.getManagementService().executeCommand(new Command<Object>() {
@Override
public Object execute(CommandContext commandContext) {
commandContext.getAgenda().planTakeOutgoingSequenceFlowsOperation((ExecutionEntity) commandContext.getException(), false);
return null;
}
});
task = taskService.createTaskQuery().singleResult();
logger.info("当前的task = {}", task.getName());
}
- 顺序流和网关
顺序流包括条件顺序流和默认顺序流。
网关包括四种:排他网关(只有一条路)、并行网关(多条路都可以进行、会湖忽略你的条件)、包容性网关(只要满足条件的路都可以走)、基于事件网关(每个分支都会捕获相应的事件,只有对应分支的事件被捕获,流程才会进入该分支)
(1)单一网关
编写流程定义文件:
<process id="my-process">
<startEvent id="start"/>
<userTask id="fine" name="优秀"/>
<userTask id="pass" name="及格"/>
<userTask id="fail" name="不及格"/>
<endEvent id="end"/>
<exclusiveGateway id="classify"></exclusiveGateway>
<sequenceFlow sourceRef="start" targetRef="classify"/>
<sequenceFlow sourceRef="classify" targetRef="fine">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[ ${score >= 90}]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="classify" targetRef="pass">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[ ${score < 90 && score >= 60}]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="classify" targetRef="fail"></sequenceFlow>
</process>
进行编码测试
@Test
@Deployment(resources = "my-process.bpmn20.xml")
public void test() throws InterruptedException {
Map<String, Object> params = Maps.newHashMap();
params.put("score", 90);
activitiRule.getRuntimeService().startProcessInstanceByKey("my-process", params);
Task task = activitiRule.getTaskService().createTaskQuery().singleResult();
logger.info("task = {}", task.getName());
}
(2)并行网关
特征:流程并发,通过并行网关可以将流程执行流入多个分支,然后在最后汇总到并行网关中,只有当这些分支都汇入网关中,流程才会继续往下执行。另外并行网关是不支持条件判断的,它会忽略你对并行网关分支中的条件。
<process id="my-process">
<startEvent id="start"/>
<userTask id="pay" name="确认支付"/>
<userTask id="receive" name="确认收货"/>
<userTask id="complete" name="订单完成"/>
<endEvent id="end"/>
<parallelGateway id="startgateway"></parallelGateway>
<parallelGateway id="endgateway"></parallelGateway>
<sequenceFlow sourceRef="start" targetRef="startgateway"></sequenceFlow>
<sequenceFlow sourceRef="startgateway" targetRef="pay"></sequenceFlow>
<sequenceFlow sourceRef="startgateway" targetRef="receive"></sequenceFlow>
<sequenceFlow sourceRef="pay" targetRef="endgateway"></sequenceFlow>
<sequenceFlow sourceRef="receive" targetRef="endgateway"></sequenceFlow>
<sequenceFlow sourceRef="endgateway" targetRef="complete"></sequenceFlow>
<sequenceFlow sourceRef="complete" targetRef="end"></sequenceFlow>
</process>
进行编码测试
@Test
@Deployment(resources = "my-process.bpmn20.xml")
public void test() throws InterruptedException {
ProcessInstance processInstance = activitiRule.getRuntimeService().startProcessInstanceByKey("my-process");
List<Task> tasks = activitiRule.getTaskService().createTaskQuery().processInstanceId(processInstance.getId())
.listPage(0, 100);
//1. 其中会阻塞两个任务确认支付和确认收货
for (Task task : tasks) {
logger.info("task = {}", task.getName());
activitiRule.getTaskService().complete(task.getId());
}
tasks = activitiRule.getTaskService().createTaskQuery().processInstanceId(processInstance.getId())
.listPage(0, 100);
for (Task task : tasks) {
logger.info("task = {}", task.getName());
}
}
(3)包容性网关
特征:既可以走多个分支,也可以走单个分支,流程会进入满足条件的分支。
(4)基于事件网关
特征:当流程进入事件网关时,会被阻塞,直到某个分支对应的事件被触发,流程将会继续进行,其他分支的事件订阅清理,意思是只要该网关有一个分支被执行,就算其他分支感兴趣的事件被触发也不会再执行。
- 子流程
子流程包括四种:子流程、事件子流程、事务子流程、调用子流程(相当于引用了另一个流程定义文件)。
(1)普通子流程
特征:分层建模、数据隔离、事件范围、边界事件。
编写流程定义文件(为了简便,子流程没有使用并行网关,只有一个确认支付节点)
<process id="my-process">
<startEvent id="start"/>
<userTask id="complete" name="订单完成"/>
<userTask id="errorProcess" name="异常处理"/>
<endEvent id="end"/>
<subProcess id="subProcess">
<startEvent id="startSub"/>
<serviceTask id="pay" activiti:class="activiti.servicedemo.MyPayService"/>
<endEvent id="endSub"/>
<sequenceFlow sourceRef="startSub" targetRef="pay"/>
<sequenceFlow sourceRef="pay" targetRef="endSub"/>
</subProcess>
<boundaryEvent attachedToRef="subProcess" id="errorEvent">
<errorEventDefinition errorRef="notEnoughMoney" />
</boundaryEvent>
<sequenceFlow sourceRef="start" targetRef="subProcess"/>
<sequenceFlow sourceRef="subProcess" targetRef="complete"/>
<sequenceFlow sourceRef="errorEvent" targetRef="errorProcess"/>
<sequenceFlow sourceRef="complete" targetRef="end"/>
<sequenceFlow sourceRef="errorProcess" targetRef="end"/>
</process>
编写java程序类,执行确认支付流程。
public class MyPayService implements JavaDelegate {
private static final Logger logger = LoggerFactory.getLogger(ConfigMDCTest.class);
@Override
public void execute(DelegateExecution delegateExecution) {
logger.info("确认支付");
double money = (double)delegateExecution.getVariable("money");
if(money < 10){
throw new BpmnError("notEnoughMoney");
}
}
}
进行测试
@Test
@Deployment(resources = "my-process.bpmn20.xml")
public void test() throws InterruptedException {
Map<String ,Object> params = Maps.newHashMap();
params.put("money", 9.0);
ProcessInstance processInstance = activitiRule.getRuntimeService().startProcessInstanceByKey("my-process", params);
Task task = activitiRule.getTaskService().createTaskQuery().singleResult();
logger.info("task = {}", task.getName());
}
(2)事件子流程
特征:事件触发、业务独立、中断事件流程、错误开始事件。普通子流程和事件子流程的区别是:后者可以获取子流程的本地变量,而前者不可以。
修改流程定义文件
<process id="my-process">
<startEvent id="start"/>
<userTask id="complete" name="订单完成"/>
<serviceTask id="pay" activiti:class="activiti.servicedemo.MyPayService"/>
<endEvent id="end"/>
<subProcess id="subProcess" triggeredByEvent="true">
<startEvent id="startSub">
<errorEventDefinition errorRef="notEnoughMoney"/>
</startEvent>
<endEvent id="endSub"/>
<userTask id="errorProcess" name="异常处理"/>
<sequenceFlow sourceRef="startSub" targetRef="errorProcess"/>
<sequenceFlow sourceRef="errorProcess" targetRef="endSub"/>
</subProcess>
<sequenceFlow sourceRef="start" targetRef="pay"/>
<sequenceFlow sourceRef="pay" targetRef="complete"/>
<sequenceFlow sourceRef="complete" targetRef="end"/>
</process>
使用之前的测试类进行测试。
(3)事务子流程(试验阶段,很少用)
(4)调用式子流程
定义父类的流程定义文件
<process id="my-process">
<callActivity id="callActiviti" calledElement="my-process-sub">
<extensionElements>
<!--将父流程属性supKey对应的值传入到子流程的subkey属性中-->
<activiti:in target="subKey" source="supKey"/>
<activiti:in target="money" source="money"/>
<!--将子流程属性subError对应的值传入到父流程的supError属性中,如果子类抛出异常,则无法传出-->
<activiti:out target="supError" source="subError"/>
</extensionElements>
</callActivity>
<startEvent id="start"/>
<userTask id="complete" name="订单完成"/>
<userTask id="errorProcess" name="异常处理"/>
<endEvent id="end"/>
<boundaryEvent attachedToRef="callActiviti" id="errorEvent">
<errorEventDefinition errorRef="notEnoughMoney" />
</boundaryEvent>
<sequenceFlow sourceRef="start" targetRef="callActiviti"/>
<sequenceFlow sourceRef="callActiviti" targetRef="complete"/>
<sequenceFlow sourceRef="complete" targetRef="end"/>
<sequenceFlow sourceRef="errorEvent" targetRef="errorProcess"/>
<sequenceFlow sourceRef="errorProcess" targetRef="end"/>
</process>
定义子流程的流程定义文件
<process id="my-process-sub">
<startEvent id="startSub"/>
<serviceTask id="pay" activiti:class="activiti.servicedemo.MyPayService"/>
<endEvent id="endSub"/>
<sequenceFlow sourceRef="startSub" targetRef="pay"/>
<sequenceFlow sourceRef="pay" targetRef="endSub"/>
</process>
编写子流程调用的java程序
public class MyPayService implements JavaDelegate {
private static final Logger logger = LoggerFactory.getLogger(ConfigMDCTest.class);
@Override
public void execute(DelegateExecution delegateExecution) {
logger.info("variables = {}",delegateExecution.getVariables());
logger.info("确认支付");
delegateExecution.setVariable("subError","子类的值");
double money = (double)delegateExecution.getVariable("money");
if(money < 10){
throw new BpmnError("notEnoughMoney");
}
}
}
编写测试类进行测试:
@Test
@Deployment(resources = {"my-process.bpmn20.xml", "my-process-sub.bpmn20.xml"})
public void test() throws InterruptedException {
Map<String ,Object> params = Maps.newHashMap();
params.put("money", 11.0);
params.put("supKey", "父类的值");
ProcessInstance processInstance = activitiRule.getRuntimeService().startProcessInstanceByKey("my-process", params);
Task task = activitiRule.getTaskService().createTaskQuery().singleResult();
logger.info("task = {}", task.getName());
Map<String, Object> variables = activitiRule.getRuntimeService().getVariables(processInstance.getId());
logger.info("variables = {}", variables);
}