在 HumanTask 示例中,使用了流程变量和任务变量。根据二者作用范围的不同,我们又分别把它们称之为全局变量和局部变量。
将 HumanTask.bpmn 导入到 workbench 中用流程设计器查看流程图。
查看流程实例变量。
打开 HumanTaskExample.java,找到以下代码:
// start a new process instance Map<String, Object> params = new HashMap<String, Object>(); params.put("userId", "krisv"); params.put("description", "Need a new laptop computer"); ksession.startProcess("com.sample.humantask", params);
在流程启动时,在 startProcess 方法的第二个参数中传递一个 Map,这 map 将会用于初始化流程变量的值。也就是说流程实例启动后,流程变量中的 userId 和 description 将被赋值。
此外,还有一个 date 变量也会被赋值,但是不是通过 startProcess() 方法赋值的,而是通过一个脚本任务来赋值的。
选中流程图中的名为 Time 的任务,打开属性面板:
脚本任务是自动执行的,它调用了一段 java 代码,利用 kcontext 的 setVariable 方法将 date 变量设置为当前时间。
为了验证这一点,我们可以在任务执行前打印出流程变量的值。打印流程变量的代码是这段代码:
Map<String, Object> content = taskService.getTaskContent(task4.getId()); for (Map.Entry<?, ?> entry : content.entrySet()) { System.out.println(entry.getKey() + " = " + entry.getValue()); }
在 HumanTaskExample.java 中找到这段代码,选中它,右键 Refactor -> Extract -> Method …,将它抽取成一个私有方法:
private static void getVariables(TaskService taskService, TaskSummary task4) { Map<String, Object> content = taskService.getTaskContent(task4.getId()); for (Map.Entry<?, ?> entry : content.entrySet()) { System.out.println(entry.getKey() + " = " + entry.getValue()); } }
jBMP 7 似乎不允许直接读取流程变量,尝试了各种直接访问流程变量的方法都不成功。
实际上这个方法打印的并不会打印流程变量,而只会打印任务变量,即 Assignments 中的输入变量。因为这些变量已经和全局变量进行了绑定,所以也可以用这种方式查看全局变量。
然后在,每次 taskService.start() 语句后调用这个方法,比如 getVariables(taskService,task1);
,注意改变 task1 为不同的变量。
在每个 getVariables() 调用之后添加断点。Debug 运行。
当第一个断点停下时,控制台中会打印出:
date = Fri Mar 08 13:39:17 CST 2019 TaskName = Request Review NodeName = Review description = Need a new laptop computer SwimlaneActorId = null userId = krisv GroupId = sales
这是第一个用户任务,当第一个用户任务开始时,脚本任务已经执行,所以这里的 date 变量是有值的,TaskName 是当前任务(流程图中的 Review 节点)的任务名称,NodeName 是当前任务的节点名称,这两者可以一样,也可以不一样。
description 和 userId 的值就是 startProcess 方法赋值的,值和代码中设置的是一样的。
GroupId 是任务的 Groups 属性所设置的。
继续执行,断点在第二个任务(即 User Approval 节点)start 后停下,此时控制台打印:
result = Accept
date = Fri Mar 08 13:39:17 CST 2019
TaskName = Request Approval
NodeName = User Approval
description = Need a new laptop computer
comment = Agreed, existing laptop needs replacing
ActorId = krisv
userId = krisv
和之前相比,增加了 result 和 comment 变量。在上一个任务完成时:
Map<String, Object> results = new HashMap<String, Object>(); results.put("comment", "Agreed, existing laptop needs replacing"); results.put("outcome", "Accept"); taskService.complete(task1.getId(), "sales-rep", results);
jBPM 7 似乎不允许直接设置全局变量,尝试了各种方法都不成功,complete 方法似乎是唯一的设置全局变量的方法。
TaskService 的 complete 方法设置全局变量其实比较绕。首先要通过一个 Map 来设置任务的输出变量,然后引擎将它同步到全局变量。然后在下一个任务中,又要通过读取输入变量,才能读取到全局变量的值。
例如在第一个任务 complete 时,我们在 map 中设置了 comment 和 outcome。这两个其实是任务的输入变量。然后运行时引擎会根据 Assignments 的设置将它们同步给全局变量 comment 和 result。
其中 outcome 变量会绑定到全局变量 result。你可以查看任务的分配属性(Assignments):
outcome 绑定了全局变量 result。所以 taskService.complete 方法实际上是对局部任务变量进行赋值,然后由运行时引擎同步给全局变量。
而在第二个任务中,当我们用 getVariables 打印第二个任务的局部变量时,局部变量中就包括了 result:
这个 result 绑定的是全局变量 result。所以上面的打印结果中包含了 result。
最后一个是 ActorId 变量。这来自于任务的 Actors 属性:
#{userId}
引用了变量 userId。
继续执行,断点在第三个任务(Manager Approval 节点)执行时停下。控制台:
result = Accept date = Fri Mar 08 13:39:17 CST 2019 TaskName = Request Approval NodeName = Manager Approval description = Need a new laptop computer comment = Agreed, existing laptop needs replacing userId = krisv GroupId = PM
除了 GroupId 变量(来自任务的 Groups 属性)外,没有增加新的变量。但是在 User Approval 节点的办结代码中,修改了 outcome 变量:
results = new HashMap<String, Object>(); results.put("outcome", "Agree"); taskService.complete(task2.getId(), "krisv", results);
但是,这个 outcome 变量绑定的不再是 result 全局变量,而是 resultUser 全局变量:
但在 Manager Approval 任务的打印中,我们并没有看到 resultUser 变量!
这是因为 User Approval 和 Manager Approval 使用的并行网关!二者不是前后关系,而是并行关系。也就是说,User Approval 节点的下一节点并不是 Manager Approval,而是 Notification 节点。而 TaskService.complete 方法传递变量的方式是向下一节点传递,因此在 Manager Approval 节点不会收到 User Approval 节点传递的变量。如果想看到 User Approval 的传递的局部变量,应该在 Notification 节点进行打印。
继续执行,断点在第四个节点(Notification节点)停下。控制台:
result = Accept date = Fri Mar 08 13:39:17 CST 2019 resultUser = Agree resultManager = Agree TaskName = Request Notification NodeName = Notification description = Need a new laptop computer comment = Agreed, existing laptop needs replacing SwimlaneActorId = sales-rep userId = krisv GroupId = sales
看到了吧!在 Notification 节点,你不仅看到了 User Approval 新增的 resultUser 变量,还看到了 Manager Approval 增加的 resultManager 变量!因为 Manager Approval 的下一节点还是 Notification(再次使用了一个并行网关)。
再看一眼打印全局变量的方法,它需要一个 TaskService 实例,和一个 TaskSummary 对象(其实只需要 taskId 就行了)。那么假设这个任务已经 complete,甚至整个运行时已经被销毁了,它还能工作吗?还是报空指针异常?
让我们来试一试,在任务 complete 和 disposeRuntimeEngine 之后增加一个 getVariables 语句:
taskService.complete(task4.getId(), "sales-rep", null); getVariables(taskService,task4); System.out.println("Process instance completed"); manager.disposeRuntimeEngine(runtime); getVariables(taskService,task4);
变量仍然打印出来了,没有任何问题。