在工作流中会有遇到这样一个"多个人处理同一个任务“的情形,在 camunda 中可以使用"任务的多实例"来实现。
这里以或签为例,可以设置完成条件为 ${nrOfCompletedInstances==1}
,如果是会签,设置成 ${nrOfCompletedInstances==n}
即可
bpmn核心配置为
<bpmn:userTask id="Activity_0aa" name="或签" camunda:assignee="${assignee}">
<bpmn:incoming>Flow_10mm1vybpmn:incoming>
<bpmn:outgoing>Flow_1e19xopbpmn:outgoing>
<bpmn:multiInstanceLoopCharacteristics camunda:collection="assigneeList" camunda:elementVariable="assignee">
<bpmn:completionCondition xsi:type="bpmn:tFormalExpression">${nrOfCompletedInstances==1}bpmn:completionCondition>
bpmn:multiInstanceLoopCharacteristics>
bpmn:userTask>
源码环境:camunda-bpm-spring-boot-starter-7.17.0.jar
打开 spring.factories
,可以看到自动注类为 org.camunda.bpm.spring.boot.starter.CamundaBpmAutoConfiguration
进入该类,可以看到默认的处理引擎类为 org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl
@Autowired
protected ProcessEngineConfigurationImpl processEngineConfigurationImpl;
@Bean
public ProcessEngineFactoryBean processEngineFactoryBean() {
final ProcessEngineFactoryBean factoryBean = new ProcessEngineFactoryBean();
factoryBean.setProcessEngineConfiguration(processEngineConfigurationImpl);
return factoryBean;
}
ProcessEngineFactoryBean
为 FactoryBean
,核心方法为 getObject
public ProcessEngine getObject() throws Exception {
if (processEngine == null) {
initializeExpressionManager();
initializeTransactionExternallyManaged();
processEngine = (ProcessEngineImpl) processEngineConfiguration.buildProcessEngine();
}
return processEngine;
}
最终执行 org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl
的 buildProcessEngine()
方法
@Override
public ProcessEngine buildProcessEngine() {
// 初始化各个组件
init();
processEngine = new ProcessEngineImpl(this);
invokePostProcessEngineBuild(processEngine);
return processEngine;
}
最终会走到 getBpmnDeployer()
方法,梳理一下前面的调用链
CamundaBpmAutoConfiguration#processEngineFactoryBean()
ProcessEngineFactoryBean#getObject()
ProcessEngineConfigurationImpl#buildProcessEngine()
ProcessEngineConfigurationImpl#init()
ProcessEngineConfigurationImpl#initDeployers()
ProcessEngineConfigurationImpl#getDefaultDeployers()
ProcessEngineConfigurationImpl#getBpmnDeployer()
进而获取解析核心工厂类 DefaultBpmnParseFactory
if (bpmnParseFactory == null) {
bpmnParseFactory = new DefaultBpmnParseFactory();
}
该工厂类中指定了获取 BpmnParse
的方法
public BpmnParse createBpmnParse(BpmnParser bpmnParser) {
return new BpmnParse(bpmnParser);
}
BpmnParse
是解析流程图的核心类
部署一下该流程
@Test
public void testDeploy() {
Deployment deploy = repositoryService
.createDeployment()
.name("多实例案例")
.tenantId("zyx")
.addClasspathResource("bpmn/midemo.bpmn")
.deploy();
}
然后发起流程
@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
方法
核心代码如下,可以与前面的配置文件搭配查看
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
@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
的大小
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
填充属性,这一步会通过索引及列表填充审批人的参数
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);
}
}
通过上面的源码阅读发现,指定审批人参数的核心方法有两个:
MultiInstanceActivityBehavior#nrOfInstances
:获取审批人个数MultiInstanceActivityBehavior#evaluateCollectionVariable
:计算审批人属性其实最最核心的还是上面一个方法,因为它先执行,我们甚至可以直接在里面指定审批人列表 ,而且它决定了审批人的个数,会影响后面人员的获取
通过上面的分析,我们可以看到,要实现自定义获取审批人列表,可以通过实现下面两个方法中的一个:
MultiInstanceActivityBehavior#nrOfInstances
:直接修改审批人列表参数MultiInstanceActivityBehavior#evaluateCollectionVariable
:计算审批人属性时按照自定义方法获取修改默认方法可以参考下面的链接:
camunda spirngboot配置
主要是需要添加一个 ProcessEnginePlugin
,里面可以设置 BpmnParseFactory
MyCamundaConfiguration
@Configuration
public class MyCamundaConfiguration {
@Bean
@Order(Ordering.DEFAULT_ORDER + 1)
public static ProcessEnginePlugin myCustomConfiguration() {
return new MyCustomConfiguration();
}
}
MyCustomConfiguration
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
public class CustomBpmnParseFactory extends DefaultBpmnParseFactory {
@Override
public BpmnParse createBpmnParse(BpmnParser bpmnParser) {
return new CustomeBpmnParse(bpmnParser);
}
}
CustomeBpmnParse
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
修改获取审批人方法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
:与上面类似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
实现的 ,但是可以参考其策略模式指定审批人的实现。