子流程是一个包含其自身活动、网关和事件的流程片段,它本身作为父流程中的一个单一活动存在。从父流程的视角看,整个子流程就是一个黑盒子,有唯一的入口和出口。
为什么用?
使用子流程能带来巨大的好处:
降低复杂度(Abstraction): 你可以将一个复杂的业务流程分解成几个高阶的步骤,每个步骤都是一个子流程。这使得主流程图非常清晰、易于理解。你可以先关注“做什么”(主流程),再深入到“怎么做”(子流程)。
模块化与重用(Modularity & Reusability): 将通用的流程片段(如“发票审批流程”、“用户认证流程”)创建为独立的子流程,就可以在多个不同的主流程中调用它,避免重复建模。
范围界定(Scoping): 子流程为其内部的活动创建了一个独立的作用域。这对于变量管理和事件处理尤其重要。例如,你可以为一个子流程(代表一个阶段)整体附加一个边界事件(如定时器或错误事件),而不是为其中的每一个活动都附加。
两种核心类型的子流程
在 Flowable 中,子流程主要分为两大类:嵌入式子流程(Embedded Sub-Process) 和 调用活动(Call Activity)。它们的区别非常关键,是理解子流程的核心。
a) 嵌入式子流程 (Embedded Sub-Process)
可以把它想象成一个“私有方法”或流程图里的一个“文件夹”。
它在图形上表现为一个圆角矩形,内部可以展开,包含一系列的活动。
核心特性:
物理包含: 嵌入式子流程的定义是其父流程BPMN XML文件的一部分。它不是一个独立的文件。
共享实例: 它在父流程的同一个流程实例中运行。没有创建新的流程实例。
直接访问变量: 它可以直接读取和修改父流程的所有变量,就像在同一个作用域中一样。它也可以创建自己的局部变量,当子流程结束后,这些变量通常会合并到父流程作用域(取决于具体实现)。
**不可重用:**因为它被物理定义在父流程内部,所以它不能被其他流程定义所调用。
主要用途:为一组活动创建作用域(Scoping)。 这是它最强大的用途,特别是为了附加边界事件。
<process id="orderProcess">
...
<!-- 嵌入式子流程定义 -->
<subProcess id="paymentSubProcess" name="支付处理阶段">
<startEvent id="subStart"/>
<sequenceFlow sourceRef="subStart" targetRef="callPaymentGateway"/>
<serviceTask id="callPaymentGateway" name="调用支付网关"/>
...
<endEvent id="subEnd"/>
</subProcess>
<!-- 边界定时器事件,附加在整个子流程上 -->
<boundaryEvent id="paymentTimeout" attachedToRef="paymentSubProcess">
<timerEventDefinition>
<timeDuration>PT30M</timeDuration>
</timerEventDefinition>
</boundaryEvent>
...
</process>
在这个例子中,paymentTimeout 定时器监控的是整个 paymentSubProcess。无论流程在子流程的哪个步骤,只要30分钟一到,整个子流程就会被中断,流程会沿着定时器事件的路径走。这极大地简化了模型。
b) 调用活动 (Call Activity)
可以把它想象成一个“公共方法调用”或调用一个完全独立的程序。
它在图形上通常表现为一个带有粗边框的圆角矩形。
核心特性:
物理独立: 调用活动所调用的子流程是一个完全独立的BPMN流程定义(即一个单独的 .bpmn20.xml 文件)。
创建新实例: 当父流程执行到调用活动时,它会创建一个被调用流程的全新流程实例。父流程会暂停,等待这个子流程实例完成后再继续。
数据隔离与映射: 父子流程之间的数据是隔离的。 子流程无法直接访问父流程的变量。必须通过在调用活动上明确配置输入/输出参数映射来传递数据。
可重用: 因为被调用的流程是独立的,所以它可以被任意多个不同的父流程所调用,是实现流程重用的主要方式。
主要用途:流程的模块化和重用。
场景示例:通用的审批流程
公司里可能有很多流程都需要一个“两级审批”模块(如请假、报销、采购申请)。我们可以把“两级审批”建成一个独立的、可重用的流程。
<process id="expenseClaimProcess">
...
<!-- 调用活动 -->
<callActivity id="twoLevelApproval" name="执行两级审批"
calledElement="twoLevelApprovalProcess">
<extensionElements>
<!-- 输入映射:将父流程变量传递给子流程 -->
<flowable:in source="claimAmount" target="amount"/>
<flowable:in source="claimant" target="applicant"/>
<!-- 输出映射:将子流程结果取回到父流程 -->
<flowable:out source="approved" target="isExpenseApproved"/>
</extensionElements>
</callActivity>
...
</process>
子流程代码块
<!-- 这是一个完全独立的流程定义 -->
<process id="twoLevelApprovalProcess" name="两级审批流程">
<startEvent id="start"/>
<userTask id="managerApproval" name="经理审批"/>
<userTask id="directorApproval" name="总监审批"/>
<endEvent id="end"/>
<!-- 审批逻辑... -->
<!-- 最终会设置一个名为 "approved" 的布尔型流程变量 -->
</process>
在这个例子中,expenseClaimProcess 通过 callActivity 调用了 twoLevelApprovalProcess。它通过 flowable:in 将自己的 claimAmount 和 claimant 变量的值,分别传递给了子流程的 amount 和 applicant 变量。当子流程结束后,它通过 flowable:out 将子流程的 approved 变量的值取回,并存入父流程的 isExpenseApproved 变量中。