服务任务
概览
服务任务(Service task) 是流程中表示自动活动的一种任务类型。 服务任务可以调用 Java 代码、外部服务、API 或者自动化程序以完成相应的功能。
服务任务在图中显示为一个圆角矩形,左上角带一个齿轮图标。
根据不同的实现,服务任务的 XML 可能不同。请参阅相应任务类型介绍中的示例。
服务任务的类型
任务类型(Task type) 参数定义服务任务的实现方式。 Jmix BPM 中的服务任务有以下几种类型:
我们简要了解一下:
- Spring bean
-
当流程到达任务时,将执行所选 bean 的指定方法。 方法参数可使用流程变量或固定值,返回结果可以存储在结果变量中。
- Java Delegate Class
-
服务任务作为 JavaDelegate 类实现,执行任务时调用 execute() 方法。 此外,还支持 字段注入 机制。
- Expression
-
这是调用任何方法或计算内联表达式的通用方法。
- Delegate expression
-
实际上,这不是表达式,而是一个实现 JavaDelegate 接口的 Spring bean。 因此,这种类型的任务具有两者的特点。
Spring Bean 服务任务
Jmix BPM 支持从服务任务中调用 Spring bean 方法,并为其提供参数值。 这种类型是最常见的服务任务类型。 因此作为默认类型选项。
可以点击 'Plus' 按钮从这里创建一个新的 bean:
然后,输入 bean 的名称:
完成后自动切换至代码编辑器,并编写需要使用的方法,示例:
@Component(value = "smpl_OrderStatusBean")
public class OrderStatusBean {
public Integer setStatus(String orderId, String status) {
// set status, returns quantity of items
return quantity;
}
}
Bean 的名称和方法也可以从下拉列表中选择:
选择方法后,将显示一个用于输入方法参数值的面板:
BPMN Inspector 会自动构建一个表达式,用于调用 bean 方法,这个表达式无法手动编辑。 对于上面截图中选择的方法,表达式为:
${smpl_OrderStatusBean.setStatus(OrderId,'Sent')}
请注意 Is Variable 复选框。主要对字符串参数有意义。 如果未勾选该复选框,则形成的表达式中,该参数将作为字符串输入(带单引号)。 如果勾选该复选框,则不会添加单引号,并且使用同名变量值作为参数。
-
${smpl_MyBean.someMethod('description')}
— 该表达式将使用字符串值description
。 -
${smpl_MyBean.someMethod(description)}
— 该表达式将使用名为description
的变量值。
结果变量
如果选择的方法有返回值,则会显示 结果变量(Result variable) 字段。 可以在此处使用一个已有的流程变量,也可以输入名称创建一个新的流程变量。
使用已有变量时请注意变量类型。
如果结果类型与已有变量的类型不同,则将创建一个相同名称的新流程变量。
例如,如果已经有一个 |
结果变量 有一个 本地变量(Use local scope) 复选框。
当设置为 true
时,服务任务创建的结果变量仅在任务的执行上下文中有效。
也就是说,该变量只能在当前执行过程中访问,不会传播到父执行过程或流程实例。
该设置可以将变量与服务任务的特定执行过程隔离开来。 如果同一服务任务的 多实例 同时运行, 则每个实例都有自己的本地变量,不会发生变量互相干扰的情况。
下面是 Spring bean 服务任务参数在 XML 中的表示:
<serviceTask id="set-status-service-task" name="Set order status"
flowable:async="true" (1)
flowable:expression="${smpl_OrderStatusBean.setStatus(orderId,'Sent')}" (2)
flowable:resultVariable="quantity" (3)
flowable:useLocalScopeForResultVariable="true" (4)
jmix:taskType="springBean" jmix:beanName="smpl_OrderStatusBean"> (5)
<extensionElements>
<jmix:springBean beanName="smpl_OrderStatusBean"
methodName="setStatus"> (6)
<jmix:methodParam name="orderId" type="java.lang.String"
isVariable="true">orderId</jmix:methodParam> (7)
<jmix:methodParam name="status" type="java.lang.String"
isVariable="false">Sent</jmix:methodParam> (8)
</jmix:springBean>
</extensionElements>
</serviceTask>
1 | — 异步(Async) 标记,默认是 false 并省略。 |
2 | — 通用表达式,单引号用 ' 转义。 |
3 | — 结果变量。 |
4 | — 本地变量,默认是 false 并省略。 |
5 | — 任务类型。 |
6 | — Spring bean 和方法。 |
7 | — 流程变量传递参数。 |
8 | — 值传递参数。 |
“execution” 流程变量
Spring bean 看不到流程执行的上下文。 但是在很多情况下需要获取流程执行的上下文。 例如,访问流程变量和当前的任务属性。
有一个名为 “execution” 的嵌入式流程变量,其类型为 DelegateExecution
,可以用作 Spring bean 的方法参数。
创建一个这样的方法,例如:
@Component("MyProcessBean")
public class MyProcessBean {
public void mySampleMethod(DelegateExecution execution) { (1)
String currentActivityId = execution.getCurrentActivityId();
Set<String> variableNames = execution.getVariableNames();
// etc.
}
}
1 | — execution 参数 |
然后在服务任务中设置:
Java 代理服务任务
在这种类型的服务任务中,业务逻辑将由一个实现了 org.flowable.engine.delegate.JavaDelegate
接口的类的 execute() 方法执行。
方法接收一个 execution
对象作为参数,因此可以在访问流程上下文,包括全部流程变量。
如果在 Task type 下拉框中选择 JavaDelegate class,可以通过点击 'Plus' 按钮新建一个类:
在弹窗内输入新 Java 代理类的名称:
然后会自动切换到代码编辑器,可以继续编写需要的逻辑。 例如,我们实现一个带有随机值的流程变量:
public class RandomIndexJavaDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
long randomIndex = new Random().nextLong(100L);
execution.setVariable("randomIndex", randomIndex);
}
}
在 XML 中,类名需要在 flowable:class
属性中设置,且使用全限定名称。
<serviceTask id="Activity_java_delegate" name="Java delegate"
flowable:class="com.company.jmixbpmtraining.delegate.RandomIndexJavaDelegate" (1)
jmix:taskType="javaDelegateClass"> (2)
<extensionElements />
</serviceTask>
1 | — 指定 Java 代理类。 |
2 | — 定义任务类型。 |
实例化 Java 代理类
Java Delegate 类型的服务任务中使用的类 在部署期间不会实例化。 当流程引擎在执行期间首次到达这个任务时,会创建一个 JavaDelegate 类的实例。
对于一个使用 Java 代理的服务任务,只会实例化一个代理类的实例。 如果一个流程中的多个服务任务使用的是同一个 Java 代理类, 那么将为每个服务任务创建一个单独的代理类实例。 所有流程实例共享任务的对应代理类实例。
也就是说,不同线程可能会同时执行同一个代理类,因此代理类中不能使用任何成员变量,且必须保证线程安全。 可能也会影响 字段注入。
代理表达式服务任务
代理表达式(delegate expression) 是服务任务中的一个强大功能,可以在运行时动态确定 Java 对象。
例如,${myServiceBean}
表达式会解析为一个名为 myServiceBean
的 Spring bean。
在 Spring 上下文中,代理表达式可以直接引用 Spring bean,无缝集成 Spring 框架。 因此,可以在代理实现中使用依赖注入并使用 Spring 的功能。
为了能在代理表达式中使用,JavaDelegate
的实现类必须带有 Spring 的 @Component
注解。
此时,两种类型功能都可使用 — Spring bean 和 Java 代理类:
@Component
public class MyDelegateExpression implements JavaDelegate {
// Class fields and injections
@Override
public void execute(DelegateExecution execution) {
// Required logic
}
}
最终,我们可以从该类中访问 Spring 上下文和流程上下文。 调用时,请使用 Delegate expression 任务类型。例如:
这里可以创建一个新的代理表达式类:
或从下拉列表选择一个已有的类:
如需指定服务任务在流程执行期间调用的类,可以使用解析为对象的表达式。
在 XML 中,可以使用 flowable:delegateExpression
属性:
<serviceTask id="delegate-expression"
name="Delegate expression task"
flowable:delegateExpression="${smpl_MyDelegateExpression}"
jmix:taskType="delegateExpression">
</serviceTask>
失败重试
关于 失败重试(fail retry) 的介绍,请参阅 失败重试。
配置
设置失败重试参数时,首先在 BPMN Inspector 中找到对应的属性:
设置值的格式必须是符合 ISO 8601 格式的时间周期表达式,与计时器事件表达式相同。
上图示例中的 R5/PT7M
表示作业执行程序会重试 5 次,并在每次重试前等待 7 分钟。
失败重试参数使用 flowable:failedJobRetryTimeCycle
元素表示。
示例:
<serviceTask id="failingServiceTask"
flowable:async="true"
flowable:class="org.flowable.engine.test.jobexecutor.RetryFailingDelegate">
<extensionElements>
<flowable:failedJobRetryTimeCycle>R5/PT7M</flowable:failedJobRetryTimeCycle> (1)
</extensionElements>
</serviceTask>
1 | — 失败重试参数 |
在默认配置下,流程引擎会对出现异常的作业重复执行 3 次。 |
字段注入
字段注入(field injections) 是 Flowable 中的一种机制,用于将固定字符串值或字符串解析的表达式传递给 Java 代理 类中的参数。 可以用在以下任务类型中:
-
Java 代理类
如果调用对象是 Java 代理类,那么也可以用在:
-
代理表达式
-
表达式
注入的字段只能是 org.flowable.engine.delegate.Expression
类型。
当注入的表达式解析完成后,可以转换为合适的目标类型。
不能通过 字段注入 传递实体或其他对象。实际上,表达式是以 |
如何注入字段:
-
在
JavaDelegate
类中创建字段定义:private Expression messageField; private Expression quantityField;
-
在图中,选择服务任务并创建与代码中定义的同名字段:
-
然后输入字段值,例如,表达式或字符串:
如果需要传递数值,请使用上图所示的表达式,例如
${3}
。 如果你只写 3,将被解析为String
对象 “3”,并且不能转换为Integer
类型。 -
在运行时,流程引擎会解析表达式并将结果字符串传递给 Java 代理类。
-
在 Java 代理类中,必须有从字段中获取值并转换为所需类型的代码:
String message = (String) messageField.getValue(execution); Integer quantity = (Integer) quantityField.getValue(execution);
字段注入和线程安全
一般来是,在服务任务中使用 Java 代理和字段注入是线程安全的。但是,在极少情况下,由于 Flowable 运行的配置或环境因素会导致线程安全无法得到保证。
- Java 代理类任务类型
-
这种类型使用字段注入始终是线程安全的。对于多个服务任务使用同一个代理类,仅实例化一个新实例,并在实例创建完成后立即执行字段注入。在不同的任务或流程定义中多次重用同一个类是没有问题的。
需要记住的是,不同的流程实例使用的 Java 代理类是同一个实例。 因此一个流程实例理论上有可能影响另一个流程实例,但实际上不会发生。
- Spring bean 服务和表达式任务类型
-
从技术上讲,对于 Flowable,Spring bean 服务任务由
flowable:expression
属性表示。使用
flowable:expression
属性时,不需要使用字段注入。参数通过方法调用传递,并且这些调用始终是线程安全的。严格意义上讲,字段注入可以用,但不应该用。
- 代理表达式服务任务
-
当使用
flowable:delegateExpression
属性时,代理实例的线程安全性将取决于表达式的解析方式。如果代理表达式在多个任务或流程定义中重复使用,并且表达式始终返回相同的实例,则使用字段注入不是线程安全的。两个服务任务可以使用相同的代理表达式,但注入不同的
Expression
字段。如果表达式解析为同一实例,则在流程执行的并发场景中注入字段时,可能存在先到先得的情况。解决此问题的最简单方法是:
-
重写 Java 代理,使用表达式或 Spring Bean,并通过方法参数传递所需的数据。
-
每次解析代理表达式时,返回代理类的新实例。例如,当使用 Spring 时,bean 的 scope 必须设置为
prototype
(例如,可以为代理类添加@Scope(SCOPE_PROTOTYPE)
注解)。
-
示例
public class UpperCaseJavaDelegate implements JavaDelegate {
private Expression messageField;
private Expression quantityField;
@Override
public void execute(DelegateExecution execution) {
String message = (String) messageField.getValue(execution);
Integer quantity = (Integer) quantityField.getValue(execution);
String upperCaseMessage = message.toUpperCase();
for (int i = 0; i < quantity; i++) {
System.out.println(upperCaseMessage);
}
}
}