服务任务
概览
服务任务(Service task) 用于调用 Java 逻辑、外部服务、API 或自动化程序。
服务任务在图中显示为一个圆角矩形,左上角带一个齿轮图标。
根据不同的实现,服务任务的 XML 可能不同。请参阅相应任务类型介绍中的示例。
任务的类型
任务类型 声明 Java 逻辑从服务任务中调用的方式:
-
Spring Bean:指定一个 Spring Bean、方法和方法参数。
-
Java 代理类:提供一个类,需要实现
JavaDelegate或FutureJavaDelegate。 -
表达式:调用一个方法表达式。
-
代理表达式:计算一个表达式,但是这个表达式的结果应该为一个代理对象。
Spring Bean
Jmix BPM 支持从服务任务中调用 Spring bean 方法并为其提供参数值。
可以分别使用 Bean Name 和 Method 字段选择现有的 bean 及其关联的方法。
选择方法后,会出现更多字段用于输入方法参数的值。
请注意 is variable 复选框。主要对字符串参数有意义。 如果未勾选该复选框,则形成的表达式中,该参数将作为字符串输入(带单引号)。 如果勾选该复选框,则不会添加单引号,并且使用同名变量值作为参数。
-
${smpl_MyBean.someMethod('description')}— 该表达式将使用字符串值description。 -
${smpl_MyBean.someMethod(description)}— 该表达式将使用名为description的变量值。
结果变量
如果选择的方法有返回值,则会显示 Result variable(结果变量) 字段。 可以在此处使用一个已有的流程变量,也可以输入名称创建一个新的流程变量。
|
使用已有变量时请注意变量类型。
如果结果类型与已有变量的类型不同,则将创建一个相同名称的新流程变量。
例如,如果已经有一个 |
在 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 或 org.flowable.engine.delegate.FutureJavaDelegate 接口的类的 execute() 方法执行。方法接收一个 execution 对象作为参数,因此可以访问流程上下文,包括全部流程变量。
如果在 Task type 下拉框中选择 JavaDelegate class,则可以继续选择需要使用的类:
例如,我们实现一个带有随机值的流程变量:
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 的功能。
为了能在代理表达式中使用代理类,与 Spring bean 类似,这些类需要带有 @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="${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>
</extensionElements>
</serviceTask>
|
在默认配置下,流程引擎会对出现异常的作业重复执行 3 次。 |
字段注入
字段注入(field injections) 是 Flowable 中的一种机制,用于将固定字符串值或字符串解析的表达式传递给 Java 代理 类中的参数。
注入的字段只能是 org.flowable.common.engine.api.delegate.Expression 类型。
当注入的表达式解析完成后,可以转换为合适的目标类型。
|
不能通过 字段注入 传递实体或其他对象。实际上,表达式是以 |
如何注入字段:
-
在代理类中创建字段定义:
private Expression messageField; private Expression quantityField; -
选择服务任务并使用与代码中一样的名称创建字段。字段值可以是表达式或字符串。
如果需要传递数值,请使用上图所示的表达式,例如
${3}。 如果只写 3,将被解析为String对象 “3”,并且不能转换为Integer类型。 -
在运行时,流程引擎会解析表达式并将结果字符串传递给代理类。
-
在 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);
}
}
}