Camunda自定义多实例审批人列表
1.多实例案例
在工作流中会有遇到这样一个"多个人处理同一个任务"的情形,在 camunda 中可以使用"任务的多实例"来实现。
这里以或签为例,可以设置完成条件为 ${nrOfCompletedInstances==1}
,如果是会签,设置成 ${nrOfCompletedInstances==n}
即可
bpmn核心配置为
xml
<bpmn:userTask id="Activity_0aal2tp" name="或签" camunda:assignee="${assignee}">
<bpmn:incoming>Flow_10mm1vy</bpmn:incoming>
<bpmn:outgoing>Flow_1e19xop</bpmn:outgoing>
<bpmn:multiInstanceLoopCharacteristics camunda:collection="assigneeList" camunda:elementVariable="assignee">
<bpmn:completionCondition xsi:type="bpmn:tFormalExpression">${nrOfCompletedInstances==1}</bpmn:completionCondition>
</bpmn:multiInstanceLoopCharacteristics>
</bpmn:userTask>
2.源码走读
源码环境:camunda-bpm-spring-boot-starter-7.17.0.jar
2.1 启动类
打开 spring.factories
,可以看到自动注类为 org.camunda.bpm.spring.boot.starter.CamundaBpmAutoConfiguration
进入该类,可以看到默认的处理引擎类为 org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl
java
@Autowired
protected ProcessEngineConfigurationImpl processEngineConfigurationImpl;
@Bean
public ProcessEngineFactoryBean processEngineFactoryBean() {
final ProcessEngineFactoryBean factoryBean = new ProcessEngineFactoryBean();
factoryBean.setProcessEngineConfiguration(processEngineConfigurationImpl);
return factoryBean;
}
ProcessEngineFactoryBean
为 FactoryBean
,核心方法为 getObject
java
public ProcessEngine getObject() throws Exception {
if (processEngine == null) {
initializeExpressionManager();
initializeTransactionExternallyManaged();
processEngine = (ProcessEngineImpl) processEngineConfiguration.buildProcessEngine();
}
return processEngine;
}
最终执行 org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl
的 buildProcessEngine()
方法
java
@Override
public ProcessEngine buildProcessEngine() {
// 初始化各个组件
init();
processEngine = new ProcessEngineImpl(this);
invokePostProcessEngineBuild(processEngine);
return processEngine;
}
最终会走到 getBpmnDeployer()
方法,梳理一下前面的调用链
java
CamundaBpmAutoConfiguration#processEngineFactoryBean()
ProcessEngineFactoryBean#getObject()
ProcessEngineConfigurationImpl#buildProcessEngine()
ProcessEngineConfigurationImpl#init()
ProcessEngineConfigurationImpl#initDeployers()
ProcessEngineConfigurationImpl#getDefaultDeployers()
ProcessEngineConfigurationImpl#getBpmnDeployer()
进而获取解析核心工厂类 DefaultBpmnParseFactory
java
if (bpmnParseFactory == null) {
bpmnParseFactory = new DefaultBpmnParseFactory();
}
该工厂类中指定了获取 BpmnParse
的方法
java
public BpmnParse createBpmnParse(BpmnParser bpmnParser) {
return new BpmnParse(bpmnParser);
}
BpmnParse
是解析流程图的核心类
2.2 获取审批人的方法
部署一下该流程
java
@Test
public void testDeploy() {
Deployment deploy = repositoryService
.createDeployment()
.name("多实例案例")
.tenantId("zyx")
.addClasspathResource("bpmn/midemo.bpmn")
.deploy();
}
然后发起流程
java
@ParameterizedTest
@ValueSource(strings = {"1704859505474060288"})
public void testStartMultiInstance(String deployId) {
ProcessDefinition processDef = repositoryService
.createProcessDefinitionQuery()
.deploymentId(deployId)
.tenantIdIn("zyx")
.singleResult();
VariableMap variableMap = Variables.createVariables()
.putValue("assigneeList", Arrays.asList("201", "202", "203"));
ProcessInstance processInstance = runtimeService
.startProcessInstanceById(processDef.getId(), IdUtil.getSnowflakeNextIdStr(), variableMap);
// log.info("==========>>>>>>>>>> instance id: {}", processInstance.getId());
}
重点关注 BpmnParse#parseActivity(Element activityElement, Element parentElement, ScopeImpl scopeElement)
关注 userTask
事件,debug进入 parseMultiInstanceLoopCharacteristics
方法
核心代码如下,可以与前面的配置文件搭配查看
java
public ScopeImpl parseMultiInstanceLoopCharacteristics(Element activityElement, ScopeImpl scope) {
Element miLoopCharacteristics = activityElement.element("multiInstanceLoopCharacteristics");
if (miLoopCharacteristics == null) {
return null;
} else {
String id = activityElement.attribute("id");
LOG.parsingElement("mi body for activity", id);
id = getIdForMiBody(id);
ActivityImpl miBodyScope = scope.createActivity(id);
setActivityAsyncDelegates(miBodyScope);
miBodyScope.setProperty(PROPERTYNAME_TYPE, ActivityTypes.MULTI_INSTANCE_BODY);
miBodyScope.setScope(true);
boolean isSequential = parseBooleanAttribute(miLoopCharacteristics.attribute("isSequential"), false);
MultiInstanceActivityBehavior behavior = null;
if (isSequential) {
behavior = new SequentialMultiInstanceActivityBehavior();
} else {
behavior = new ParallelMultiInstanceActivityBehavior();
}
miBodyScope.setActivityBehavior(behavior);
// loopCardinality
Element loopCardinality = miLoopCharacteristics.element("loopCardinality");
if (loopCardinality != null) {
String loopCardinalityText = loopCardinality.getText();
if (loopCardinalityText == null || "".equals(loopCardinalityText)) {
addError("loopCardinality must be defined for a multiInstanceLoopCharacteristics definition ", miLoopCharacteristics, id);
}
behavior.setLoopCardinalityExpression(expressionManager.createExpression(loopCardinalityText));
}
// 指定终止条件
Element completionCondition = miLoopCharacteristics.element("completionCondition");
if (completionCondition != null) {
String completionConditionText = completionCondition.getText();
behavior.setCompletionConditionExpression(expressionManager.createExpression(completionConditionText));
}
// 获取 collection 的名称
String collection = miLoopCharacteristics.attributeNS(CAMUNDA_BPMN_EXTENSIONS_NS, "collection");
if (collection != null) {
if (collection.contains("{")) {
behavior.setCollectionExpression(expressionManager.createExpression(collection));
} else {
behavior.setCollectionVariable(collection);
}
}
// ............
return miBodyScope;
}
}
可以看到已经获取了 bpmn 中多实例相关参数
多实例的执行位于 MultiInstanceActivityBehavior#execute
java
@Override
public void execute(ActivityExecution execution) throws Exception {
int nrOfInstances = resolveNrOfInstances(execution);
if (nrOfInstances == 0) {
leave(execution);
}
else if (nrOfInstances < 0) {
throw LOG.invalidAmountException("instances", nrOfInstances);
}
else {
createInstances(execution, nrOfInstances);
}
}
跟进到 resolveNrOfInstances
方法,可以看到 nrOfInstances
本质还是获取参数中的 list
的大小
java
protected int resolveNrOfInstances(ActivityExecution execution) {
int nrOfInstances = -1;
if (loopCardinalityExpression != null) {
nrOfInstances = resolveLoopCardinality(execution);
} else if (collectionExpression != null) {
Object obj = collectionExpression.getValue(execution);
if (!(obj instanceof Collection)) {
throw LOG.unresolvableExpressionException(collectionExpression.getExpressionText(), "Collection");
}
nrOfInstances = ((Collection<?>) obj).size();
} else if (collectionVariable != null) {
Object obj = execution.getVariable(collectionVariable);
if (!(obj instanceof Collection)) {
throw LOG.invalidVariableTypeException(collectionVariable, "Collection");
}
nrOfInstances = ((Collection<?>) obj).size();
} else {
throw LOG.resolveCollectionExpressionOrVariableReferenceException();
}
return nrOfInstances;
}
然后 debug 到 MultiInstanceActivityBehavior#createInstances
可以看到是一个抽象方法
执行类为 ParallelMultiInstanceActivityBehavior
,这个在前面看到过,是在 BpmnParse#parseMultiInstanceLoopCharacteristics
方法中定义的
ParallelMultiInstanceActivityBehaviorcreateConcurrentExecution(execution)
创建 execution
并添加到 concurrentExecutions
中
然后从 concurrentExecutions
中逐一获取,通过 MultiInstanceActivityBehavior#performInstance
填充属性,这一步会通过索引及列表填充审批人的参数
java
protected void performInstance(ActivityExecution execution, PvmActivity activity, int loopCounter) {
setLoopVariable(execution, LOOP_COUNTER, loopCounter);
// 填充列表参数
evaluateCollectionVariable(execution, loopCounter);
execution.setEnded(false);
execution.setActive(true);
execution.executeActivity(activity);
}
protected void evaluateCollectionVariable(ActivityExecution execution, int loopCounter) {
if (usesCollection() && collectionElementVariable != null) {
Collection<?> collection = null;
if (collectionExpression != null) {
collection = (Collection<?>) collectionExpression.getValue(execution);
} else if (collectionVariable != null) {
collection = (Collection<?>) execution.getVariable(collectionVariable);
}
// 通过列表及索引获取实际参数
Object value = getElementAtIndex(loopCounter, collection);
setLoopVariable(execution, collectionElementVariable, value);
}
}
2.3 小结
通过上面的源码阅读发现,指定审批人参数的核心方法有两个:
MultiInstanceActivityBehavior#nrOfInstances
:获取审批人个数MultiInstanceActivityBehavior#evaluateCollectionVariable
:计算审批人属性
其实最最核心的还是上面一个方法,因为它先执行,我们甚至可以直接在里面指定审批人列表 ,而且它决定了审批人的个数,会影响后面人员的获取
3.自定义审批人实现
通过上面的分析,我们可以看到,要实现自定义获取审批人列表,可以通过实现下面两个方法中的一个:
MultiInstanceActivityBehavior#nrOfInstances
:直接修改审批人列表参数MultiInstanceActivityBehavior#evaluateCollectionVariable
:计算审批人属性时按照自定义方法获取
修改默认方法可以参考下面的链接:
主要是需要添加一个 ProcessEnginePlugin
,里面可以设置 BpmnParseFactory
MyCamundaConfiguration
java
@Configuration
public class MyCamundaConfiguration {
@Bean
@Order(Ordering.DEFAULT_ORDER + 1)
public static ProcessEnginePlugin myCustomConfiguration() {
return new MyCustomConfiguration();
}
}
MyCustomConfiguration
java
public class MyCustomConfiguration implements ProcessEnginePlugin {
@Override
public void preInit(ProcessEngineConfigurationImpl processEngineConfiguration) {
// 自定义 BpmnParse 工厂
processEngineConfiguration.setBpmnParseFactory(new CustomBpmnParseFactory());
}
@Override
public void postInit(ProcessEngineConfigurationImpl processEngineConfiguration) {
}
@Override
public void postProcessEngineBuild(ProcessEngine processEngine) {
}
}
CustomBpmnParseFactory
java
public class CustomBpmnParseFactory extends DefaultBpmnParseFactory {
@Override
public BpmnParse createBpmnParse(BpmnParser bpmnParser) {
return new CustomeBpmnParse(bpmnParser);
}
}
CustomeBpmnParse
java
public class CustomeBpmnParse extends BpmnParse {
public CustomeBpmnParse(BpmnParser parser) {
super(parser);
}
/**
* 自定义解析多实例流程
*/
@Override
public ScopeImpl parseMultiInstanceLoopCharacteristics(Element activityElement, ScopeImpl scope) {
Element miLoopCharacteristics = activityElement.element("multiInstanceLoopCharacteristics");
if (miLoopCharacteristics == null) {
return null;
} else {
String id = activityElement.attribute("id");
LOG.parsingElement("mi body for activity", id);
id = getIdForMiBody(id);
ActivityImpl miBodyScope = scope.createActivity(id);
setActivityAsyncDelegates(miBodyScope);
miBodyScope.setProperty(BpmnProperties.TYPE.getName(), ActivityTypes.MULTI_INSTANCE_BODY);
miBodyScope.setScope(true);
boolean isSequential = parseBooleanAttribute(miLoopCharacteristics.attribute("isSequential"), false);
MultiInstanceActivityBehavior behavior;
// 自定义解析 多实例流程的 behavior
if (isSequential) {
behavior = new CustomSequentialMultiInstanceActivityBehavior();
} else {
behavior = new CustomParallelMultiInstanceActivityBehavior();
}
miBodyScope.setActivityBehavior(behavior);
// loopCardinality
Element loopCardinality = miLoopCharacteristics.element("loopCardinality");
if (loopCardinality != null) {
String loopCardinalityText = loopCardinality.getText();
if (loopCardinalityText == null || "".equals(loopCardinalityText)) {
addError("loopCardinality must be defined for a multiInstanceLoopCharacteristics definition ", miLoopCharacteristics, id);
}
behavior.setLoopCardinalityExpression(expressionManager.createExpression(loopCardinalityText));
}
// completionCondition
Element completionCondition = miLoopCharacteristics.element("completionCondition");
if (completionCondition != null) {
String completionConditionText = completionCondition.getText();
behavior.setCompletionConditionExpression(expressionManager.createExpression(completionConditionText));
}
// activiti:collection
String collection = miLoopCharacteristics.attributeNS(CAMUNDA_BPMN_EXTENSIONS_NS, "collection");
if (collection != null) {
if (collection.contains("{")) {
behavior.setCollectionExpression(expressionManager.createExpression(collection));
} else {
behavior.setCollectionVariable(collection);
}
}
// loopDataInputRef
Element loopDataInputRef = miLoopCharacteristics.element("loopDataInputRef");
if (loopDataInputRef != null) {
String loopDataInputRefText = loopDataInputRef.getText();
if (loopDataInputRefText != null) {
if (loopDataInputRefText.contains("{")) {
behavior.setCollectionExpression(expressionManager.createExpression(loopDataInputRefText));
} else {
behavior.setCollectionVariable(loopDataInputRefText);
}
}
}
// activiti:elementVariable
String elementVariable = miLoopCharacteristics.attributeNS(CAMUNDA_BPMN_EXTENSIONS_NS, "elementVariable");
if (elementVariable != null) {
behavior.setCollectionElementVariable(elementVariable);
}
// dataInputItem
Element inputDataItem = miLoopCharacteristics.element("inputDataItem");
if (inputDataItem != null) {
String inputDataItemName = inputDataItem.attribute("name");
behavior.setCollectionElementVariable(inputDataItemName);
}
// Validation
if (behavior.getLoopCardinalityExpression() == null && behavior.getCollectionExpression() == null && behavior.getCollectionVariable() == null) {
addError("Either loopCardinality or loopDataInputRef/activiti:collection must been set", miLoopCharacteristics, id);
}
// Validation
if (behavior.getCollectionExpression() == null && behavior.getCollectionVariable() == null && behavior.getCollectionElementVariable() != null) {
addError("LoopDataInputRef/activiti:collection must be set when using inputDataItem or activiti:elementVariable", miLoopCharacteristics, id);
}
for (BpmnParseListener parseListener : parseListeners) {
parseListener.parseMultiInstanceLoopCharacteristics(activityElement, miLoopCharacteristics, miBodyScope);
}
return miBodyScope;
}
}
}
CustomSequentialMultiInstanceActivityBehavior
:通过resolveNrOfInstances
修改获取审批人方法
java
public class CustomSequentialMultiInstanceActivityBehavior extends SequentialMultiInstanceActivityBehavior {
@Override
protected int resolveNrOfInstances(ActivityExecution execution) {
// collectionExpression 和 collectionVariable 是互斥的
super.collectionExpression = null;
// 这里可以自定义获取 审批人列表的 逻辑
List<String> list = Arrays.asList("300", "301", "302");
execution.setVariable(super.collectionVariable, list);
return super.resolveNrOfInstances(execution);
}
}
CustomParallelMultiInstanceActivityBehavior
:与上面类似
java
public class CustomParallelMultiInstanceActivityBehavior extends ParallelMultiInstanceActivityBehavior {
@Override
protected int resolveNrOfInstances(ActivityExecution execution) {
// collectionExpression 和 collectionVariable 是互斥的
super.collectionExpression = null;
// 这里可以自定义获取 审批人列表的 逻辑
List<String> list = Arrays.asList("300", "301", "302");
execution.setVariable(super.collectionVariable, list);
return super.resolveNrOfInstances(execution);
}
}
查看 ACT_RU_TASK
表,可以看到,审批人已经变成了 "300", "301", "302"
关于如何 设计 获取 审批人的策略可以参考开源仓库 ruoyi-vue-pro
的实现,不过该仓库工作流是通过 Flowable
实现的 ,但是可以参考其策略模式指定审批人的实现。