当我们的流程复杂到一定程度时,就需要按照一定规则把业务拆分成若干子流程,这样业务模块之间才能明晰易于划分。
jBPM4 提供了 sub-process – 子流程活动,这可以在 “ 主干流程 ” 定义中调用其他的流程定义,从而 “ 组装 ” 我们的流程定义 。在运行到子流程活动时,工作流引擎将创建一个子流程实例,然后等待直到完成,当子流程实例完成后,流程就会流向下一个节点。
sub-process 活动的属性:
属性 | 类型 | 默认值 | 是否必需 | 描述 |
---|---|---|---|---|
sub-process-id | 字符串 | 无 | sub-process-id 与 sub_process-key,必需其一 | 流程定义的 ID 标识,可以通过一个流程的 ID 去引用此流程定义的指定版本 。 |
sub-process-key | 字符串 | 无 | sub-process-id 与 sub_process-key,必需其一 | 流程 key 标识,通过 key 去引用流程定义,也就意味着引用了该流程定义的最新版本 。 注意,该流程定义的最新版本会在每次活动实例执行时计算得出 。 |
outcome | 表达式 | 无 | 当 sub-process 活动的 transition 元素具有 outcome-value 时必需 | 当子流程活动执行结束时执行的表达式 。 表达式值用来匹配流出转移中的 outcome-value 元素值,起到选择 sub-process 活动下一步流向的作用 。 |
sub-process 活动的元素:
元素 | 聚合关系 | 描述 |
---|---|---|
parameter-in | 0..* | 子流程输入参数。即声明一个变量,在创建子流程实例时传入。 |
parameter-out | 0..* | 子流程输出参数。即声明一个变量,在子流程实例结束时,返回父流程实例。 |
sub-process 的 parameter-in – 子流程活动输入元素的属性:
属性 | 类型 | 默认值 | 是否必需 | 描述 |
---|---|---|---|---|
subvar | 字符串 | 无 | 必需 | 被赋值的子流程变量的名称。 |
var | 字符串 | 无 | var 与 expr 必需二选一 | 从父流程中输入的变量名称。 |
expr | 字符串 | 无 | var 与 expr 必需二选一 | 此表达式会在父流程中被解析,结果值会被输入到对应的子流程变量中。 |
lang | 字符串 | EL 表达式 | 可选 | 表达式使用的脚本语言。 |
sub-process 的 parameter-out – 子流程活动输出元素的属性:
属性 | 类型 | 默认值 | 是否必需 | 描述 |
---|---|---|---|---|
var | 字符串 | 无 | 必需 | 输出的目标 – 父流程中的变量名称。 |
subvar | 字符串 | 无 | subvar 与 expr 必需二选一 | 子流程中需要被输出的变量名称。 |
expr | 字符串 | 无 | subvar 与 expr 必需二选一 | 此表达式会在子流程中被解析,结果会被传入到对应的父流程变量中。 |
lang | 字符串 | EL 表达式 | 可选 | 表达式使用的脚本语言。 |
sub-process 活动的 outcome 元素必须有与之相呼应的 outcome-value 元素,这个 outcome-value 元素被定义在子流程活动的流出转移( transition )中 。
sub-process transition 的 outcome-value 元素:
元素 | 聚合关系 | 描述 |
---|---|---|
outcome-value | 0..1 | 它是一个值表达式 。 子流程活动结束时,如果子流程中某个转移的 outcome-value 值与子流程的 outcome 值相匹配,那么,父流程会通过此转移 。 注意:这个 outcome-value 值是在 transition 元素中定义的 。 |
父子流程是通过流程变量来传递数据的:父流程在子流程启动把值输入,子流程结束时把值输出。
假设有这样一个场景,父流程中的一个节点是审查,而这个节点是通过子流程实现的:
jPDL:
<process key="SubProcessDocument" xmlns="http://jbpm.org/4.4/jpdl">
<start g="211,136,48,48" name="start1">
<transition to="审核"/>
start>
<sub-process g="315,135,92,52" name="审核" sub-process-key="SubProcessReview">
<parameter-in subvar="document" var="document"/>
<parameter-out subvar="result" var="reviewResult"/>
<transition to="等待"/>
sub-process>
<state g="467,137,92,52" name="等待"/>
process>
这里引用了下面定义的子流程:
在实际的业务中,存在着各种各样的子流程,它们会被其他流程所引用。
jPDL:
<process name="SubProcessReview" xmlns="http://jbpm.org/4.4/jpdl">
<start g="269,233,48,48" name="start1">
<transition to="同意"/>
start>
<task assignee="deniro" g="384,232,107,52" name="同意">
<transition to="end1"/>
task>
<end g="548,236,48,48" name="end1"/>
process>
测试代码:
//创建变量,并设置
Map<String, Object> variables = new HashMap<>();
variables.put("document", "This document");
//带父变量发起流程实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey
("SubProcessDocument", variables);
//获取用户任务列表
List<Task> taskList = taskService.findPersonalTasks("deniro");
Task task = taskList.get(0);
//获取任务变量(来自父流程)
String document = (String) taskService.getVariable(task.getId(), "document");
assertEquals("This document", document);
//在子流程任务上设置新变量 result(实践一般来自于用户填写的表单)
variables = new HashMap<>();
variables.put("result", "accept");
// result 作为子流程变量返回给父流程实例
taskService.setVariables(task.getId(), variables);
taskService.completeTask(task.getId());//完成任务,结束子流程实例
processInstance = executionService.findProcessInstanceById(processInstance.getId());
//断言父流程已完成子流程活动,并到达了 wait 节点
assertNotNull(processInstance.findActiveExecutionIn("等待"));
//验证父流程的 reviewResult 变量值(它是子流程的输出结果)
String result = (String) executionService.getVariable(processInstance.getId(),
"reviewResult");
assertEquals("accept", result);
注意:如果是在 eclipse 中使用 jBPM 的插件来生成的 jPDL ,jPDL 会默认生成 sub-process-id,这样就无法通过 id 来得到子流程的定义信息(因为我们在子流程中没有定义 ID,用的是 sub-process-key)。
现在,我们使用 sub-process 活动的 outcome 属性来决定父流程的流出转移。
jPDL:
<process name="SubProcessDocument2" xmlns="http://jbpm.org/4.4/jpdl">
<start name="start1" g="113,224,48,48">
<transition to="审查"/>
start>
<sub-process name="审查" sub-process-key="SubProcessReview" outcome="#{result}"
g="226,220,92,52">
<transition name="同意" to="下一步" g="275,173:28,-27"/>
<transition name="需要完善" to="更新" g="-16,-22"/>
<transition name="拒绝" to="关闭" g="274,313:28,-21"/>
sub-process>
<state name="下一步" g="384,148,92,52"/>
<state name="更新" g="387,221,92,52"/>
<state name="关闭" g="388,289,92,52"/>
process>
子流程定义与之前的定义相同:
测试代码:
//带父变量发起流程实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey
("SubProcessDocument2");
//获取用户任务列表
List taskList = taskService.findPersonalTasks("deniro");
Task task = taskList.get(0);
//在子流程任务上设置新变量 result,这个值会被传递给 outcome 属性以决定父流程的走向
Map variables = new HashMap<>();
variables.put("result", "同意");
// result 作为子流程变量返回给父流程实例
taskService.setVariables(task.getId(), variables);
taskService.completeTask(task.getId());//完成任务,结束子流程实例
processInstance = executionService.findProcessInstanceById(processInstance.getId());
//断言父流程已完成子流程活动,并到达了“下一步”节点
assertNotNull(processInstance.findActiveExecutionIn("下一步"));
这种方法比 outcome 属性更容易理解。因为一个流程可以定义多个 end 活动,那么我们可以为子流程定义多个不同名称的 end 活动,这些 end 活动名称会自动与父流程的流出转移名称做同名关联。
父流程的定义与上一个父流程定义相比,仅少了 outcome 属性:
jPDL:
<process name="SubProcessDocument3" xmlns="http://jbpm.org/4.4/jpdl">
<start name="start1" g="113,224,48,48">
<transition to="审查"/>
start>
<sub-process name="审查" sub-process-key="SubProcessReview2" g="226,220,92,52">
<transition name="同意" to="下一步" g="275,173:28,-27"/>
<transition name="需要完善" to="更新" g="-16,-22"/>
<transition name="拒绝" to="关闭" g="274,313:28,-21"/>
sub-process>
<state name="下一步" g="384,148,92,52"/>
<state name="更新" g="387,221,92,52"/>
<state name="关闭" g="388,289,92,52"/>
process>
jPDL:
<process name="SubProcessReview2" xmlns="http://jbpm.org/4.4/jpdl">
<start g="269,233,48,48" name="start1">
<transition to="同意"/>
start>
<task assignee="deniro" g="384,232,107,52" name="同意">
<transition to="end1" name="需要完善" g="-23,-22"/>
<transition name="同意" to="end2" g="439,178:38,-22"/>
<transition name="拒绝" to="end3" g="442,339:30,-26"/>
task>
<end g="548,236,48,48" name="end1"/>
<end name="end2" g="550,154,48,48"/>
<end name="end3" g="546,315,48,48"/>
process>
测试代码:
//带父变量发起流程实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey
("SubProcessDocument3");
//获取用户任务列表
List taskList = taskService.findPersonalTasks("deniro");
Task task = taskList.get(0);
// 让任务流向 “同意”转移
taskService.completeTask(task.getId(),"同意");//完成任务,结束子流程实例
processInstance = executionService.findProcessInstanceById(processInstance.getId());
//断言父流程已完成子流程活动,并到达了“下一步”节点
assertNotNull(processInstance.findActiveExecutionIn("下一步"));