jBPM 对于所有的流程执行操作默认都是同步的。所以我们调用 ExecutionService.startProcessInstanceById
或 ExecutionService.singalProcessInstanceById
之类的方法时,会让流程实例一直处于执行状态,直到流程实例到达一个 “等待” 状态后才会被中断。
同步执行之所以是默认的,是因为它有这些优点:
* 用户业务系统的事务可以很容易地接入 jBPM,这样 jBPM 的数据库持久化操作可以在业务系统的事务环境中完成。
* 当流程执行过程中,出现错误时,客户端可以立即获得一个异常。
一般情况下,在流程执行的两个等待状态之间,需要完成的工作都是一些耗时比较短的活动(自动活动),所以我们通常希望能够在一个事务中执行所有的这些工作。因此,jBPM 工作流引擎会在客户端发起的线程中同步执行流程的所有工作,直到陷入等待状态或流程结束。
jBPM 工作流引擎也支持异步执行。流程一旦进入异步执行,异步的消息就会被当做当前事务的一部分被发送出去,当前事务立即被提交;同时, 工作流引擎会为异步执行的活动启动一个新事务,最后,引擎会通过异步消息通知客户端,是否已完成异步执行工作(job)。
大多数活动都支持异步属性:
属性 | 类型 | 默认值 | 是否必需 | 描述 |
---|---|---|---|---|
continue | sync / async / exclusive | sync | 可选 | 异步属性 |
* sync:同步执行。
* async:异步执行,当前事务被自动提交,指定的活动会在一个新事务中执行。工作流引擎使用异步消息来通知我们是否已完成异步执行工作,异步消息会被包含在这个新事务中。
* exclusive| 独占模式的异步执行。当前事务也会被自动提交,指定的活动会在一个新事务中执行。。工作流引擎使用异步消息来通知我们是否已完成异步执行工作。这与 async 相同。不同的是,这个异步消息具有独占性,即工作流引擎会为每一个异步消息启动一个新的事务,以确保同一个流程实例中的每个异步工作都是绝对排他,非同时被执行。这样做可以避免资源互锁,但系统开销也会变大。在服务器集群环境中,这个属性很重要。
1 异步活动
jPDL:
<?xml version="1.0" encoding="UTF-8"?>
<process name="AsyncActivity" xmlns="http://jbpm.org/4.4/jpdl">
<start g="224,202,48,48" name="start1">
<transition to="生成 PDF"/>
</start>
<!-- 耗时活动 1(异步) -->
<java class="net.deniro.jbpm.test.async.activity.Application" continue="async" g="348,199,103,52" method="generatePdf" name="生成 PDF">
<transition to="计算"/>
</java>
<!-- 耗时活动 2(异步) -->
<java class="net.deniro.jbpm.test.async.activity.Application" continue="async" g="513,200,120,52" method="calculatePrimes" name="计算">
<transition to="end1"/>
</java>
<end g="682,204,7,8" name="end1"/>
</process>
假设这里有两个活动都很耗时,所以我们都把它们设置为异步方式执行。
现在模拟这两个耗时的活动:
jPDL:
public class Application implements Serializable {
public void generatePdf() throws InterruptedException {
Thread.sleep(1000);
}
public void calculatePrimes() throws InterruptedException {
Thread.sleep(2000);
}
}
注意:在生产环境中,job-executor (配置在 jbpm.jobexecutor.cfg.xml)会自动获得异步消息并执行它。这里为了测试方便,所以没有设置 job-executor,我们使用 managementService.executeJob(job.getId());
来执行异步操作。
单元测试:
//发起实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey
("AsyncActivity");
String pId = processInstance.getId();
//断言流程处于异步执行状态
assertEquals(Execution.STATE_ASYNC, processInstance.getState());
//获取异步消息队列中的第一条消息
Job job = managementService.createJobQuery().processInstanceId(pId).uniqueResult();
//手动执行
managementService.executeJob(job.getId());
processInstance = executionService.findProcessInstanceById(pId);
//断言流程处于异步执行状态(因为还有一条异步消息)
assertEquals(Execution.STATE_ASYNC, processInstance.getState());
//获取异步消息队列中的第二条消息
job = managementService.createJobQuery().processInstanceId(pId).uniqueResult();
managementService.executeJob(job.getId());
//断言流程已结束
assertNull(executionService.findProcessInstanceById(pId));
2 异步分支与聚合
在这个定义中,异步执行是并行执行的。
jPDL:
<?xml version="1.0" encoding="UTF-8"?>
<process name="AsyncFork" xmlns="http://jbpm.org/4.4/jpdl">
<start g="164,210,48,48" name="start1">
<transition to="fork1"/>
</start>
<fork g="280,212,48,48" name="fork1">
<!-- 让并行的流程分支以独占的方式异步执行-->
<on event="end" continue="exclusive"/>
<transition g="301,187:" to="货物装船"/>
<transition g="304,296:" to="汇款"/>
</fork>
<java class="net.deniro.jbpm.test.fork.Application" g="418,154,92,52" method="shipGoods"
name="货物装船">
<transition g="613,178:" to="join1"/>
</java>
<java class="net.deniro.jbpm.test.fork.Application" g="417,274,92,52" method="sendBill"
name="汇款">
<transition g="612,303:" to="join1"/>
</java>
<!-- 流程聚合-->
<join g="589,226,48,48" name="join1">
<transition to="end1"/>
</join>
<end g="700,225,48,48" name="end1"/>
</process>
我们在 fork 活动的 end 事件中,设置了独占异步属性,这将导致这两个分支指向的活动,都会使用独占的方式异步执行。
这两个 Job 对象将分别持久化,分别在各自的独立事务中执行相应的业务逻辑,然后聚合到 join 活动,在聚合活动时,两个事务可能会同时操作同一个流程执行实例(execution 对象),这可能会导致一个潜在的乐观锁失败问题。
单元测试:
//发起实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey
("AsyncFork");
String pId = processInstance.getId();
//获取异步消息队列的消息列表
List<Job> jobs = managementService.createJobQuery().processInstanceId(pId).list();
//有两个分支
assertEquals(2, jobs.size());
Job job = jobs.get(0);
//手动执行其一
managementService.executeJob(job.getId());
//手动执行其二
job = jobs.get(1);
managementService.executeJob(job.getId());
//断言流程已结束
assertNull(executionService.findProcessInstanceById(pId));