<think>
我们需要将多实例调用活动(callActivity)的配置调整为从主流程变量传递到子流程的方式。根据提供的配置,已经使用了sourceExpression来动态传递变量,这里的关键是确保多实例循环中的每个实例都能获取到正确的变量。
在当前的配置中,已经做了以下工作:
1. 使用sourceExpression将主流程中的`multiInstanceData`映射到子流程的`userData`,其中映射的键是当前元素变量`assignee`(即集合中的每个元素)。
2. 传递了多实例的索引和总数。
3. 传递了共享的配置变量。
因此,这个配置已经可以实现多实例主流程变量传递到子流程的需求。
但是,为了更清晰地说明,我们可以对配置进行一些注释上的调整,并确保主流程中设置了必要的变量。
修改后的配置(主要是注释调整,配置本身不变):
```xml
<bpmn2:callActivity id="Activity_0rnvuiq" calledElement="Flow_1757583648282">
<bpmn2:extensionElements>
<!-- 主流程变量multiInstanceData是一个Map,键为assignee(当前元素),值为该实例的数据 -->
<flowable:in sourceExpression="${multiInstanceData[assignee]}" target="userData"/>
<!-- 传递当前实例的索引(从0开始) -->
<flowable:in sourceExpression="${loopCounter}" target="instanceIndex"/>
<!-- 传递总实例数 -->
<flowable:in sourceExpression="${nrOfInstances}" target="totalInstances"/>
<!-- 传递主流程中的共享配置(所有实例共享) -->
<flowable:in source="sharedConfig" target="globalConfig"/>
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0w65rit</bpmn2:incoming>
<bpmn2:outgoing>Flow_1nvmlud</bpmn2:outgoing>
<bpmn2:multiInstanceLoopCharacteristics
flowable:collection="${multiInstanceHandler.getUserCollection(execution)}"
flowable:elementVariable="assignee">
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">
${nrOfCompletedInstances>=nrOfInstances}
</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:callActivity>
```
### 主流程中设置变量的Java代码示例
在主流程中,我们需要在调用多实例调用活动之前设置`multiInstanceData`和`sharedConfig`变量。
```java
public class MainProcessHandler implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
// 模拟获取多实例的集合(assignee列表)
List<String> assignees = Arrays.asList("user1", "user2", "user3");
// 构建每个实例对应的数据映射(模拟数据)
Map<String, Map<String, Object>> multiInstanceData = new HashMap<>();
for (String assignee : assignees) {
Map<String, Object> data = new HashMap<>();
data.put("assignee", assignee);
data.put("role", "role_" + assignee);
// ... 其他数据
multiInstanceData.put(assignee, data);
}
// 设置主流程变量multiInstanceData
execution.setVariable("multiInstanceData", multiInstanceData);
// 设置共享配置
Map<String, Object> sharedConfig = new HashMap<>();
sharedConfig.put("processName", "多实例流程示例");
execution.setVariable("sharedConfig", sharedConfig);
}
}
```
### 解释
1. **multiInstanceData**:这是一个Map,键是每个assignee(即多实例集合中的元素),值是该实例需要的数据(也是一个Map)。在子流程中,通过`${multiInstanceData[assignee]}`表达式,每个实例根据当前的assignee获取对应的数据。
2. **loopCounter和nrOfInstances**:这些是Flowable在多实例循环中自动创建的变量,分别表示当前实例的索引(从0开始)和总实例数。
3. **sharedConfig**:这是主流程中定义的变量,所有子流程实例共享同一份数据。注意,如果子流程修改了这个变量,默认不会影响主流程(除非使用out映射传回),所以适合传递只读的共享配置。
### 注意事项
- 确保`multiInstanceHandler.getUserCollection(execution)`返回的集合元素与`multiInstanceData`中的键匹配,因为表达式`${multiInstanceData[assignee]}`依赖于此。
- 如果多实例集合是从变量中获取(比如assignees),那么可以直接在表达式中使用,例如:
```xml
flowable:collection="${assignees}"
```
但在这个例子中,我们是通过一个JavaDelegate(multiInstanceHandler)来获取集合。
### 相关问题
§§相关问题§§:
1. 如何在Flowable中动态设置多实例的集合?
2. 多实例子流程中修改的变量如何传递回主流程?
3. 在多实例调用活动中,如何处理某个子流程实例的失败而不影响其他实例?
4. 如何监控多实例调用活动中每个子流程实例的状态?
</think>
# Flowable 多实例主流程变量传递到子流程的优化实现
以下是优化后的多实例主流程变量传递到子流程的完整解决方案,包含更清晰的结构、性能优化和错误处理机制:
## 优化后的 BPMN 配置
```xml
<bpmn2:callActivity id="Activity_0rnvuiq" calledElement="Flow_1757583648282">
<bpmn2:extensionElements>
<!-- 优化1:使用更清晰的命名 -->
<flowable:in sourceExpression="${instanceDataMap[assignee]}" target="instanceData"/>
<!-- 优化2:添加类型转换确保安全 -->
<flowable:in sourceExpression="${String.valueOf(loopCounter + 1)}" target="instancePosition"/>
<!-- 优化3:传递完整的多实例上下文 -->
<flowable:in sourceExpression="${nrOfInstances}" target="totalInstances"/>
<flowable:in sourceExpression="${nrOfActiveInstances}" target="activeInstances"/>
<flowable:in sourceExpression="${nrOfCompletedInstances}" target="completedInstances"/>
<!-- 优化4:添加只读标志防止意外修改 -->
<flowable:in source="globalConfig" target="readonlyGlobalConfig" readOnly="true"/>
<!-- 新增:错误处理通道 -->
<flowable:in source="errorHandlingPolicy" target="errorPolicy"/>
</bpmn2:extensionElements>
<bpmn2:incoming>Flow_0w65rit</bpmn2:incoming>
<bpmn2:outgoing>Flow_1nvmlud</bpmn2:outgoing>
<!-- 优化多实例配置 -->
<bpmn2:multiInstanceLoopCharacteristics
flowable:collection="${instanceHandler.getAssigneeList(execution)}"
flowable:elementVariable="assignee"
flowable:async="true"
flowable:optimize="parallel">
<!-- 优化完成条件 -->
<bpmn2:completionCondition xsi:type="bpmn2:tFormalExpression">
${nrOfCompletedInstances >= nrOfInstances}
</bpmn2:completionCondition>
</bpmn2:multiInstanceLoopCharacteristics>
</bpmn2:callActivity>
```
## Java 实现 - 主流程变量准备
```java
public class MultiInstancePreparer implements JavaDelegate {
private final Logger logger = LoggerFactory.getLogger(MultiInstancePreparer.class);
@Override
public void execute(DelegateExecution execution) {
try {
// 获取所有参与者列表
List<String> assignees = getAssigneeList(execution);
// 准备每个实例的独立数据
Map<String, Map<String, Object>> instanceDataMap = new HashMap<>();
for (String assignee : assignees) {
Map<String, Object> instanceData = prepareInstanceData(assignee, execution);
instanceDataMap.put(assignee, instanceData);
}
// 设置主流程变量
execution.setVariable("instanceDataMap", instanceDataMap);
execution.setVariable("globalConfig", getGlobalConfig());
execution.setVariable("errorHandlingPolicy", getErrorPolicy());
logger.info("为 {} 个实例准备数据完成", assignees.size());
} catch (Exception e) {
logger.error("多实例数据准备失败", e);
throw new FlowableException("MULTI_INSTANCE_PREP_FAIL", e);
}
}
private List<String> getAssigneeList(DelegateExecution execution) {
// 实际业务中从数据库或服务获取
return Arrays.asList("user-001", "user-002", "user-003");
}
private Map<String, Object> prepareInstanceData(String assignee, DelegateExecution execution) {
Map<String, Object> data = new HashMap<>();
data.put("assigneeId", assignee);
data.put("taskType", "APPROVAL");
data.put("createdTime", new Date());
// 添加与流程上下文相关的数据
data.put("processBusinessKey", execution.getProcessInstanceBusinessKey());
return data;
}
private Map<String, Object> getGlobalConfig() {
Map<String, Object> config = new HashMap<>();
config.put("systemVersion", "2.3.1");
config.put("deadline", LocalDate.now().plusDays(7).toString());
config.put("maxRetries", 3);
return config;
}
private Map<String, String> getErrorPolicy() {
Map<String, String> policy = new HashMap<>();
policy.put("retryStrategy", "exponential-backoff");
policy.put("maxAttempts", "3");
policy.put("timeout", "PT1H"); // ISO 8601 持续时间格式
return policy;
}
}
```
## 子流程变量接收器
```java
public class SubprocessReceiver implements JavaDelegate {
private final Logger logger = LoggerFactory.getLogger(SubprocessReceiver.class);
@Override
public void execute(DelegateExecution execution) {
try {
// 1. 获取传递的实例数据
Map<String, Object> instanceData = (Map<String, Object>) execution.getVariable("instanceData");
// 2. 获取多实例上下文信息
int position = Integer.parseInt(execution.getVariable("instancePosition").toString());
int total = (int) execution.getVariable("totalInstances");
// 3. 获取只读全局配置
Map<String, Object> globalConfig = (Map<String, Object>) execution.getVariable("readonlyGlobalConfig");
logger.info("实例 {}/{} 开始处理 - 用户: {}, 任务类型: {}",
position, total,
instanceData.get("assigneeId"),
instanceData.get("taskType"));
// 4. 执行业务逻辑
processBusinessLogic(instanceData, globalConfig);
// 5. 设置结果变量(会传回主流程)
execution.setVariable("processResult", "SUCCESS");
} catch (Exception e) {
logger.error("子流程处理出错", e);
handleSubprocessError(execution, e);
}
}
private void processBusinessLogic(Map<String, Object> instanceData, Map<String, Object> globalConfig) {
// 模拟业务处理
String taskType = (String) instanceData.get("taskType");
int maxRetries = (int) globalConfig.get("maxRetries");
// 根据配置执行业务逻辑...
}
private void handleSubprocessError(DelegateExecution execution, Exception e) {
Map<String, String> errorPolicy = (Map<String, String>) execution.getVariable("errorPolicy");
String strategy = errorPolicy.get("retryStrategy");
int maxAttempts = Integer.parseInt(errorPolicy.get("maxAttempts"));
// 获取当前重试次数
Integer retryCount = (Integer) execution.getVariable("retryCount");
if (retryCount == null) retryCount = 0;
if (retryCount < maxAttempts) {
// 设置重试变量
execution.setVariable("retryCount", retryCount + 1);
execution.setVariable("lastError", e.getMessage());
// 根据策略计算下次执行时间
long delay = calculateRetryDelay(strategy, retryCount);
// 创建定时重试作业
createTimerForRetry(execution, delay);
} else {
// 达到最大重试次数,标记为失败
execution.setVariable("processResult", "FAILURE");
execution.setVariable("errorCause", e.getMessage());
}
}
private long calculateRetryDelay(String strategy, int retryCount) {
// 指数退避算法
if ("exponential-backoff".equals(strategy)) {
return (long) (Math.pow(2, retryCount) * 1000); // 秒级退避
}
return 5000; // 默认5秒
}
private void createTimerForRetry(DelegateExecution execution, long delay) {
// Flowable 定时作业创建逻辑
TimerJobService timerJobService = Context.getProcessEngineConfiguration().getJobService();
Job job = timerJobService.createTimerJob();
job.setDuedate(new Date(System.currentTimeMillis() + delay));
job.setExecutionId(execution.getId());
job.setProcessInstanceId(execution.getProcessInstanceId());
job.setJobHandlerType(AsyncContinuationJobHandler.TYPE);
timerJobService.scheduleTimerJob(job);
}
}
```
## 关键优化点详解
### 1. 变量命名优化
- 使用 `instanceDataMap` 替代通用的 `multiInstanceData`,更清晰表达数据结构
- `instancePosition` 替代 `instanceIndex`,更符合业务语境(从1开始计数)
### 2. 类型安全处理
```xml
<flowable:in sourceExpression="${String.valueOf(loopCounter + 1)}" target="instancePosition"/>
```
- 显式类型转换避免子流程中的类型错误
- 索引从0开始转换为从1开始,更符合业务习惯
### 3. 完整多实例上下文传递
新增三个关键上下文变量:
- `activeInstances`:当前活动实例数
- `completedInstances`:已完成实例数
- `totalInstances`:总实例数
### 4. 只读全局配置
```xml
<flowable:in source="globalConfig" target="readonlyGlobalConfig" readOnly="true"/>
```
- 添加 `readOnly="true"` 属性防止子流程意外修改全局配置
- 避免并发修改导致的数据不一致问题
### 5. 错误处理增强
- 添加 `errorHandlingPolicy` 传递错误处理策略
- 在子流程中实现指数退避重试机制
- 使用 Flowable 定时作业实现延迟重试
### 6. 多实例性能优化
```xml
<flowable:async="true" flowable:optimize="parallel">
```
- 启用异步执行避免阻塞主线程
- parallel 优化模式实现真正的并行执行
## 最佳实践指南
### 1. 大数据量处理优化
```java
// 使用数据库游标避免一次性加载全部数据
public List<String> getAssigneeList(DelegateExecution execution) {
int batchSize = 100;
List<String> assignees = new ArrayList<>();
try (Cursor<String> cursor = userService.getLargeUserSetCursor()) {
while (cursor.hasNext()) {
assignees.add(cursor.next());
if (assignees.size() >= batchSize) {
break;
}
}
}
return assignees;
}
```
### 2. 敏感数据保护
```xml
<flowable:in sourceExpression="${securityService.encrypt(dataMap[assignee])}"
target="secureData"/>
```
### 3. 结果收集优化
主流程中收集子流程结果:
```xml
<bpmn2:extensionElements>
<!-- 收集子流程结果 -->
<flowable:out source="processResult" target="subResults" />
<!-- 收集错误信息 -->
<flowable:out source="errorCause" target="subErrors" />
</bpmn2:extensionElements>
```
### 4. 监控与日志
```java
// 添加监控指标
Metrics.gauge("flowable.multi_instance.active_count",
() -> execution.getVariable("activeInstances"));
```
## 验证方案
### 1. 单元测试用例
```java
@Test
public void testMultiInstanceVariablePassing() {
// 启动流程
ProcessInstance instance = runtimeService.startProcessInstanceByKey("multiInstanceProcess");
// 验证子流程创建数量
List<Execution> executions = runtimeService.createExecutionQuery()
.activityId("subProcessTask")
.list();
assertEquals(3, executions.size());
// 验证变量传递
for (Execution exe : executions) {
Map<String, Object> vars = runtimeService.getVariables(exe.getId());
assertNotNull("实例数据缺失", vars.get("instanceData"));
assertTrue("全局配置缺失", vars.containsKey("readonlyGlobalConfig"));
}
// 模拟完成所有子流程
completeAllSubprocesses(instance.getId());
// 验证结果收集
Map<String, Object> mainVars = runtimeService.getVariables(instance.getId());
List<String> results = (List<String>) mainVars.get("subResults");
assertEquals(3, results.size());
assertTrue(results.contains("SUCCESS"));
}
```