本文节选自《疯狂工作流讲义(第2版)》
京东购买地址:https://item.jd.com/12246565.html
工作流Activiti6电子书:http://blog.csdn.net/boxiong86/article/details/78488562
工作流Activiti6教学视频:http://blog.csdn.net/boxiong86/article/details/78608585
在11.5.4章节中,讲解了补偿边界事件的使用,在该小节的案例中,使用了补偿中间事件触发补偿边界事件,补偿中间事件主要用于触发当前所在执行流的全部补偿Catching事件(补偿边界事件),补偿中间事件是Throwing事件,每个补偿事件都需要有关联的处理者,当补偿事件触发时,会转给这些处理者处理补偿,本小节将讲解在使用补偿中间事件的各种特性。
当补偿中间事件被触发,会触发当前执行流的补偿Catching事件,如果一个附有补偿Catching事件的流程活动会执行多次(具有多实例的特性),那么在进行补偿时,补偿次数与流程活动的执行次数一样。BPMN2.0中允许多实例的流程活动,在一个流程中,一个流程活动(某些部分或者全部)可以被执行多次,现定义一个测试流程,如图11-16所示。
图11-16 测试补偿次数流程
图11-16表示一个简单的流程,流程首先会到达“正常工作”的ServiceTask,然后会到达“抛出错误”的ServiceTask,这个“抛出错误”的ServiceTask总会抛出错误,然后触发错误边界事件,然后流程会到达补偿中间事件,此时补偿中间事件会触发“正常工作”的补偿边界事件,补偿将会由“补偿工作”的ServiceTask执行,该流程对应的流程文件内容如代码清单11-37所示。
代码清单11-37:codes\11\11.7\compensation-times\resource\bpmn\CompensationTimes.bpmn
activiti:class="org.crazyit.activiti.RegularWork">
isSequential="true">
iceTask>
attachedToRef="servicetask1">
activiti:class="org.crazyit.activiti.ThrowError">
attachedToRef="servicetask2">
name="SignalThrowEvent">
targetRef="servicetask1">
targetRef="servicetask2">
targetRef="endevent1">
targetRef="signalintermediatethrowevent1">
sourceRef="signalintermediatethrowevent1" targetRef="endevent2">
targetRef="servicetask3">
rocess>
代码清单11-37中的①定义了一个ServiceTask,这是一个多实例的ServiceTask,多实例活动是为了让流程活动能重复执行,声明一个流程活动需要执行多次,可以为serviceTask加入multiInstanceLoopCharacteristics子元素,在本例中,定义了“正常工作”的ServiceTask会执行3次,这个ServiceTask对应的类为RegularWork,该类实现如代码清单11-38所示。
代码清单11-38:codes\11\11.7\compensation-times\src\org\crazyit\activiti\RegularWork.java
public class RegularWork implements JavaDelegate {
int i = 0;
public void execute(DelegateExecution execution) throws Exception {
i++;
System.out.println("处理第 " + i + " 次正常工作...");
}
}
RegularWork类会打印执行次数,由于本例定义了“正常工作”的ServiceTask会执行3次,因此可知道此处会输出3次。代码清单11-37中的④定义了一个“补偿工作”的ServiceTask,对应的是CompensationWork类,该类的实现如代码清单11-39所示。
代码清单11-39:
codes\11\11.7\compensation-times\src\org\crazyit\activiti\CompensationWork.java
public class CompensationWork implements JavaDelegate {
int i = 0;
public void execute(DelegateExecution execution) throws Exception {
i++;
System.out.println("处理第 " + i + " 次补偿...");
}
}
CompensationWork类同样会打印执行次数,代码清单11-37中的②定义了一个“抛出错误”的ServiceTask,对应的类为ThrowError,该类无论怎样,均会抛出BpmnError,依附在这个ServiceTask的错误边界事件会被触发,然后流程会转向补偿边界事件(代码清单11-37中的③)并触发补偿。编写代码加载流程文件,启动流程,详细代码请见codes\11\11.7\compensation-times\src\org\crazyit\activiti\CompensationTimes.java,仅仅加载流程和启动流程,并没有其他特殊操作,运行CompensationTimes.java后,可以看到输出结果如下:
处理第 1 次正常工作...
处理第 2 次正常工作...
处理第 3 次正常工作...
抛出错误,触发补偿
处理第 1 次补偿...
处理第 2 次补偿...
处理第 3 次补偿...
根据结果可知,“正常工作”对应的JavaDelegate执行了3次,当补偿中间事件被触发时,“补偿工作”对应的JavaDelegate同样执行了3次,因此可以得出结论:补偿的执行次数与对应的流程活动的执行次数相等。
当流程的补偿触发时,各个补偿处理者会按照倒序进行补偿,最先完成的流程活动,其补偿处理者会最后执行,即使在并行的流程中,先完成的流程活动,其补偿处理者也会最后执行。图11-23为没有流程分支且需要补偿的流程。
图11-17 无流程分支的补偿
如图11-17所示,该流程执行完前面的两个ServiceTask后,会触发补偿中间事事件,第一个ServiceTask对应的补偿处理者为“CompansationA”,第二个ServiceTask对应的补偿处理者为“CompansationB”,当补偿中间事件触发时,会先执行“CompansationB”,再执行“CompansationA”。图11-17对应的流程文件内容如代码清单11-40所示。
代码清单11-40:
codes\11\11.7\compensation-sequence\resource\bpmn\CompensationSequence.bpmn
attachedToRef="servicetask1">
attachedToRef="servicetask2">
name="SignalThrowEvent">
activiti:class="org.crazyit.activiti.CompensationA"
isForCompensation="true">
activiti:class="org.crazyit.activiti.CompensationB"
isForCompensation="true">
targetRef="servicetask4" associationDirection="None">
targetRef="servicetask3" associationDirection="None">
...省略顺序流配置
代码清单11-40中各个ServiceTask对应的JavaDelegate类,均只打印一句话,表示运行到该类,编写代码加载该流程,本例中对应的运行类为codes\11\11.7\compensation-sequence\src\org\crazyit\activiti\CompensationSequence.java,运行该类,可以看到输出结果如下:
A处理类处理任务...
B处理类处理任务...
B补偿类处理任务...
A补偿类处理任务...
对于一些并行的流程,并且在每一个流程分支中均有需要补偿的流程活动,那么相应的补偿处理者的执行顺序与正常流程一致,先完成的活动,补偿会最后执行,即使这些并行的活动是异步的。图11-18为含有流程分支并且需要进行补偿的流程,代码清单11-41为对应的流程文件内容。
图11-18 并行且发生补偿的流程
代码清单11-41:
codes\11\11.7\compensation-sequence\resource\bpmn\CompensationSequence2.bpmn
activiti:class="org.crazyit.activiti.HandlerB">
attachedToRef="servicetask2">
activiti:class="org.crazyit.activiti. HandlerA">
attachedToRef="servicetask1">
activiti:class="org.crazyit.activiti.CompensationA"
isForCompensation="true">
activiti:class="org.crazyit.activiti. CompensationB"
isForCompensation="true">
name="SignalThrowEvent">
targetRef="servicetask4" associationDirection="None">
targetRef="servicetask3" associationDirection="None">
...省略顺序流配置
编写运行代码加载该流程文件并启动流程,本例对应的Java类如下:codes\11\11.7\compensation-sequence\src\org\crazyit\activiti\CompensationSequence2.java。运行效果如下:
A处理类处理任务...
B处理类处理任务...
B补偿类处理任务...
A补偿类处理任务...
在流程中设置参数,可以分为两类,一类是设置了只对当前执行流有效的参数(setVariableLocal),另外一类是设置了对整个流程有效的参数(setVariable)。那么在补偿触发时,如果在执行的流程活动中设置了参数,需要在补偿处理者中获取参数,其中就涉及这些参数的作用域问题。如果在流程活动中设置了全局的参数,那么在补偿处理者中肯定可以获取,如果在流程活动中只设置了对当前执行流有效的参数(setVariableLocal),那么在补偿处理者同样可以获取该参数。同样地,如果对一个子流程进行补偿,那么在补偿处理者中,可以获取setVariableLocal设置的参数,根据前面章节可以得知,有补偿边界事件的子执行流完成后,其相关数据仍然会保存在数据库中,这些数据包括执行流数据、流程参数和事件描述等。图11-19为一个含有补偿中间事件的普通流程。
图11-19 含有补偿中间事件的普通流程
在本例中,可以在流程活动(ServiceA的TaskService)中设置参数,然后在补偿处理者中获取参数。ServiceA对应的JavaDelegate和补偿处理者(CompensationA)对应的JavaDelegate如代码清单11-42所示。
代码清单11-42:
codes\11\11.7\compensastion-vars\src\org\crazyit\activiti\CompensationA.java
codes\11\11.7\compensastion-vars\src\org\crazyit\activiti\ServiceA.java
public class ServiceA implements JavaDelegate {
public void execute(DelegateExecution execution) throws Exception {
execution.setVariableLocal("user1", "angus");
System.out.println("处理类A执行...");
}
}
public class CompensationA implements JavaDelegate {
public void execute(DelegateExecution execution) throws Exception {
System.out.println("补偿A获取参数 " + execution.getVariable("user1"));
System.out.println("补偿A处理...");
}
}
代码清单11-42中的ServiceA是流程活动的JavaDelegate,而CompensationA则是补偿处理者,图11-19对应的流程文件为codes\11\11.7\compensastion-vars\resource\bpmn\Variable1.bpmn,在此不贴出该文件内容,编写代码加载流程文件并启动流程,可以看到输出结果如下:
处理类A执行...
补偿A获取参数 angus
补偿A处理...
根据结果可以看出,在流程活动中使用执行流对象的setVariableLocal方法设置的参数,在补偿处理类中,可以使用执行流对象的getVariable方法获取。同样地,在子流程中,补偿处理者也可以使用同样的方式获取流程活动中使用setVaribleLocal方法设置的参数。
京东购买地址:https://item.jd.com/12246565.html
工作流Activiti6电子书:http://blog.csdn.net/boxiong86/article/details/78488562
工作流Activiti6教学视频:http://blog.csdn.net/boxiong86/article/details/78608585
本书代码共享地址:https://gitee.com/yangenxiong/CrazyActiviti