别再手动画图了!用Java代码生成Activiti流程图XML的保姆级教程
别再手动画图了!用Java代码生成Activiti流程图XML的保姆级教程
在传统的OA审批、工单流转系统开发中,流程设计往往依赖于Activiti Designer等可视化工具进行拖拽式设计。但当遇到需要根据用户角色、部门架构等业务规则动态生成流程的场景时,这种静态设计方式就显得力不从心。想象一下,当你的系统需要为不同分公司自动生成差异化的报销流程,或者根据产品类型动态调整售后工单流转路径时,手动画图不仅效率低下,更难以实现流程的灵活配置。
这正是代码生成BPMN XML的价值所在。通过 activiti-bpmn-model 和 activiti-bpmn-converter 这两个核心库,我们可以用Java代码直接构建流程模型,并输出符合BPMN 2.0标准的XML文件。这种方式不仅能够实现流程的动态生成,还能将流程设计与业务代码深度集成,真正做到"流程即代码"。
本文将带你从零开始,掌握以下核心技能:
- 理解BPMN XML的结构与代码模型的对应关系
- 使用Java代码构建完整的流程定义(包括任务、网关、连线等)
- 精确控制每个元素的图形化位置信息
- 将内存中的流程模型转换为标准BPMN XML
- 处理实际项目中的复杂流程生成场景
1. 环境准备与基础概念
在开始编码之前,我们需要先搭建好开发环境。创建一个标准的Maven项目,在pom.xml中添加以下依赖:
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-model</artifactId>
<version>7.1.0.M6</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-converter</artifactId>
<version>7.1.0.M6</version>
</dependency>
BPMN模型的核心是 BpmnModel 类,它包含了一个完整流程定义的所有元素。在代码层面,Activiti将流程元素分为两大类:
- 流程元素 :定义流程的逻辑结构,如任务、网关、事件等
- 图形元素 :定义这些流程元素在流程图中的可视化表现
这种分离设计非常巧妙,使得我们可以独立处理流程逻辑和图形布局。例如,你可以先构建完整的流程逻辑,然后再考虑各个元素在画布上的位置关系。
提示:虽然BPMN规范非常庞大,但日常工作中80%的场景只需要掌握约20%的核心元素。我们首先聚焦这些高频使用的元素。
2. 构建基础流程结构
让我们从一个最简单的审批流程开始:提交申请 → 经理审批 → 结束。对应的Java代码如下:
// 创建BPMN模型容器
BpmnModel bpmnModel = new BpmnModel();
// 创建流程定义
Process process = new Process();
process.setId("leaveApproval"); // 流程ID
process.setName("请假审批流程"); // 流程名称
bpmnModel.addProcess(process);
// 创建开始事件
StartEvent startEvent = new StartEvent();
startEvent.setId("startEvent");
process.addFlowElement(startEvent);
// 创建用户任务 - 提交申请
UserTask applyTask = new UserTask();
applyTask.setId("applyTask");
applyTask.setName("提交请假申请");
applyTask.setAssignee("${applicant}"); // 动态分配
process.addFlowElement(applyTask);
// 创建用户任务 - 经理审批
UserTask approveTask = new UserTask();
approveTask.setId("approveTask");
approveTask.setName("经理审批");
approveTask.setCandidateGroups("managers"); // 候选组
process.addFlowElement(approveTask);
// 创建结束事件
EndEvent endEvent = new EndEvent();
endEvent.setId("endEvent");
process.addFlowElement(endEvent);
现在我们已经定义了流程的逻辑结构,但还缺少将这些元素连接起来的顺序流。添加顺序流的代码如下:
// 开始事件 → 提交申请
SequenceFlow flow1 = new SequenceFlow();
flow1.setId("flow1");
flow1.setSourceRef("startEvent");
flow1.setTargetRef("applyTask");
process.addFlowElement(flow1);
// 提交申请 → 经理审批
SequenceFlow flow2 = new SequenceFlow();
flow2.setId("flow2");
flow2.setSourceRef("applyTask");
flow2.setTargetRef("approveTask");
process.addFlowElement(flow2);
// 经理审批 → 结束事件
SequenceFlow flow3 = new SequenceFlow();
flow3.setId("flow3");
flow3.setSourceRef("approveTask");
flow3.setTargetRef("endEvent");
process.addFlowElement(flow3);
至此,我们已经完成了一个完整但"不可见"的流程定义。接下来需要为每个元素添加图形化信息,使其能够被可视化展现。
3. 添加图形布局信息
Activiti使用 GraphicInfo 类来表示元素在画布上的位置和大小。下面我们为之前的流程元素添加布局信息:
// 设置画布大小(可选)
bpmnModel.addGraphicInfo("diagram", new GraphicInfo(0, 0, 1000, 1000));
// 开始事件位置
GraphicInfo startEventGi = new GraphicInfo();
startEventGi.setX(100);
startEventGi.setY(100);
startEventGi.setWidth(30);
startEventGi.setHeight(30);
bpmnModel.addGraphicInfo("startEvent", startEventGi);
// 提交申请任务位置
GraphicInfo applyTaskGi = new GraphicInfo();
applyTaskGi.setX(200);
applyTaskGi.setY(80);
applyTaskGi.setWidth(100);
applyTaskGi.setHeight(80);
bpmnModel.addGraphicInfo("applyTask", applyTaskGi);
// 经理审批任务位置
GraphicInfo approveTaskGi = new GraphicInfo();
approveTaskGi.setX(400);
approveTaskGi.setY(80);
approveTaskGi.setWidth(100);
approveTaskGi.setHeight(80);
bpmnModel.addGraphicInfo("approveTask", approveTaskGi);
// 结束事件位置
GraphicInfo endEventGi = new GraphicInfo();
endEventGi.setX(600);
endEventGi.setY(100);
endEventGi.setWidth(30);
endEventGi.setHeight(30);
bpmnModel.addGraphicInfo("endEvent", endEventGi);
对于顺序流,我们需要定义其路径上的转折点(Waypoints):
// 开始事件 → 提交申请的连线路径
List<GraphicInfo> flow1Waypoints = new ArrayList<>();
flow1Waypoints.add(new GraphicInfo(130, 115, 0, 0)); // 起点
flow1Waypoints.add(new GraphicInfo(200, 120, 0, 0)); // 终点
bpmnModel.addFlowGraphicInfoList("flow1", flow1Waypoints);
// 提交申请 → 经理审批的连线路径
List<GraphicInfo> flow2Waypoints = new ArrayList<>();
flow2Waypoints.add(new GraphicInfo(300, 120, 0, 0));
flow2Waypoints.add(new GraphicInfo(400, 120, 0, 0));
bpmnModel.addFlowGraphicInfoList("flow2", flow2Waypoints);
// 经理审批 → 结束事件的连线路径
List<GraphicInfo> flow3Waypoints = new ArrayList<>();
flow3Waypoints.add(new GraphicInfo(500, 120, 0, 0));
flow3Waypoints.add(new GraphicInfo(600, 115, 0, 0));
bpmnModel.addFlowGraphicInfoList("flow3", flow3Waypoints);
4. 生成BPMN XML
完成模型构建后,我们可以使用 BpmnXMLConverter 将其转换为标准BPMN 2.0 XML:
BpmnXMLConverter converter = new BpmnXMLConverter();
byte[] xmlBytes = converter.convertToXML(bpmnModel);
String bpmnXml = new String(xmlBytes, StandardCharsets.UTF_8);
System.out.println(bpmnXml);
生成的XML文件可以直接被Activiti引擎部署和运行。如果你使用Activiti Modeler等工具查看这个XML,会看到完整的可视化流程图。
5. 处理复杂流程场景
实际项目中的流程往往比简单的线性审批复杂得多。让我们看几个常见复杂场景的实现方法。
5.1 分支网关与条件流
假设我们的请假流程需要根据请假天数走不同的审批路径:3天以下只需直接主管审批,3天以上还需要HR审批。这需要用到排他网关:
// 创建排他网关
ExclusiveGateway decisionGateway = new ExclusiveGateway();
decisionGateway.setId("decisionGateway");
process.addFlowElement(decisionGateway);
// 修改原有流程:提交申请 → 决策网关
flow2.setTargetRef("decisionGateway");
// 决策网关 → 主管审批(条件:days <= 3)
SequenceFlow toManager = new SequenceFlow();
toManager.setId("toManager");
toManager.setSourceRef("decisionGateway");
toManager.setTargetRef("approveTask");
toManager.setConditionExpression("${days <= 3}");
process.addFlowElement(toManager);
// 决策网关 → HR审批(条件:days > 3)
UserTask hrApproval = new UserTask();
hrApproval.setId("hrApproval");
hrApproval.setName("HR审批");
hrApproval.setCandidateGroups("hr");
process.addFlowElement(hrApproval);
SequenceFlow toHr = new SequenceFlow();
toHr.setId("toHr");
toHr.setSourceRef("decisionGateway");
toHr.setTargetRef("hrApproval");
toHr.setConditionExpression("${days > 3}");
process.addFlowElement(toHr);
// HR审批 → 主管审批
SequenceFlow hrToManager = new SequenceFlow();
hrToManager.setId("hrToManager");
hrToManager.setSourceRef("hrApproval");
hrToManager.setTargetRef("approveTask");
process.addFlowElement(hrToManager);
5.2 子流程与调用活动
对于复杂的流程,我们可以将其拆分为多个子流程。下面是创建调用活动(Call Activity)的示例:
// 主流程中的调用活动
CallActivity departmentReview = new CallActivity();
departmentReview.setId("departmentReview");
departmentReview.setName("部门复核");
departmentReview.setCalledElement("deptReviewProcess"); // 引用的子流程ID
process.addFlowElement(departmentReview);
// 添加进出调用活动的连线
SequenceFlow toDeptReview = new SequenceFlow();
toDeptReview.setId("toDeptReview");
toDeptReview.setSourceRef("approveTask");
toDeptReview.setTargetRef("departmentReview");
process.addFlowElement(toDeptReview);
SequenceFlow fromDeptReview = new SequenceFlow();
fromDeptReview.setId("fromDeptReview");
fromDeptReview.setSourceRef("departmentReview");
fromDeptReview.setTargetRef("endEvent");
process.addFlowElement(fromDeptReview);
5.3 动态任务分配
代码生成的最大优势是可以实现动态的任务分配。以下是一些常见模式:
// 根据部门动态分配
UserTask deptTask = new UserTask();
deptTask.setId("deptTask");
deptTask.setName("部门处理");
deptTask.setAssignee("${deptManagerService.findManager(execution)}");
// 会签任务(多处理人)
UserTask multiApprove = new UserTask();
multiApprove.setId("multiApprove");
multiApprove.setName("会签审批");
multiApprove.setCandidateUsers("${approvalService.getApprovers(execution)}");
6. 高级技巧与最佳实践
在实际项目中应用代码生成流程时,以下经验可以帮助你避免常见陷阱:
- 坐标计算工具类 :手动计算每个元素的位置非常繁琐,建议创建一个工具类来辅助布局:
public class BpmnLayoutHelper {
private int currentX = 100;
private int currentY = 100;
private final int horizontalGap = 150;
private final int verticalGap = 100;
public GraphicInfo nextTaskPosition(int width, int height) {
GraphicInfo gi = new GraphicInfo();
gi.setX(currentX);
gi.setY(currentY);
gi.setWidth(width);
gi.setHeight(height);
currentX += width + horizontalGap;
return gi;
}
public List<GraphicInfo> createSequenceFlow(GraphicInfo source, GraphicInfo target) {
List<GraphicInfo> waypoints = new ArrayList<>();
waypoints.add(new GraphicInfo(
source.getX() + source.getWidth(),
source.getY() + source.getHeight()/2,
0, 0));
waypoints.add(new GraphicInfo(
target.getX(),
target.getY() + target.getHeight()/2,
0, 0));
return waypoints;
}
}
- 模板方法模式 :对于相似流程,可以使用模板方法减少重复代码:
public abstract class AbstractProcessGenerator {
protected final BpmnModel bpmnModel = new BpmnModel();
protected final Process process = new Process();
public AbstractProcessGenerator(String processId) {
process.setId(processId);
bpmnModel.addProcess(process);
}
protected abstract void buildProcessStructure();
protected abstract void layoutProcessElements();
public String generate() {
buildProcessStructure();
layoutProcessElements();
return new String(new BpmnXMLConverter().convertToXML(bpmnModel),
StandardCharsets.UTF_8);
}
}
- 版本控制与变更管理 :代码生成的流程应该与业务代码一起纳入版本控制。建议为每个流程定义一个描述文件(YAML/JSON),将流程结构与业务规则分离:
process:
id: leaveApproval_v2
name: 请假审批流程(2023版)
elements:
- type: startEvent
id: start
- type: userTask
id: apply
name: 提交申请
assignee: ${applicant}
- type: exclusiveGateway
id: decision
- type: userTask
id: managerApprove
name: 主管审批
candidateGroups: managers
condition: ${days <= 3}
- type: userTask
id: hrApprove
name: HR审批
candidateGroups: hr
condition: ${days > 3}
- type: endEvent
id: end
然后编写解析器将这种描述文件转换为BPMN模型:
public class ProcessGeneratorFromYaml {
public String generate(String yamlContent) {
ProcessDefinition definition = parseYaml(yamlContent);
BpmnModel model = new BpmnModel();
Process process = new Process();
// 根据definition构建process
// ...
return convertToXml(model);
}
}
- 单元测试策略 :为生成的流程编写测试用例,验证流程逻辑的正确性:
@Test
public void testLeaveApprovalProcess() {
// 生成流程
String bpmnXml = new LeaveProcessGenerator().generate();
// 部署流程
Deployment deployment = repositoryService.createDeployment()
.addString("leave.bpmn20.xml", bpmnXml)
.deploy();
// 启动流程实例
Map<String, Object> vars = new HashMap<>();
vars.put("applicant", "user1");
vars.put("days", 2);
ProcessInstance instance = runtimeService.startProcessInstanceByKey(
"leaveApproval", vars);
// 验证任务分配
Task task = taskService.createTaskQuery()
.processInstanceId(instance.getId())
.singleResult();
assertEquals("提交请假申请", task.getName());
// 完成任务并验证下一步
taskService.complete(task.getId());
task = taskService.createTaskQuery()
.processInstanceId(instance.getId())
.singleResult();
assertEquals("经理审批", task.getName());
}
- 性能优化 :当需要批量生成大量流程时,可以考虑以下优化手段:
// 重用BpmnXMLConverter实例(线程安全)
private static final BpmnXMLConverter CONVERTER = new BpmnXMLConverter();
// 使用池化技术管理BpmnModel实例
private static final ObjectPool<BpmnModel> MODEL_POOL = new GenericObjectPool<>(
new BasePooledObjectFactory<BpmnModel>() {
@Override
public BpmnModel create() {
return new BpmnModel();
}
});
public String generateProcess() throws Exception {
BpmnModel model = MODEL_POOL.borrowObject();
try {
// 使用model构建流程
return new String(CONVERTER.convertToXML(model),
StandardCharsets.UTF_8);
} finally {
model.reset(); // 清理内部状态
MODEL_POOL.returnObject(model);
}
}
更多推荐


所有评论(0)