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

Flowable入门系列文章20 - 基本的Flowable概念一

Flowable入门系列文章49 - 骡子任务

Flowable入门系列文章53 - 壳任务