Flowable入门系列文章44 - Java服务任务
Posted 分享牛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flowable入门系列文章44 - Java服务任务相关的知识,希望对你有一定的参考价值。
1、描述
Java服务任务用于调用外部Java类。
2、图形表示法
服务任务可视化为圆角矩形,左上角有一个小齿轮图标。
3、XML表示
有四种方式来声明如何调用Java逻辑:
- 指定实现JavaDelegate或ActivityBehavior的类
- 评估解析为委托对象的表达式
- 调用方法表达式
- 评估一个值表达式
要指定在流程执行期间调用的类,需要使用flowable:class属性提供完全限定的类名。
<serviceTask id="javaService"
name="My Java Service Task"
flowable:class="org.flowable.MyJavaDelegate" />
也可以使用解析为对象的表达式。这个目的必须遵循相同的规则,当被创建的对象flowable:class,使用属性。
<serviceTask id="serviceTask" flowable:delegateExpression="${delegateExpressionBean}" />
这里delegateExpressionBean是一个bean,它实现了JavaDelegate在Spring容器中定义的接口。
要指定应评估的UEL方法表达式,请使用属性flowable:expression。
<serviceTask id="javaService"
name="My Java Service Task"
flowable:expression="#{printer.printMessage()}" />
printMessage将在名为的命名对象上调用方法(不带参数)printer。
也可以使用表达式中使用的方法传递参数。
<serviceTask id="javaService"
name="My Java Service Task"
flowable:expression="#{printer.printMessage(execution, myVar)}" />
方法printMessage将在名为的对象上调用printer。传递的第一个参数是DelegateExecution,在表达式上下文中可用,默认情况下,可用execution。传递的第二个参数是myVar当前执行中名称变量的值。
要指定应评估的UEL值表达式,请使用属性flowable:expression。
<serviceTask id="javaService"
name="My Java Service Task"
flowable:expression="#{split.ready}" />
财产的getter方法ready,getReady(不带参数),将在叫的bean被调用split。命名的对象在执行的流程变量和Spring上下文(如果适用)
中解析。
4、履行
要实现一个可以在流程执行期间调用的类,这个类需要实现org.flowable.engine.delegate.JavaDelegate接口,并在execute方法中提供所需的逻辑。当流程执行到达此特定步骤时,它将执行该方法中定义的逻辑,并将该活动保留为默认的BPMN 2.0方式。
例如,我们来创建一个可以用来将流程变量String更改为大写的Java类。这个类需要实现org.flowable.engine.delegate.JavaDelegate接口,它要求我们实现execute(DelegateExecution)方法。这个操作将被引擎调用,并且需要包含业务逻辑。流程实例信息,比如流程变量,可以通过DelegateExecution接口进行访问和操作。
public class ToUppercase implements JavaDelegate {
public void execute(DelegateExecution execution) {
String var = (String) execution.getVariable("input");
var = var.toUpperCase();
execution.setVariable("input", var);
}
}
注意:将只有一个为其定义的serviceTask创建的Java类实例。所有流程实例共享将用于调用execute(DelegateExecution)的相同类实例。这意味着类不能使用任何成员变量,并且必须是线程安全的,因为它可以从不同的线程同时执行。这也影响了田间注入的处理方式。
流程定义中引用的类(通过使用flowable:class)在部署期间不会被实例化。只有当流程执行第一次到达使用类的过程中的某个点时,才会创建该类的一个实例。如果找不到类,FlowableException就会抛出一个。其原因是,部署时的环境(更具体地说是类路径)通常与实际运行时环境有所不同。例如,在Flowable应用程序中使用ant或业务归档上载来部署流程时,类路径将不会自动包含引用的类。
[INTERNAL:非公共实现类]也可以提供一个实现org.flowable.engine.impl.delegate.ActivityBehavior接口的类。然后,实现可以访问更强大的引擎功能,例如,影响流程的控制流程。但请注意,这不是一个很好的做法,应该尽可能避免。所以,建议只将ActivityBehavior接口用于高级用例,如果你确切地知道你在做什么。
5、现场注入
可以将值注入到委托类的字段中。支持以下类型的注入:
- 固定字符串值
- 表达式
如果可用,则按照Java Bean命名约定(例如,字段firstName为setter setFirstName(…)),通过您的委托类中的公共setter方法注入该
值。如果没有setter可用于该字段,私人成员的值将被设置在委托。某些环境中的SecurityManagers不允许修改私有字段,因此为要注入的字段公开setter方法更为安全。
无论在过程定义中声明的值的类型如何,注入目标上的setter / private字段的类型应始终为org.flowable.engine.delegate.Expression。表达式解析后,可以将其转换为适当的类型。
使用’flowable:class’属性时支持字段注入。当使用可移植的:delegateExpression属性时,字段注入也是可能的,但是关于线程安全的特殊规则也适用。
下面的代码片段显示了如何将一个常量值注入到类中声明的字段中。请注意,我们需要在实际的字段注入声明之前声明extensionElements XML元素,这是BPMN 2.0 XML Schema的一个要求。
<serviceTask id="javaService"
name="Java service invocation"
flowable:class="org.flowable.examples.bpmn.servicetask.ToUpperCaseFieldInjected">
<extensionElements>
<flowable:field name="text" stringValue="Hello World" />
</extensionElements>
</serviceTask>
该类ToUpperCaseFieldInjected有一个text类型的字段org.flowable.engine.delegate.Expression。调用时ext.getValue(execution),
Hello World将返回配置的字符串值:
public class ToUpperCaseFieldInjected implements JavaDelegate {
private Expression text;
public void execute(DelegateExecution execution) {
execution.setVariable("var", ((String)text.getValue(execution)).toUpperCase());
}
}
或者,对于长文本(例如,内联电子邮件),可以使用’flowable:string’子元素:
<serviceTask id="javaService"
name="Java service invocation"
flowable:class="org.flowable.examples.bpmn.servicetask.ToUpperCaseFieldInjected">
<extensionElements>
<flowable:field name="text">
<flowable:string>
This is a long string with a lot of words and potentially way longer even!
</flowable:string>
</flowable:field>
</extensionElements>
</serviceTask>
要注入在运行时动态解析的值,可以使用表达式。这些表达式可以使用流程变量或Spring定义的bean(如果使用Spring的话)。正如服务任务实现中提到的那样,当使用flowable:class属性时,Java类的一个实例将在服务任务中的所有进程实例之间共享。要在字段中动态注入值,可以将值和方法表达式注入org.flowable.engine.delegate.Expression到可以使用elegateExecution传入的execute方法评估/调用的表达式中。
下面的示例类使用注入的表达式并使用当前的解决方案DelegateExecution。甲genderBean同时使该方法调用用于性别变量。完整的代码和测试可以在中找到org.flowable.examples.bpmn.servicetask.JavaServiceTaskTest.testExpressionFieldInjection
<serviceTask id="javaService" name="Java service invocation"
flowable:class="org.flowable.examples.bpmn.servicetask.ReverseStringsFieldInjected">
<extensionElements>
<flowable:field name="text1">
<flowable:expression>${genderBean.getGenderString(gender)} </flowable:expression>
</flowable:field>
<flowable:field name="text2">
<flowable:expression>Hello ${gender == 'male' ? 'Mr.' : 'Mrs.'} ${name}</flowable:expression>
</flowable:field>
</ extensionElements>
</ serviceTask>
public class ReverseStringsFieldInjected implements JavaDelegate {
private Expression text1;
private Expression text2;
public void execute(DelegateExecution execution) {
String value1 = (String) text1.getValue(execution);
execution.setVariable("var1", new StringBuffer(value1).reverse().toString());
String value2 = (String) text2.getValue(execution);
execution.setVariable("var2", new StringBuffer(value2).reverse().toString());
}
}
或者,也可以将表达式设置为属性而不是子元素,从而使XML不那么冗长。
<flowable:field name="text1" expression="${genderBean.getGenderString(gender)}" />
<flowable:field name="text1" expression="Hello ${gender == 'male' ? 'Mr.' : 'Mrs.'} ${name}" />
6、现场注射和螺纹安全
一般来说,对Java委托和字段注入使用服务任务是线程安全的。但是,有一些情况下,不能保证线程安全,这取决于Flowable正在运行的设置或环境。
有了flowable:class属性,使用字段注入总是线程安全的。对于引用特定类的每个服务任务,新实例将被实例化,并且在创建实例时将会注入一次字段。在不同的任务或流程定义中多次重复使用同一个类是没有问题的。
使用flowable:expression属性时,不能使用字段注入。参数是通过方法调用传递的,而且这些参数总是线程安全的。
使用flowable:delegateExpression属性时,委托实例的线程安全性将取决于表达式的解析方式。如果委托表达式在各种任务或过程定义中被重用,并且表达式总是返回相同的实例,则使用字段注入不是线程安全的。我们来看几个例子来澄清。
假设表达式是$ {factory.createDelegate(someVariable)},其中factory是引擎已知的Java bean(例如,使用Spring集成的Springbean),每次解析表达式时都会创建一个新实例。在这种情况下使用字段注入时,关于线程安全性没有问题:每次解析表达式时,这些字段都会注入到这个新实例中。
然而,假设表达式是$ {someJavaDelegateBean},它解析为JavaDelegate类的实现,并且我们运行在创建每个bean的singleton实例的环境中(比如Spring,但也有很多其他的)。在不同的任务或流程定义中使用此表达式时,表达式将始终解析为相同的实例。在这种情况下,使用字段注入不是线程安全的。例如:
<serviceTask id="serviceTask1" flowable:delegateExpression="${someJavaDelegateBean}">
<extensionElements>
<flowable:field name="someField" expression="${input * 2}"/>
</extensionElements>
</serviceTask>
<!-- other process definition elements -->
<serviceTask id="serviceTask2" flowable:delegateExpression="${someJavaDelegateBean}">
<extensionElements>
<flowable:field name="someField" expression="${input * 2000}"/>
</extensionElements>
</serviceTask>
此示例代码片段有两个使用相同委托表达式的服务任务,但为“ 表达式”字段注入不同的值。如果表达式解析为相同的实例,则在执行进程时,在注入字段someField时,并发场景中可能会出现争用条件。
解决这个问题的最简单的方法是:
- 重写Java委托以使用表达式并通过方法参数将所需的数据传递给委托。
- 每次解析委托表达式时,都会返回委托类的新实例。例如,使用Spring时,这意味着必须将bean的范围设置为原型(例如,通过将@Scope(SCOPE_PROTOTYPE)注释添加到委托类)。
从Flowable v5.22开始,可以通过设置delegateExpressionFieldInjectionMode属性的值(采用org.flowable.engine中的一个值)来设置流程引擎配置,从而禁止在委托表达式中使用字段注入.imp.cfg.DelegateExpressionFieldInjectionMode enum)。
以下设置是可能的:
- DISABLED:在使用委托表达式时完全禁用字段注入。将不会尝试现场注射。当涉及到线程安全时,这是最安全的模式。
- 兼容性:在这种模式下,行为将与v5.21之前的行为完全相同:使用委托表达式时可以进行字段注入,并且在委托类中未定义字段时将引发异常。当然,这对于线程安全来说是最不安全的模式,但是它可能需要向后兼容,或者可以安全地使用委托表达式仅用于一组进程定义中的一个任务(因此不使用并发的竞赛条件可能会发生)。
- MIXED:在使用delegateExpressions时允许注入,但当代理上没有定义字段时不会抛出异常。这允许混合的行为,其中一些代表有注入(例如,因为他们不是单身人士),有些则不是。
- Flowable 5.x版的默认模式是COMPATIBILITY。
- Flowable 6.x版的默认模式是MIXED。
举一个例子,假设我们正在使用MIXED模式,并且正在使用Spring集成。假设我们在Spring配置中有以下bean:
<bean id="singletonDelegateExpressionBean"
class="org.flowable.spring.test.fieldinjection.SingletonDelegateExpressionBean" />
<bean id="prototypeDelegateExpressionBean"
class="org.flowable.spring.test.fieldinjection.PrototypeDelegateExpressionBean"
scope="prototype" />
第一个bean是一个普通的Spring bean,因此是一个单例。第二个有作为范围的原型,并且Spring容器将在每次请求bean时返回一个新的实例。
给定以下流程定义:
<serviceTask id="serviceTask1" flowable:delegateExpression="${prototypeDelegateExpressionBean}">
<extensionElements>
<flowable:field name="fieldA" expression="${input * 2}"/>
<flowable:field name="fieldB" expression="${1 + 1}"/>
<flowable:field name="resultVariableName" stringValue="resultServiceTask1"/>
</extensionElements>
</serviceTask>
<serviceTask id="serviceTask2" flowable:delegateExpression="${prototypeDelegateExpressionBean}">
<extensionElements>
<flowable:field name="fieldA" expression="${123}"/>
<flowable:field name="fieldB" expression="${456}"/>
<flowable:field name="resultVariableName" stringValue="resultServiceTask2"/>
</extensionElements>
</serviceTask>
<serviceTask id="serviceTask3" flowable:delegateExpression="${singletonDelegateExpressionBean}">
<extensionElements>
<flowable:field name="fieldA" expression="${input * 2}"/>
<flowable:field name="fieldB" expression="${1 + 1}"/>
<flowable:field name="resultVariableName" stringValue="resultServiceTask1"/>
</extensionElements>
</serviceTask>
<serviceTask id="serviceTask4" flowable:delegateExpression="${singletonDelegateExpressionBean}">
<extensionElements>
<flowable:field name="fieldA" expression="${123}"/>
<flowable:field name="fieldB" expression="${456}"/>
<flowable:field name="resultVariableName" stringValue="resultServiceTask2"/>
</extensionElements>
</serviceTask>
我们有四个服务任务,第一个和第二个使用$ {prototypeDelegateExpressionBean}委托表达式,第三个和第四个使用$
{singletonDelegateExpressionBean}委托表达式。
先来看看原型bean:
public class PrototypeDelegateExpressionBean implements JavaDelegate {
public static AtomicInteger INSTANCE_COUNT = new AtomicInteger(0);
private Expression fieldA;
private Expression fieldB;
private Expression resultVariableName;
public PrototypeDelegateExpressionBean() {
INSTANCE_COUNT.incrementAndGet();
}
@Override
public void execute(DelegateExecution execution) {
Number fieldAValue = (Number) fieldA.getValue(execution);
Number fieldValueB = (Number) fieldB.getValue(execution);
int result = fieldAValue.intValue() + fieldValueB.intValue();
execution.setVariable(resultVariableName.getValue(execution).toString(), result);
}
}
当我们在运行上面的流程定义的流程实例之后检查INSTANCE_COUNT时,我们会返回两个,因为每次解析$
{prototypeDelegateExpressionBean}时都会创建一个新实例。字段可以在这里没有任何问题注入,我们可以在这里看到三个Expression成员字段。
但是,看起来稍有不同:
import java.util.concurrent.atomic.AtomicInteger;
public class SingletonDelegateExpressionBean implements JavaDelegate {
public static AtomicInteger INSTANCE_COUNT = new AtomicInteger(0);
public SingletonDelegateExpressionBean() {
INSTANCE_COUNT.incrementAndGet();
}
@Override
public void execute(DelegateExecution execution) {
Expression fieldAExpression = DelegateHelper.getFieldExpression(execution, "fieldA");
Number fieldA = (Number) fieldAExpression.getValue(execution);
Expression fieldBExpression = DelegateHelper.getFieldExpression(execution, "fieldB");
Number fieldB = (Number) fieldBExpression.getValue(execution);
int result = fieldA.intValue() + fieldB.intValue();
String resultVariableName = DelegateHelper.getFieldExpression(execution,
"resultVariableName").getValue(execution).toString();
execution.setVariable(resultVariableName, result);
}
}
该INSTANCE_COUNT将永远是一个在这里,因为它是一个单例。在这个委托中,没有表达式成员字段。这是可能的,因为我们在混合模式下运行。在COMPATIBILITY模式下,它会抛出一个异常,因为它期望成员字段在那里。DISABLED模式也适用于这个bean,但是它不允许使用上面使用field注入的原型bean。
在这个委托代码中,使用org.flowable.engine.delegate.DelegateHelper类,它具有一些有用的实用方法来执行相同的逻辑,但是在代理是单例时以线程安全的方式。而不是注入表达式,它通过getFieldExpression方法获取。这意味着当涉及到服务任务XML时,这些字段的定义与单例bean完全一样。如果您查看上面的XML片段,您可以看到它们在定义上是相同的,只有实现逻辑不同。
技术说明:getFieldExpression会反省BpmnModel,并在执行该方法时动态创建Expression,从而使其线程安全。
- 对于Flowable v5.x,DelegateHelper不能用于ExecutionListener或TaskListener(由于架构上的缺陷)。要为这些侦听器创建线程安全的实例,请使用表达式或确保在每次解析委托表达式时都创建一个新实例。
- 对于Flowable V6.x,DelegateHelper在ExecutionListener和TaskListener实现中起作用。例如,在V6.x中,可以使用DelegateHelper。
编写以下代码:
<extensionElements>
<flowable:executionListener
delegateExpression="${testExecutionListener}" event="start">
<flowable:field name="input" expression="${startValue}" />
<flowable:field name="resultVar" stringValue="processStartValue" />
</flowable:executionListener>
</extensionElements>
其中testExecutionListener解析为实现ExecutionListener接口的实例:
@Component("testExecutionListener")
public class TestExecutionListener implements ExecutionListener {
@Override
public void notify(DelegateExecution execution) {
Expression inputExpression = DelegateHelper.getFieldExpression(execution, "input");
Number input = (Number) inputExpression.getValue(execution);
int result = input.intValue() * 100;
Expression resultVarExpression = DelegateHelper.getFieldExpression(execution, "resultVar");
execution.setVariable(resultVarExpression.getValue(execution).toString(Flowable入门系列文章45 - Web服务任务
Flowable入门系列文章13 - Flowable API 03
Flowable入门系列文章11 - Flowable API 01