在实现并行会签的时候,遇到了奇怪的问题,记录一下方便遇到同样问题的朋友快速解决问题。
问题起源
首先,官方文档里提到如何使用多实例,以下是官方提供的 sample:
<userTask id="miTasks" name="My Task" activiti:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="false"
activiti:collection="assigneeList" activiti:elementVariable="assignee" >
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
解释一下上面的代码,
assigneeList 为 集合,例如 [“kermit”, “gonzo”, “fozzie”];
activiti:elementVariable=”assignee” 为 接收 loop 中的值的变量名;
activiti:assignee=”${assignee}” 相当于将认领人指定为 loop 中取得的变量对象,就和 java 中 foreach 差不多的意思;
nrOfInstances:实例总数
nrOfActiveInstances:当前活动(即尚未完成)实例的数量。对于顺序多实例,这将始终为 1。
nrOfCompletedInstances:已完成实例的数量。
注意,这三个变量存在当前执行的 父执行 中。
${nrOfCompletedInstances/nrOfInstances >= 0.6},意思不言而喻,就是完成改执行的量大于等于 60% 就可以通过该节点进入下一节点。
整个这段就是完成会签的雏形。如果完成了 60%,我认为绝大部分通过(这里的通过是指完成这个节点任务,和业务上的通过不同。),可以进入下一阶段。
官网就只写到这里。具体可以参考 activiti 用户手册 — 8.5.14。多实例(每个)
然后问题出现了。我参照上列配置,发现 nrOfCompletedInstances 始终为 0。所以完全没有触发停止循环的条件。
通过跟踪代码,发现当循环中的节点被 complete 之后,会经过ParallelMultiInstanceBehavior 这个类的 leave(DelegateExecution execution) 方法
其中一段代码
/**
* Called when the wrapped {@link ActivityBehavior} calls the {@link AbstractBpmnActivityBehavior#leave(ActivityExecution)} method. Handles the completion of one of the parallel instances
*/
public void leave(DelegateExecution execution) {
boolean zeroNrOfInstances = false;
if (resolveNrOfInstances(execution) == 0) {
// Empty collection, just leave.
zeroNrOfInstances = true;
removeLocalLoopVariable(execution, getCollectionElementIndexVariable());
super.leave(execution); // Plan the default leave
execution.setMultiInstanceRoot(false);
}
int loopCounter = getLoopVariable(execution, getCollectionElementIndexVariable());
int nrOfInstances = getLoopVariable(execution, NUMBER_OF_INSTANCES);
int nrOfCompletedInstances = getLoopVariable(execution, NUMBER_OF_COMPLETED_INSTANCES) + 1;
int nrOfActiveInstances = getLoopVariable(execution, NUMBER_OF_ACTIVE_INSTANCES) - 1;
Context.getCommandContext().getHistoryManager().recordActivityEnd((ExecutionEntity) execution, null);
callActivityEndListeners(execution);
if (zeroNrOfInstances) {return;}
DelegateExecution miRootExecution = getMultiInstanceRootExecution(execution);
if (miRootExecution != null) { // will be null in case of empty collection
setLoopVariable(miRootExecution, NUMBER_OF_COMPLETED_INSTANCES, nrOfCompletedInstances);
setLoopVariable(miRootExecution, NUMBER_OF_ACTIVE_INSTANCES, nrOfActiveInstances);
}
.......
}
可以看到这段代码里,nrOfCompletedInstances 是通过 getLoopVariable(execution, NUMBER_OF_COMPLETED_INSTANCES)取得然后 +1 得到的。
那我们猜想,这意思应该就是每次完成后,这个计数器就 +1,然后存会到参数当中去,在下面的代码中 setLoopVariable(miRootExecution, NUMBER_OF_COMPLETED_INSTANCES, nrOfCompletedInstances);
确实也说明他是这样的操作。
这样看设值应该没问题,那一定是取值有问题,然后我们看一下 getLoopVariable 方法
protected Integer getLoopVariable(DelegateExecution execution, String variableName) {Object value = execution.getVariableLocal(variableName);
DelegateExecution parent = execution.getParent();
while (value == null && parent != null) {value = parent.getVariableLocal(variableName);
parent = parent.getParent();}
return (Integer) (value != null ? value : 0);
}
很明显了,先从当前的 execution 中取值,如果没有,那就从父对象中取。
我在代码的任何地方都未曾配置过 nrOfCompletedInstances 这个变量。按理说应该 value 为 null, 但是 debug 过程中发现,value 值为 0!!! 这就直接到导致,每次就取的是当前 execution 的值。而设置的时候,又是将值设置在了父 execution 的变量当中。所以每次的这个变量值就不会改变。都是从 0 ->1 的变化。
解决问题
思路:添加监听器,在执行 leave 方法前,将父 execution 中的变量值,放到当前变量中。
<userTask id="miTasks" name="My Task" activiti:assignee="${assignee}">
<extensionElements>
<activiti:taskListener event="complete" delegateExpression="${counterSignCompleteListener}">
</activiti:taskListener>
<modeler:initiator-can-complete xmlns:modeler="http://activiti.com/modeler"><![CDATA[false]]>
</modeler:initiator-can-complete>
</extensionElements>
<multiInstanceLoopCharacteristics isSequential="false"
activiti:collection="assigneeList" activiti:elementVariable="assignee" >
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6}</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
然后在监听里修改值
@Component
public class CounterSignCompleteListener implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {Integer completedInstances = Integer.valueOf(String.valueOf(delegateTask.getExecution().getParent().getVariable("nrOfCompletedInstances")));
Integer nrOfActiveInstances = Integer.valueOf(String.valueOf(delegateTask.getExecution().getParent().getVariable("nrOfActiveInstances")));
delegateTask.setVariableLocal("nrOfCompletedInstances", completedInstances);
delegateTask.setVariableLocal("nrOfActiveInstances", nrOfActiveInstances);
}
}
这样在调用的时候,变量就能正常自增,就达到想要的结果了。
这次问题我决定不是最好的解决办法,因为很多细节的地方仍旧没有弄明白,我一直认为是我自己的配置出了问题才会有这样的结果,否则官网指导也不会值是给那么简单的 sample 就说可以完成任务了。所以希望有明白的朋友留言给我,我也希望能从根本解决问题。~~ 谢谢大家