- 1. 简介
- 2. 开始
- 3. 配置
- 4. Flowable API
- 5. 集成 Spring
- 6. 部署
- 7. BPMN 2.0 介绍
- 8. BPMN 2.0 结构
- 9. 表单
- 10. JPA
- 11. 历史
- 12. 身份管理
- 13. Eclipse Designer
- 14. Flowable UI 应用
- 15. REST API
- 16. 集成 CDI
- 17. 集成 LDAP
- 18. 高级
- 19. 工具
8.5. 任务
8.5.1. 用户任务
描述
“用户任务(user task)”用于对需要人工执行的任务进行建模。当流程执行到达用户任务时,会为指派至该任务的用户或组的任务列表创建一个新任务。
图示
用户任务用左上角有一个小用户图标的标准任务(圆角矩形)表示。
XML表示
用户任务在XML中如下定义。其中id是必须属性,name是可选属性。
<userTask name="Important task" />
也可以为用户任务添加描述(description)。事实上任何BPMN 2.0元素都可以有描述。描述由documentation元素定义。
<userTask name="Schedule meeting" >
<documentation>
Schedule an engineering meeting for next week with the new hire.
</documentation>
可以使用标准Java方式获取描述文本:
task.getDescription()
到期日期
每个任务都可以使用一个字段标志该任务的到期日期(due date)。可以使用查询API,查询在给定日期前或后到期的任务。
可以在任务定义中使用扩展指定表达式,以在任务创建时设定到期日期。该表达式必须解析为java.util.Date
,java.util.String (ISO8601格式)
,ISO8601时间长度(例如PT50M),或者null
。例如,可以使用在流程里前一个表单中输入的日期,或者由前一个服务任务计算出的日期。如果使用的是时间长度,则到期日期基于当前时间加上给定长度计算。例如当dueDate使用“PT30M”时,任务在从现在起30分钟后到期。
<userTask name="Important task" flowable:dueDate="${dateVariable}"/>
任务的到期日期也可以使用TaskService
,或者在TaskListener
中使用传递的DelegateTask
修改。
用户指派
用户任务可以直接指派(assign)给用户。可以定义humanPerformer子元素来实现。humanPerformer需要resourceAssignmentExpression来实际定义用户。目前,只支持formalExpressions。
<process >
...
<userTask id='theTask' name='important task' >
<humanPerformer>
<resourceAssignmentExpression>
<formalExpression>kermit</formalExpression>
</resourceAssignmentExpression>
</humanPerformer>
</userTask>
只能指定一个用户作为任务的humanPerformer。在Flowable术语中,这个用户被称作办理人(assignee)。拥有办理人的任务,在其他人的任务列表中不可见,而只能在该办理人的个人任务列表中看到。
可以通过TaskService获取特定用户办理的任务:
List<Task> tasks = taskService.createTaskQuery().taskAssignee("kermit").list();
任务也可以放在用户的候选任务列表中。在这个情况下,需要使用potentialOwner(潜在用户)结构。用法与humanPerformer结构类似。请注意需要指定表达式中的每一个元素为用户还是组(引擎无法自行判断)。
<process >
...
<userTask id='theTask' name='important task' >
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>user(kermit), group(management)</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
可用如下方法获取定义了potentialOwner结构的任务:
List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("kermit");
将获取所有kermit作为候选用户的任务,也就是说,表达式含有user(kermit)的任务。同时也将获取所有指派给kermit为其成员的组的任务(例如,kermit时management组的成员,且任务指派给management组)。组在运行时解析,并可通过身份服务管理。
如果并未指定给定字符串是用户还是组,引擎默认其为组。下列代码与声明group(accountancy)效果一样。
<formalExpression>accountancy</formalExpression>
用于任务指派的Flowable扩展
很明显,当指派关系不复杂时,这种用户与组的指派方式十分笨重。为避免这种复杂性,可以在用户任务上使用自定义扩展。
assignee(办理人)属性:这个自定义扩展用于直接将用户指派至用户任务。
<userTask name="my task" flowable:assignee="kermit" />
与上面定义的humanPerformer结构效果完全相同。
candidateUsers(候选用户)属性:这个自定义扩展用于为任务指定候选用户。
<userTask name="my task" flowable:candidateUsers="kermit, gonzo" />
与使用上面定义的potentialOwner结构效果完全相同。请注意不需要像在potentialOwner中一样,使用user(kermit)的声明,因为这个属性只能用于用户。
candidateGroups(候选组)attribute:这个自定义扩展用于为任务指定候选组。
<userTask name="my task" flowable:candidateGroups="management, accountancy" />
与使用上面定义的potentialOwner结构效果完全相同。请注意不需要像在potentialOwner中一样,使用group(management)的声明,因为这个属性只能用于组。
可以定义在一个用户任务上同时定义candidateUsers与candidateGroups。
请注意:尽管Flowable提供了IdentityService身份管理组件,但并不会检查给定的用户是否实际存在。这是为了便于将Flowable嵌入应用时,与已有的身份管理解决方案进行集成。
自定义身份关联类型
在用户指派中已经介绍过,BPMN标准支持单个指派用户即hunamPerformer,或者由一组用户构成potentialOwners潜在用户池。另外,Flowable为用户任务定义了扩展属性元素,用于代表任务的办理人或者候选用户。
Flowable支持的身份关联(identity link)类型有:
public class IdentityLinkType {
/* Flowable内置角色 */
public static final String ASSIGNEE = "assignee";
public static final String CANDIDATE = "candidate";
public static final String OWNER = "owner";
public static final String STARTER = "starter";
public static final String PARTICIPANT = "participant";
}
BPMN标准及Flowable示例中,身份认证是用户与组。在前一章节提到过,Flowable的身份管理实现并不适用于生产环境,而需要在支持的认证概要下自行扩展。
如果需要添加额外的关联类型,可按照下列语法,使用自定义资源作为扩展元素:
<userTask name="make profit">
<extensionElements>
<flowable:customResource flowable:name="businessAdministrator">
<resourceAssignmentExpression>
<formalExpression>user(kermit), group(management)</formalExpression>
</resourceAssignmentExpression>
</flowable:customResource>
</extensionElements>
</userTask>
自定义关联表达式添加至TaskDefinition类:
protected Map<String, Set<Expression>> customUserIdentityLinkExpressions =
new HashMap<String, Set<Expression>>();
protected Map<String, Set<Expression>> customGroupIdentityLinkExpressions =
new HashMap<String, Set<Expression>>();
public Map<String, Set<Expression>> getCustomUserIdentityLinkExpressions() {
return customUserIdentityLinkExpressions;
}
public void addCustomUserIdentityLinkExpression(
String identityLinkType, Set<Expression> idList) {
customUserIdentityLinkExpressions.put(identityLinkType, idList);
}
public Map<String, Set<Expression>> getCustomGroupIdentityLinkExpressions() {
return customGroupIdentityLinkExpressions;
}
public void addCustomGroupIdentityLinkExpression(
String identityLinkType, Set<Expression> idList) {
customGroupIdentityLinkExpressions.put(identityLinkType, idList);
}
这些方法将在运行时,由UserTaskActivityBehavior的handleAssignments方法调用。
最后,需要扩展IdentityLinkType类,以支持自定义身份关联类型:
package com.yourco.engine.task;
public class IdentityLinkType extends org.flowable.engine.task.IdentityLinkType {
public static final String ADMINISTRATOR = "administrator";
public static final String EXCLUDED_OWNER = "excludedOwner";
}
通过任务监听器自定义指派
如果上面的方式仍不能满足要求,可以在创建事件(create event)上使用任务监听器,调用自定义指派逻辑:
<userTask name="My task" >
<extensionElements>
<flowable:taskListener event="create" class="org.flowable.MyAssignmentHandler" />
</extensionElements>
</userTask>
传递至TaskListener
的DelegateTask
,可用于设置办理人与候选用户/组:
public class MyAssignmentHandler implements TaskListener {
public void notify(DelegateTask delegateTask) {
// 在这里执行自定义身份查询
// 然后调用如下命令:
delegateTask.setAssignee("kermit");
delegateTask.addCandidateUser("fozzie");
delegateTask.addCandidateGroup("management");
...
}
}
当使用Spring时,可以按上面章节的介绍使用自定义指派属性,并交由使用任务监听器、带有表达式的Spring bean,监听任务创建事件。在下面的例子中,通过调用ldapService
Spring bean的findManagerOfEmployee
方法设置办理人。传递的emp参数是一个流程变量。
<userTask name="My Task" flowable:assignee="${ldapService.findManagerForEmployee(emp)}"/>
也可以用于候选用户与组:
<userTask name="My Task" flowable:candidateUsers="${ldapService.findAllSales()}"/>
请注意调用方法的返回类型必须是String
或Collection<String>
(候选用户或组):
public class FakeLdapService {
public String findManagerForEmployee(String employee) {
return "Kermit The Frog";
}
public List<String> findAllSales() {
return Arrays.asList("kermit", "gonzo", "fozzie");
}
}
8.5.2. 脚本任务
描述
脚本任务(script task)是自动执行的活动。当流程执行到达脚本任务时,会执行相应的脚本。
图示
脚本任务用左上角有一个小“脚本”图标的标准BPMN 2.0任务(圆角矩形)表示。
XML表示
脚本任务使用script与scriptFormat元素定义。
<scriptTask name="Execute script" scriptFormat="groovy">
<script>
sum = 0
for ( i in inputArray ) {
sum += i
}
</script>
</scriptTask>
scriptFormat属性的值,必须是兼容JSR-223(Java平台脚本)的名字。默认情况下,JavaScript包含在每一个JDK中,因此不需要添加任何JAR文件。如果想使用其它(兼容JSR-223的)脚本引擎,则需要在classpath中添加相应的jar,并使用适当的名字。例如,Flowable单元测试经常使用Groovy,因为其语法与Java十分相似。
请注意Groovy脚本引擎与groovy-all JAR捆绑在一起。在Groovy 2.0版本以前,脚本引擎是Groovy JAR的一部分。因此,必须添加如下依赖:
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.x.x<version>
</dependency>
脚本中的变量
到达脚本引擎的执行中,所有的流程变量都可以在脚本中使用。在这个例子里,脚本变量'inputArray'实际上就是一个流程变量(一个integer的数组)。
<script>
sum = 0
for ( i in inputArray ) {
sum += i
}
</script>
也可以简单地调用execution.setVariable("variableName", variableValue),在脚本中设置流程变量。默认情况下,变量不会自动储存(请注意,在一些早期版本中是会储存的!)。可以将scriptTask
的autoStoreVariables
参数设置为true
,以自动保存任何在脚本中定义的变量(例如上例中的sum)。然而这并不是最佳实践。最佳实践是显式调用execution.setVariable(),因为在JDK近期的一些版本中,某些脚本语言不能自动保存变量。查看这个链接了解更多信息。
<scriptTask scriptFormat="JavaScript" flowable:autoStoreVariables="false">
这个参数的默认值为false
。也就是说如果在脚本任务定义中忽略这个参数,则脚本声明的所有变量将只在脚本执行期间有效。
在脚本中设置变量的例子:
<script>
def scriptVar = "test123"
execution.setVariable("myVar", scriptVar)
</script>
请注意:下列名字是保留字,不能用于变量名:out,out:print,lang:import,context,elcontext。
脚本任务的结果
脚本任务的返回值,可以通过为脚本任务定义的'flowable:resultVariable'属性设置为流程变量。可以是已经存在的,或者新的流程变量。如果指定为已存在的流程变量,则流程变量的值会被脚本执行的结果值覆盖。如果不指定结果变量名,则脚本结果值将被忽略。
<scriptTask name="Execute script" scriptFormat="juel" flowable:resultVariable="myVar">
<script>#{echo}</script>
</scriptTask>
在上面的例子中,脚本执行的结果(解析表达式'#{echo}'的值),将在脚本完成后,设置为名为'myVar'的流程变量。
安全性
当使用javascript作为脚本语言时,可以使用“安全脚本(secure scripting)”。参见安全脚本章节。
8.5.3. Java服务任务
描述
Java服务任务(Java service task)用于调用Java类。
图示
服务任务用左上角有一个小齿轮图标的圆角矩形表示。
XML表示
有四种方法声明如何调用Java逻辑:
指定实现了JavaDelegate或ActivityBehavior的类
调用解析为委托对象(delegation object)的表达式
调用方法表达式(method expression)
对值表达式(value expression)求值
使用flowable:class属性提供全限定类名(fully qualified classname),指定流程执行时调用的类。
<serviceTask
name="My Java Service Task"
flowable:class="org.flowable.MyJavaDelegate" />
查看实现章节,了解使用这种类的更多信息。
也可以使用解析为对象的表达式。该对象必须遵循的规则,与使用flowable:class
创建的对象规则相同(查看更多)。
<serviceTask flowable:delegateExpression="${delegateExpressionBean}" />
delegateExpressionBean
是一个实现了JavaDelegate
接口的bean,定义在Spring容器中。
使用flowable:expression属性指定需要计算的UEL方法表达式。
<serviceTask
name="My Java Service Task"
flowable:expression="#{printer.printMessage()}" />
将在名为printer
的对象上调用printMessage
方法(不带参数)。
也可以为表达式中使用的方法传递变量。
<serviceTask
name="My Java Service Task"
flowable:expression="#{printer.printMessage(execution, myVar)}" />
将在名为printer
的对象上调用printMessage
方法。传递的第一个参数为DelegateExecution
,名为execution
,在表达式上下文中默认可用。传递的第二个参数,是当前执行中,名为myVar
变量的值。
可以使用flowable:expression属性指定需要计算的UEL值表达式。
<serviceTask
name="My Java Service Task"
flowable:expression="#{split.ready}" />
会调用名为split
的bean的ready
参数的getter方法,getReady
(不带参数)。该对象会被解析为执行的流程变量或(如果可用的话)Spring上下文中的bean。
实现
要实现可以在流程执行中调用的类,需要实现org.flowable.engine.delegate.JavaDelegate接口,并在execute方法中提供所需逻辑。当流程执行到达该活动时,会执行方法中定义的逻辑,并按照BPMN 2.0的默认方法离开活动。
下面是一个Java类的示例,用于将流程变量String改为大写。这个类需要实现org.flowable.engine.delegate.JavaDelegate接口,因此需要实现execute(DelegateExecution)方法。这个方法就是引擎将调用的方法,需要实现业务逻辑。可以通过DelegateExecution接口(点击链接获取该接口操作的详细Javadoc)访问流程实例信息,如流程变量等。
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)。这意味着该类不能有任何成员变量,并需要是线程安全的,因为它可能会在不同线程中同时执行。这也影响了字段注入的使用方法。(译者注:原文可能较老,不正确。5.21中,flowable:class指定的类,会在流程实例启动时,为每个活动分别进行实例化。不过,当该活动在流程中重复执行,或者为多实例时,使用的都会是同一个类实例。)
在流程定义中引用(如flowable:class
)的类,不会在部署时实例化。只有当流程执行第一次到达该类使用的地方时,才会创建该类的实例。如果找不到这个类,会抛出FlowableException
。这是因为部署时的环境(更准确的说classpath),与实际运行的环境经常不一样。例如当使用ant或者Flowable应用中业务存档上传的方式部署的流程,其classpath中不会自动包含流程引用的类。
[内部:非公开实现类]也可以使用实现了org.flowable.engine.impl.delegate.ActivityBehavior接口的类。该实现可以访问更强大的引擎功能,例如,可以影响流程的控制流程。请注意这并不是很好的实践,需要避免这么使用。建议只有在高级使用场景下,并且你确知在做什么的时候,才使用ActivityBehavior接口。
字段注入
可以为委托类的字段注入值。支持下列注入方式:
字符串常量
表达式
如果可以的话,会按照Java Bean命名约定(例如,firstName
成员使用setter setFirstName(…)
),通过委托类的公有setter方法,注入变量。如果该字段没有可用的setter,会直接设置该委托类的私有成员的值。有的环境中,SecurityManagers不允许修改私有字段,因此为想要注入的字段暴露一个公有setter方法,是更安全的做法。
不论在流程定义中声明的是什么类型的值,注入对象的setter/私有字段的类型,总是org.flowable.engine.delegate.Expression
。解析表达式后,可以被转型为合适的类型。
'flowable:class'属性支持字段注入。也可以在使用flowable:delegateExpression属性时,进行字段注入。然而考虑到线程安全,需要遵循特殊的规则(参见下一章节)。
下面的代码片段展示了如何为类中声明的字段注入常量值。请注意按照BPMN 2.0 XML概要的要求,在实际字段注入声明前,需要先声明’extensionElements’XML元素。
<serviceTask
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
类型。当调用text.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
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)Spring定义的Bean。在服务任务实现中提到过,当服务任务中使用flowable:class属性时,该Java类的实例在所有流程实例中共享。要动态地为字段注入值,可以在org.flowable.engine.delegate.Expression
中注入值或方法表达式,它们会通过execute
方法传递的DelegateExecution
计算/调用。
下面的示例类使用了注入的表达式,并使用当前的DelegateExecution
解析它们。调用genderBean方法时传递的是gender变量。完整的代码与测试可以在org.flowable.examples.bpmn.servicetask.JavaServiceTaskTest.testExpressionFieldInjection
中找到
<serviceTask 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}" />
字段注入与线程安全
通常情况下,在服务任务中使用Java委托与字段注入是线程安全的。然而,有些情况下不能保证线程安全。这取决于设置,或Flowable运行的环境。
当使用flowable:class属性时,使用字段注入总是线程安全的(译者注:仍不完全安全,如对于多实例服务任务,使用的是同一个实例)。对于引用了某个类的每一个服务任务,都会实例化新的实例,并且在创建实例时注入一次字段。在不同的任务或流程定义中多次使用同一个类没有问题。
当使用flowable:expression属性时,不能使用字段注入。只能通过方法调用传递变量。总是线程安全的。
当使用flowable:delegateExpression属性时,委托实例的线程安全性,取决于表达式解析的方式。如果该委托表达式在多个任务或流程定义中重复使用,并且表达式总是返回相同的示例,则字段注入不是线程安全的。让我们看几个例子。
假设表达式为${factory.createDelegate(someVariable)},其中factory为引擎可用的Java bean(例如使用Spring集成时的Spring bean),并在每次表达式解析时创建新的实例。这种情况下,使用字段注入时,没有线程安全性问题:每次表达式解析时,都会注入新实例的字段。
然而,如果表达式为${someJavaDelegateBean},解析为JavaDelegate的实现,并且在创建单例的环境(如Spring)中运行。当在不同的任务或流程定义中使用这个表达式时,表达式总会解析为相同的实例。这种情况下,使用字段注入不是线程安全的。例如:
<serviceTask flowable:delegateExpression="${someJavaDelegateBean}">
<extensionElements>
<flowable:field name="someField" expression="${input * 2}"/>
</extensionElements>
</serviceTask>
<!-- 其它流程定义元素 -->
<serviceTask flowable:delegateExpression="${someJavaDelegateBean}">
<extensionElements>
<flowable:field name="someField" expression="${input * 2000}"/>
</extensionElements>
</serviceTask>
这段示例代码有两个服务任务,使用同一个委托表达式,但是expression字段填写不同的值。如果该表达式解析为相同的实例,就会在并发场景下,注入someField字段时出现竞争条件。
最简单的解决方法是:
使用表达式代替直接使用Java委托,并将所需数据通过方法参数传递给委托。
或者,在每次委托表达式解析时,返回委托类的新实例。这意味着这个bean的scope必须是prototype(原型)(例如在委托类上加上@Scope(SCOPE_PROTOTYPE)注解)。
在Flowable 5.22版本中,可以通过配置流程引擎配置,禁用在委托表达式上使用字段注入。需要设置delegateExpressionFieldInjectionMode参数(取org.flowable.engine.imp.cfg.DelegateExpressionFieldInjectionMode枚举中的值)。
可使用下列选项:
DISABLED(禁用):当使用委托表达式时,完全禁用字段注入。不会再尝试进行字段注入。这是最安全的方式,保证线程安全。
COMPATIBILITY(兼容):在这个模式下,行为与V5.21之前完全一样:可以在委托表达式中使用字段注入,如果委托类中没有定义该字段,会抛出异常。这是最不线程安全的模式,但可以保证历史版本兼容性,也可以在委托表达式只在一个任务中使用的时候(因此不会产生并发竞争条件),安全使用。
MIXED(混合):可以在使用委托表达式时注入,但当委托中没有定义字段时,不会抛出异常。这样可以在部分委托(比如不是单例的实例)中使用注入,而在部分委托中不使用注入。
Flowable 5.x版本的默认模式为COMPATIBILITY。
Flowable 6.x版本的默认模式为MIXED。
例如,假设使用MIXED模式,并使用Spring集成,在Spring配置中定义了如下bean:
<bean
class="org.flowable.spring.test.fieldinjection.SingletonDelegateExpressionBean" />
<bean
class="org.flowable.spring.test.fieldinjection.PrototypeDelegateExpressionBean"
scope="prototype" />
第一个bean是一般的Spring bean,因此是单例的。第二个bean的scope为prototype,因此每次请求这个bean时,Spring容器都会返回一个新实例。
在以下流程定义中:
<serviceTask 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 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 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 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的值为2。这是因为每次解析${prototypeDelegateExpressionBean}时,都会创建新实例。可以看到三个Expression成员字段的注入没有任何问题。
单例bean则有一点区别:
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);
}
}
在对于单例bean,INSTANCE_COUNT总是1。在这个委托中,没有Expression成员字段(使用MIXED模式)。而在COMPATIBILITY模式下,就会抛出异常,因为需要有成员字段。这个bean也可以使用DISABLED模式,但会禁用上面进行了字段注入的原型bean。
在委托的代码里,使用了org.flowable.engine.delegate.DelegateHelper。它提供了一些有用的工具方法,用于执行相同的逻辑,并且在单例中是线程安全的。与注入Expression不同,它通过getFieldExpression读取。这意味着在服务任务的XML里,字段定义与单例bean完全相同。查看上面的XML代码,可以看到定义是相同的,只是实现逻辑不同。
技术提示:getFieldExpression直接读取BpmnModel,并在方法执行时创建表达式,因此是线程安全的。
在Flowable V5.x版本中,(由于架构缺陷)不能在ExecutionListener或TaskListener中使用DelegateHelper。要保证监听器的线程安全,仍需使用表达式,或确保每次解析委托表达式时,都创建新实例。
在Flowable V6.x版本中,在ExecutionListener或TaskListener中可以使用DelegateHelper。例如在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(), result);
}
}
服务任务的结果
服务执行的返回值(仅对使用表达式的服务任务),可以通过为服务任务定义的'flowable:resultVariable'属性设置为流程变量。可以是已经存在的,或者新的流程变量。 如果指定为已存在的流程变量,则流程变量的值会被服务执行的结果值覆盖。 如果使用'flowable:useLocalScopeForResultVariable',则会将结果值设置为局部变量。 如果不指定结果变量名,则服务任务的结果值将被忽略。
<serviceTask
flowable:expression="#{myService.doSomething()}"
flowable:resultVariable="myVar" />
在上例中,服务执行的结果(流程变量或Spring bean中,使用'myService'名字所获取的对象,调用'doSomething()'方法的返回值),在服务执行完成后,会设置为名为'myVar'的流程变量。
可触发
一种常见的模式是发送JMS消息或HTTP调用触发外部服务,然后流程实例进入等待状态。之后外部系统会回复响应,流程实例继续执行下一个活动。在默认的BPMN中,需要使用服务任务和接收任务(receive task)。但是这样会引入竞争条件:外部服务的响应可能会早于流程实例持久化及接收任务激活。为了解决这个问题,Flowable为服务任务增加了triggerable(可触发)属性,可以将服务任务转变为执行服务逻辑,并在继续执行之前等待外部触发的任务。如果在可触发服务任务上同时设置异步(async 为 true),则流程实例会先持久化,然后在异步作业中执行服务任务逻辑。在BPMN XML中,可以这样实现可触发服务任务:
<serviceTask
flowable:expression="#{myService.doSomething()}"
flowable:triggerable="true"
flowable:async="true" />
外部服务可以同步或异步地触发等待中的流程实例。为了避免乐观锁异常,最好使用异步触发。默认情况下,异步作业是排他的,也就是说流程实例会被锁定,以保证流程实例中的其他活动不会影响到触发器的逻辑。可以使用RuntimeService的triggerAsync方法,异步触发等待中的流程实例。当然还是可以使用RuntimeService的trigger方法,同步触发。
处理异常
当执行自定义逻辑时,通常需要捕获并在流程中处理特定的业务异常。Flowable提供了多种选择。
抛出BPMN错误
可以在服务任务或脚本任务的用户代码中抛出BPMN错误。可以在Java委托、脚本、表达式与委托表达式中,抛出特殊的FlowableException:BpmnError。引擎会捕获这个异常,并将其转发至合适的错误处理器,如错误边界事件或错误事件子流程。
public class ThrowBpmnErrorDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) throws Exception {
try {
executeBusinessLogic();
} catch (BusinessException e) {
throw new BpmnError("BusinessExceptionOccurred");
}
}
}
构造函数的参数是错误代码。错误代码决定了处理这个错误的错误处理器。参见错误边界事件了解如何捕获BPMN错误。
这个机制只应该用于业务错误,需要通过流程中定义的错误边界事件或错误事件子流程处理。技术错误应该通过其他异常类型表现,并且通常不在流程内部处理。
异常映射
也可以使用mapException
扩展,直接将Java异常映射至业务异常(错误)。单映射是最简单的形式:
<serviceTask name="Service Task" flowable:class="...">
<extensionElements>
<flowable:mapException
errorCode="myErrorCode1">org.flowable.SomeException</flowable:mapException>
</extensionElements>
</serviceTask>
在上面的代码中,如果服务任务抛出org.flowable.SomeException
的实例,引擎会捕获该异常,并将其转换为带有给定errorCode的BPMN错误。然后就可以像普通BPMN错误完全一样地处理。其他的异常没有映射,仍将抛出至API调用处。
也可以在单行中使用includeChildExceptions
属性,映射特定异常的所有子异常。
<serviceTask name="Service Task" flowable:class="...">
<extensionElements>
<flowable:mapException errorCode="myErrorCode1"
includeChildExceptions="true">org.flowable.SomeException</flowable:mapException>
</extensionElements>
</serviceTask>
上面的代码中,Flowable会将SomeException
的任何直接或间接的子类,转换为带有指定错误代码的BPMN错误。 当未指定includeChildExceptions
时,视为“false”。
默认映射最泛用。默认映射是一个不指定类的映射,可以匹配任何Java异常:
<serviceTask name="Service Task" flowable:class="...">
<extensionElements>
<flowable:mapException errorCode="myErrorCode1"/>
</extensionElements>
</serviceTask>
除了默认映射,会按照从上至下的顺序检查映射,使用第一个匹配的映射。只在所有映射都不能成功匹配时使用默认映射。 只有第一个不指定类的映射会作为默认映射。默认映射忽略includeChildExceptions
。
异常顺序流
[内部:非公开实现类]
也可以选择在发生异常时,将流程执行路由至另一条路径。下面是一个例子。
<serviceTask
name="Java service invocation"
flowable:class="org.flowable.ThrowsExceptionBehavior">
</serviceTask>
<sequenceFlow sourceRef="javaService" targetRef="theEnd" />
<sequenceFlow sourceRef="javaService" targetRef="fixException" />
服务任务有两条出口顺序流,命名为exception
与no-exception
。在发生异常时,使用顺序流ID控制流程流向:
public class ThrowsExceptionBehavior implements ActivityBehavior {
public void execute(DelegateExecution execution) {
String var = (String) execution.getVariable("var");
String sequenceFlowToTake = null;
try {
executeLogic(var);
sequenceFlowToTake = "no-exception";
} catch (Exception e) {
sequenceFlowToTake = "exception";
}
DelegateHelper.leaveDelegate(execution, sequenceFlowToTake);
}
}
在JavaDelegate中使用Flowable服务
有的时候,需要在Java服务任务中使用Flowable服务(例如调用活动(call activity)不满足需要的场景下,使用RuntimeService启动流程实例)。
public class StartProcessInstanceTestDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) throws Exception {
RuntimeService runtimeService = Context.getProcessEngineConfiguration().getRuntimeService();
runtimeService.startProcessInstanceByKey("myProcess");
}
}
可以使用这个接口访问所有Flowable服务API。
使用这些API调用造成的所有数据变更都在当前事务中。在依赖注入的环境(如Spring或CDI,无论是否使用启用JTA的数据源)下也可以使用这个接口。例如,下面的代码片段与上面的代码具有相同功能,但通过注入而不是org.flowable.engine.EngineServices接口获得RuntimeService。
@Component("startProcessInstanceDelegate")
public class StartProcessInstanceTestDelegateWithInjection {
@Autowired
private RuntimeService runtimeService;
public void startProcess() {
runtimeService.startProcessInstanceByKey("oneTaskProcess");
}
}
重要技术提示:由于服务调用是在当前事务中完成的,因此在服务任务执行前产生或修改的数据尚未存入数据库。所有API调用都基于数据库数据处理,这意味着这些未提交的修改在服务任务的API调用中“不可见”。
8.5.4. Web服务任务
描述
Web服务任务(Web service task)用于同步地调用外部的Web服务。
图示
Web服务任务与Java服务任务图标一样。
XML表示
使用Web服务之前,需要导入其操作及复杂的类型。可以使用导入标签(import tag)指向Web服务的WSDL,自动处理:
<import importType="http://schemas.xmlsoap.org/wsdl/"
location="http://localhost:63081/counter?wsdl"
namespace="http://webservice.flowable.org/" />
按照上面的声明,Flowable会导入定义,但不会创建条目定义(item definition)与消息。如果需要调用一个名为’prettyPrint’的方法,则需要先为请求及回复消息创建对应的消息与条目定义:
<message itemRef="tns:prettyPrintCountRequestItem" />
<message itemRef="tns:prettyPrintCountResponseItem" />
<itemDefinition structureRef="counter:prettyPrintCount" />
<itemDefinition structureRef="counter:prettyPrintCountResponse" />
在声明服务任务前,需要定义实际引用Web服务的BPMN接口与操作。基本上,是定义“接口”与所需的“操作”。对每一个操作都可以重复使用之前定义的“传入”与“传出”消息。例如,下面的声明定义了“counter”接口及“prettyPrintCountOperation”操作:
<interface name="Counter Interface" implementationRef="counter:Counter">
<operation name="prettyPrintCount Operation"
implementationRef="counter:prettyPrintCount">
<inMessageRef>tns:prettyPrintCountRequestMessage</inMessageRef>
<outMessageRef>tns:prettyPrintCountResponseMessage</outMessageRef>
</operation>
</interface>
这样就可以使用##WebService实现,声明Web服务任务,并引用Web服务操作。
<serviceTask
name="Web service invocation"
implementation="##WebService"
operationRef="tns:prettyPrintCountOperation">
Web服务任务IO规范
除非使用简化方法处理输入与输出数据关联(见下),否则需要为每个Web服务任务声明IO规范,指出任务的输入与输出是什么。这个方法很简单,也兼容BPMN 2.0。在prettyPrint例子中,根据之前声明的条目定义,定义输入与输出:
<ioSpecification>
<dataInput itemSubjectRef="tns:prettyPrintCountRequestItem" />
<dataOutput itemSubjectRef="tns:prettyPrintCountResponseItem" />
<inputSet>
<dataInputRefs>dataInputOfServiceTask</dataInputRefs>
</inputSet>
<outputSet>
<dataOutputRefs>dataOutputOfServiceTask</dataOutputRefs>
</outputSet>
</ioSpecification>
Web服务任务数据输入关联
有两种指定数据输入关联的方式:
使用表达式
使用简化方法
使用表达式指定数据输入关联,需要定义源及目标条目,并指定每个条目与字段的关联。下面的例子中,我们针对每个条目,指定prefix与suffix字段:
<dataInputAssociation>
<sourceRef>dataInputOfProcess</sourceRef>
<targetRef>dataInputOfServiceTask</targetRef>
<assignment>
<from>${dataInputOfProcess.prefix}</from>
<to>${dataInputOfServiceTask.prefix}</to>
</assignment>
<assignment>
<from>${dataInputOfProcess.suffix}</from>
<to>${dataInputOfServiceTask.suffix}</to>
</assignment>
</dataInputAssociation>
也可以使用更简单明了的简化方法。'sourceRef’元素是一个Flowable变量名,'targetRef’是条目定义的参数。在下面的例子里,将’PrefixVariable’变量的值关联至’prefix’字段,并将’SuffixVariable’变量的值关联至’suffix’字段。
<dataInputAssociation>
<sourceRef>PrefixVariable</sourceRef>
<targetRef>prefix</targetRef>
</dataInputAssociation>
<dataInputAssociation>
<sourceRef>SuffixVariable</sourceRef>
<targetRef>suffix</targetRef>
</dataInputAssociation>
Web服务任务数据输出关联
有两种指定数据输出关联的方式:
使用表达式
使用简化方法
使用表达式指定数据输出关联,需要定义目标变量及源表达式。这种方法很简单,与数据输入关联类似:
<dataOutputAssociation>
<targetRef>dataOutputOfProcess</targetRef>
<transformation>${dataOutputOfServiceTask.prettyPrint}</transformation>
</dataOutputAssociation>
也可以使用更简单明了的简化方法。'sourceRef’是条目定义的参数,'targetRef’元素是Flowable变量名。这种方法很简单,与数据输入关联类似:
<dataOutputAssociation>
<sourceRef>prettyPrint</sourceRef>
<targetRef>OutputVariable</targetRef>
</dataOutputAssociation>
8.5.5. 业务规则任务
描述
业务规则任务(business rule task)用于同步地执行一条或多条规则。Flowable使用名为Drools Expert的Drools规则引擎执行业务规则。目前,业务规则中包含的.drl文件,必须与定义了业务规则服务并执行规则的流程定义一起部署。这意味着流程中使用的所有.drl文件都需要打包在流程BAR文件中,与任务表单等类似。要了解如何为Drools Expert创建业务规则,请访问位于JBoss Drools的Drools文档。
如果想要使用自己的规则任务实现,比如希望通过不同方法使用Drools,或者想使用完全不同的规则引擎,则可以使用BusinessRuleTask的class或expression属性。这样它会与服务任务的行为完全相同。
图示
业务规则任务显示为带有表格图标的圆角矩形。
XML表示
要执行与流程定义在同一个BAR文件中部署的一条或多条业务规则,需要定义输入与结果变量。输入变量可以用流程变量的列表定义,使用逗号分隔。输出变量只能有一个变量名,将执行业务规则后的输出对象存储至流程变量。请注意结果变量会包含对象的List。如果没有指定结果变量名,默认为org.flowable.engine.rules.OUTPUT。
下面的业务规则任务,执行与流程定义一起部署的所有业务规则:
<process>
<startEvent />
<sequenceFlow sourceRef="theStart" targetRef="businessRuleTask" />
<businessRuleTask flowable:ruleVariablesInput="${order}"
flowable:resultVariable="rulesOutput" />
<sequenceFlow sourceRef="businessRuleTask" targetRef="theEnd" />
<endEvent />
</process>
也可以将业务规则任务配置为只执行部署的.drl文件中的一组规则。要做到这一点,需要指定规则名字的列表,用逗号分隔。
<businessRuleTask flowable:ruleVariablesInput="${order}"
flowable:rules="rule1, rule2" />
这样只会执行rule1与rule2。
也可以定义需要从执行中排除的规则列表。
<businessRuleTask flowable:ruleVariablesInput="${order}"
flowable:rules="rule1, rule2" exclude="true" />
这个例子中,除了rule1与rule2之外,其它所有与流程定义一起部署在同一个BAR文件中的规则都会被执行。
前面提到过,还可以自行指定BusinessRuleTask的实现:
<businessRuleTask flowable:class="${MyRuleServiceDelegate}" />
这样配置的业务规则任务与服务任务的行为完全一样,但仍保持业务规则任务的图标,代表在这里处理业务规则。
8.5.6. 邮件任务
Flowable让你可以通过自动的邮件服务任务(email task),增强业务流程。可以向一个或多个收信人发送邮件,支持cc,bcc,HTML文本,等等。请注意邮件任务不是BPMN 2.0规范的“官方”任务(所以也没有专用图标)。因此,在Flowable中,邮件任务实现为一种特殊的服务任务。
配置邮件服务器
Flowable引擎使用支持SMTP的外部邮件服务器发送邮件。为了发送邮件,引擎需要了解如何连接邮件服务器。可以在flowable.cfg.xml配置文件中设置下面的参数:
参数 | 必填? | 描述 |
---|---|---|
mailServerHost | 否 | 邮件服务器的主机名(如mail.mycorp.com)。默认为localhost |
mailServerPort | 是,如果不使用默认端口 | 邮件服务器的SMTP端口。默认值为25 |
mailServerDefaultFrom | 否 | 若用户没有提供地址,默认使用的邮件发件人地址。默认为flowable@flowable.org |
mailServerUsername | 若服务器需要 | 部分邮件服务器发信时需要进行认证。默认为空。 |
mailServerPassword | 若服务器需要 | 部分邮件服务器发信时需要进行认证。默认为空。 |
mailServerUseSSL | 若服务器需要 | 部分邮件服务器要求ssl通信。默认设置为false。 |
mailServerUseTLS | 若服务器需要 | 部分邮件服务器要求TLS通信(例如gmail)。默认设置为false。 |
定义邮件任务
邮件任务实现为特殊的服务任务,将服务任务的type定义为'mail'进行设置。
<serviceTask flowable:type="mail">
邮件任务通过字段注入配置。这些参数的值可以使用EL表达式,并将在流程执行运行时解析。可以设置下列参数:
参数 | 必填? | 描述 |
---|---|---|
to | 是 | 邮件的收信人。可以使用逗号分隔的列表定义多个接收人 |
from | 否 | 邮件的发信人地址。如果不设置,会使用默认配置的地址 |
subject | 否 | 邮件的主题 |
cc | 否 | 邮件的抄送人。可以使用逗号分隔的列表定义多个接收人 |
bcc | 否 | 邮件的密送人。可以使用逗号分隔的列表定义多个接收人 |
charset | 否 | 可以指定邮件的字符集,对许多非英语语言很必要。 |
html | 否 | 邮件的HTML文本 |
text | 否 | 邮件的内容,用于纯文本邮件。对于不支持富文本内容的客户端,可以与html一起使用。邮件客户端可以回退为显式纯文本格式。 |
htmlVar | 否 | 存储邮件HTML内容的流程变量名。与html参数的最大区别,是这个参数会在邮件任务发送前,使用其内容进行表达式替换。 |
textVar | 否 | 存储邮件纯文本内容的流程变量名。与text参数的最大区别,是这个参数会在邮件任务发送前,使用其内容进行表达式替换。 |
ignoreException | 否 | 处理邮件失败时,是忽略还是抛出FlowableException。默认设置为false。 |
exceptionVariableName | 否 | 如果设置ignoreException = true,而处理邮件失败时,则使用给定名字的变量保存失败信息 |
示例 usage
下面的XML代码片段是使用邮件任务的示例。
<serviceTask flowable:type="mail">
<extensionElements>
<flowable:field name="from" stringValue="order-shipping@thecompany.com" />
<flowable:field name="to" expression="${recipient}" />
<flowable:field name="subject" expression="Your order ${orderId} has been shipped" />
<flowable:field name="html">
<flowable:expression>
<![CDATA[
<html>
<body>
Hello ${male ? 'Mr.' : 'Mrs.' } ${recipientName},<br/><br/>
As of ${now}, your order has been <b>processed and shipped</b>.<br/><br/>
Kind regards,<br/>
TheCompany.
</body>
</html>
]]>
</flowable:expression>
</flowable:field>
</extensionElements>
</serviceTask>
8.5.7. Http任务
Http任务(Http task)用于发出HTTP请求,增强了Flowable的集成能力。请注意Http任务不是BPMN 2.0规范的“官方”任务(所以也没有专用图标)。因此,在Flowable中,Http任务实现为一种特殊的服务任务。
配置Http客户端
Flowable使用可配置的Http客户端发出Http请求。如果不进行设置,会使用默认配置。
示例配置:
<bean
class="org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<!-- http客户端配置 -->
<property name="httpClientConfig" ref="httpClientConfig"/>
</bean>
<bean class="org.flowable.engine.cfg.HttpClientConfig">
<property name="connectTimeout" value="5000"/>
<property name="socketTimeout" value="5000"/>
<property name="connectionRequestTimeout" value="5000"/>
<property name="requestRetryLimit" value="5"/>
</bean>
参数 | 必填? | 描述 |
---|---|---|
connectTimeout | 否 | 连接超时时间,以毫秒计。 默认值 5000。 |
socketTimeout | 否 | Socket超时时间,以毫秒计。 默认值 5000。 |
connectionRequestTimeout | 否 | 请求连接超时时间。以毫秒计 默认值 5000。 |
requestRetryLimit | 否 | 请求重试次数(“0”代表不重试) 默认值 3。 |
disableCertVerify | 否 | 禁用SSL证书验证。 默认值 false。 |
定义Http任务
Http任务实现为特殊的服务任务,将服务任务的type定义为'http'进行设置。
<serviceTask flowable:type="http">
可以使用自定义的实现,覆盖Http任务的默认行为。 需要扩展org.flowable.http.HttpActivityBehavior,并覆盖perform()方法。
需要在任务定义中设置httpActivityBehaviorClass字段(默认值为 org.flowable.http.impl.HttpActivityBehaviorImpl)。
当前使用的默认实现HttpActivityBehaviorImpl基于Apache Http Client。尽管Apache Http Client可以使用很多方法自定义,但我们并没有在Http客户端配置中使用全部选项。
参考 Http Client builder 创建自定义客户端。
<serviceTask flowable:type="http"> <extensionElements> <flowable:field name="httpActivityBehaviorClass"> <flowable:string> <![CDATA[org.example.flowable.HttpActivityBehaviorCustomImpl]]> </flowable:string> </flowable:field> </extensionElements> </sericeTask>
配置Http任务
Http任务通过字段注入配置。所有参数都可以配置为EL表达式,在运行时进行解析。可以设置下列参数:
参数 | 必填? | 描述 |
---|---|---|
requestMethod | 是 | 请求方法 (GET,POST,PUT,DELETE)。 |
requestUrl | yes | 请求URL (例如 - http://flowable.org)。 |
requestHeaders | 否 | 行分隔的Http请求头。 例如 - Content-Type: application/json Authorization: Basic aGFRlc3Q= |
requestBody | 否 | 请求体 例如 - ${sampleBody} |
requestTimeout | 否 | 请求超时时间。单位为毫秒 (例如 - 5000)。 默认值为“0”,即没有超时。 链接相关的超时设置为配置Http客户端。 |
disallowRedirects | 否 | 是否禁用Http重定向。 默认为false。 (例如 - true)。 |
failStatusCodes | 否 | 逗号分隔的Http状态码,将令请求失败并抛出FlowableException。 例如:400, 404, 500, 503 例如:400, 5XX |
handleStatusCodes | 否 | 逗号分隔的Http状态码,将令任务抛出BpmnError,并可用错误边界事件捕获。 BpmnError的错误码为HTTP<statuscode>。 例如,404状态码会将错误码设置为HTTP404。 仅当disallowRedirects字段设置为true时,3XX状态码才会被抛出。若同一个状态码在handleStatusCodes及failStatusCodes中都有配置,则handleStatusCodes生效。 例如:400, 404, 500, 503 例如:3XX, 4XX, 5XX |
ignoreException | 否 | 是否忽略异常。异常将被捕获,并存储在名为<taskId>.errorMessage的变量中。 |
saveRequestVariables | 否 | 是否保存请求变量。 默认情况下,只会保存将响应相关的变量。 |
saveResponseParameters | 否 | 是否保存全部的响应变量,包括HTTP状态码,响应头等。 默认情况下,只会将响应体保存为变量。 |
resultVariablePrefix | 否 | 执行变量名的前缀。 如果不设置前缀,变量名为<taskId>.fieldName。 例如,对于id为task7的任务,其请求URL将保存为task7.requestUrl。 |
saveResponseParametersTransient | 否 | 若为true,则会将响应体变量(如果设置了保存响应头,状态码,也包括在内)设置为瞬时变量。 |
saveResponseVariableAsJson | 否 | 若为true,则响应体会保存为JSON变量,而非String。如果HTTP服务返回JSON,并且想使用点注记方式使用字段(如myResponse.user.name),这个配置就很有用。 |
httpActivityBehaviorClass | 否 | org.flowable.http.HttpActivityBehavior类的自定义扩展的全限定类名。 |
除了上面提到的字段,使用saveResponseParameters还会在执行成功后设置下列变量。
变量 | 可选? | 描述 |
---|---|---|
responseProtocol | 是 | Http版本。 |
responseReason | 是 | Http响应原因短语。 |
responseStatusCode | 是 | Http响应状态码(例如 - 200)。 |
responseHeaders | 是 | 行分隔的Http响应头。 例如 - Content-Type: application/json Content-Length: 777 |
responseBody | 是 | 字符串形式的响应体,若有。 |
errorMessage | 是 | 被忽略的异常信息,若有。 |
结果变量
请注意上述执行变量名都会使用resultVariablePrefix前缀。 例如,可以在其他活动中,使用task7.responseStatusCode获取响应状态码。 其中task7是服务任务的id。可以设置resultVariablePrefix覆盖这个行为。
示例
下面的XML片段是使用Http任务的例子。
<serviceTask flowable:type="http">
<extensionElements>
<flowable:field name="requestMethod" stringValue="GET" />
<flowable:field name="requestUrl" stringValue="http://flowable.org" />
<flowable:field name="requestHeaders">
<flowable:expression>
<![CDATA[
Accept: text/html
Cache-Control: no-cache
]]>
</flowable:expression>
</flowable:field>
<flowable:field name="requestTimeout">
<flowable:expression>
<![CDATA[
${requestTimeout}
]]>
</flowable:expression>
</flowable:field>
<flowable:field name="resultVariablePrefix">
<flowable:string>task7</flowable:string>
</flowable:field>
</extensionElements>
</serviceTask>
错误处理
默认情况下,当发生链接、IO或其他未处理的异常时,Http任务抛出FlowableException。 默认情况下,不会处理任何重定向/客户端/服务端错误状态码。
可以设置failStatusCodes及/或handleStatusCodes字段,配置Http任务处理异常及Http状态的方式。参见配置Http任务。
由handleStatusCodes抛出的BpmnError与其他BPMN异常一样,需要由对应的错误边界事件处理。 下面是一些Http任务错误处理及重试的例子。
400及5XX失败,异步执行,并按照failedJobRetryTimeCycle重试的Http任务
<serviceTask name="Fail test" flowable:async="true" flowable:type="http">
<extensionElements>
<flowable:field name="requestMethod">
<flowable:string><![CDATA[GET]]></flowable:string>
</flowable:field>
<flowable:field name="requestUrl">
<flowable:string><![CDATA[http://localhost:9798/api/fail]]></flowable:string>
</flowable:field>
<flowable:field name="failStatusCodes">
<flowable:string><![CDATA[400, 5XX]]></flowable:string>
</flowable:field>
<flowable:failedJobRetryTimeCycle>R3/PT5S</flowable:failedJobRetryTimeCycle>
</extensionElements>
</serviceTask>
将400处理为BmpnError
<serviceTask name="HTTP Task" flowable:type="http">
<extensionElements>
<flowable:field name="requestMethod">
<flowable:string><![CDATA[GET]]></flowable:string>
</flowable:field>
<flowable:field name="requestUrl">
<flowable:string><![CDATA[http://localhost:9798/api/fail]]></flowable:string>
</flowable:field>
<flowable:field name="handleStatusCodes">
<flowable:string><![CDATA[4XX]]></flowable:string>
</flowable:field>
</extensionElements>
</serviceTask>
<boundaryEvent attachedToRef="handleGet">
<errorEventDefinition errorRef="HTTP400"></errorEventDefinition>
</boundaryEvent>
忽略异常
<serviceTask name="Fail test" flowable:type="http">
<extensionElements>
<flowable:field name="requestMethod">
<flowable:string><![CDATA[GET]]></flowable:string>
</flowable:field>
<flowable:field name="requestUrl">
<flowable:string><![CDATA[http://nohost:9798/api]]></flowable:string>
</flowable:field>
<flowable:field name="ignoreException">
<flowable:string><![CDATA[true]]></flowable:string>
</flowable:field>
</extensionElements>
</serviceTask>
异常映射
参见异常映射
8.5.8. Mule任务
Mule任务可以向Mule发送消息,增强Flowable的集成特性。请注意Mule任务不是BPMN 2.0规范的“官方”任务(所以也没有专用图标)。因此,在Flowable中,Mule任务实现为一种特殊的服务任务。
定义Mule任务
Mule任务实现为特殊的服务任务,将服务任务的type定义为'mule'进行设置。
<serviceTask flowable:type="mule">
Mule任务通过字段注入配置。这些参数的值可以使用EL表达式,将在流程执行运行时解析。可以设置下列参数:
参数 | 必填? | 描述 |
---|---|---|
endpointUrl | 是 | 希望调用的Mule端点(endpoint)。 |
language | 是 | 计算payloadExpression字段所用的语言。 |
payloadExpression | 是 | 消息载荷的表达式。 |
resultVariable | 否 | 存储调用结果的变量名。 |
示例
下面的XML代码片段是使用Mule任务的例子。
<extensionElements>
<flowable:field name="endpointUrl">
<flowable:string>vm://in</flowable:string>
</flowable:field>
<flowable:field name="language">
<flowable:string>juel</flowable:string>
</flowable:field>
<flowable:field name="payloadExpression">
<flowable:string>"hi"</flowable:string>
</flowable:field>
<flowable:field name="resultVariable">
<flowable:string>theVariable</flowable:string>
</flowable:field>
</extensionElements>
8.5.9. Camel任务
Camel任务(Camel task)可以向Camel发送消息,增强Flowable的集成特性。请注意Camel任务不是BPMN 2.0规范的“官方”任务(所以也没有专用图标)。因此,在Flowable中,Camel任务实现为一种特殊的服务任务。还请注意,需要在项目中包含Flowable Camel模块才能使用Camel任务。
定义Camel任务
Camel任务实现为特殊的服务任务,将服务任务的type定义为'camel'进行设置。
<serviceTask flowable:type="camel">
只需要在流程定义的服务任务上定义Camel类型即可。集成逻辑都通过Camel容器委托。默认情况下Flowable引擎在Spring容器中查找camelContext Bean。camelContext Bean定义由Camel容器装载的Camel路由。在下面的例子中,按照指定的Java包装载路由。但也可以自行在Spring配置中直接定义路由。
<camelContext xmlns="http://camel.apache.org/schema/spring">
<packageScan>
<package>org.flowable.camel.route</package>
</packageScan>
</camelContext>
可以在Camel网站找到关于Camel路由的更多文档。下面只通过几个小例子展示基本概念。在第一个例子中,在Flowable工作流中进行最简单的Camel调用。叫做SimpleCamelCall。
如果想要定义多个Camel上下文Bean,或想使用不同的Bean名字,可以在Camel任务定义中像这样覆盖:
<serviceTask flowable:type="camel">
<extensionElements>
<flowable:field name="camelContext" stringValue="customCamelContext" />
</extensionElements>
</serviceTask>
简单Camel调用示例
这个例子相关的所有文件,都可以在flowable-camel模块的org.flowable.camel.examples.simpleCamelCall包中找到。目标是简单地启动一个Camel路由。首先需要一个配置了上面提到的路由的Spring上下文。下面的代码实现这个目的:
<camelContext xmlns="http://camel.apache.org/schema/spring">
<packageScan>
<package>org.flowable.camel.examples.simpleCamelCall</package>
</packageScan>
</camelContext>
public class SimpleCamelCallRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
from("flowable:SimpleCamelCallProcess:simpleCall").to("log:org.flowable.camel.examples.SimpleCamelCall");
}
}
路由只是记录消息体,不做更多事情。请注意from端点(endpoint)的格式,包含冒号分隔的三个部分:
端点URL部分 | 描述 |
---|---|
flowable | 指向引擎端点 |
SimpleCamelCallProcess | 流程名 |
simpleCall | 流程中Camel服务的名字 |
现在已经配置好路由,可以访问Camel。下面需要像这样定义工作流:
<process>
<startEvent/>
<sequenceFlow sourceRef="start" targetRef="simpleCall"/>
<serviceTask flowable:type="camel"/>
<sequenceFlow sourceRef="simpleCall" targetRef="end"/>
<endEvent/>
</process>
连通性测试示例
示例已经可以工作,但实际上Camel与Flowable之间并没有通信,因此没有太多价值。在这个例子里,将试着从Camel接收与发送消息。我们将发送一个字符串,Camel在其上附加一些文字并返回作为结果。发送部分比较普通,即以变量的格式将信息发送给Camel服务。这是我们的调用代码:
@Deployment
public void testPingPong() {
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("input", "Hello");
Map<String, String> outputMap = new HashMap<String, String>();
variables.put("outputMap", outputMap);
runtimeService.startProcessInstanceByKey("PingPongProcess", variables);
assertEquals(1, outputMap.size());
assertNotNull(outputMap.get("outputValue"));
assertEquals("Hello World", outputMap.get("outputValue"));
}
“input”变量是实际上是Camel路由的输入,而outputMap用于捕获Camel传回的结果。流程像是这样:
<process>
<startEvent/>
<sequenceFlow sourceRef="start" targetRef="ping"/>
<serviceTask flowable:type="camel"/>
<sequenceFlow sourceRef="ping" targetRef="saveOutput"/>
<serviceTask flowable:class="org.flowable.camel.examples.pingPong.SaveOutput" />
<sequenceFlow sourceRef="saveOutput" targetRef="end"/>
<endEvent/>
</process>
请注意SaveOutput服务任务会从上下文中取出“Output”变量,并存储至上面提到的OutputMap。现在需要了解变量如何发送至Camel,以及如何返回。这就需要了解Camel行为(Behavior)的概念。变量与Camel通信的方式可以通过CamelBehavior配置。在这个例子里使用默认配置,其它配置在后面会进行简短介绍。可以使用类似的代码配置期望的Camel行为:
<serviceTask flowable:type="camel">
<extensionElements>
<flowable:field name="camelBehaviorClass" stringValue="org.flowable.camel.impl.CamelBehaviorCamelBodyImpl" />
</extensionElements>
</serviceTask>
如果不指定行为,则会设置为org.flowable.camel.impl.CamelBehaviorDefaultImpl。这个行为会将变量复制到相同名字的Camel参数。无论选择什么行为,对于返回值:如果Camel消息体是一个map,则其中的每个元素都将复制为变量;否则整个对象将复制为名为"camelBody"的特定变量。以第二个例子作为Camel路由的总结:
@Override
public void configure() throws Exception {
from("flowable:PingPongProcess:ping").transform().simple("${property.input} World");
}
在这个路由中,字符串"world"会在结尾连接上名为“input”的参数,并将结果作为消息体返回。可以通过Java服务任务访问"camelBody"变量。也可以访问“outputMap”获取。除了这个例子中使用的默认行为之外,我们还可以看看其他的方式。在每个Camel路由开始时,流程实例ID会复制为名为"PROCESS_ID_PROPERTY"的Camel参数。之后会用于将流程实例与Camel路由相关联。也可以在Camel路由中直接使用。
Flowable提供了三种不同的行为。可以通过修改路由URL中特定的部分覆写行为。这里有个在URL中覆写已有行为的例子:
from("flowable:asyncCamelProcess:serviceTaskAsync2?copyVariablesToProperties=true").
下表展示了三种可用的Camel行为:
行为 | URL中 | 描述 |
---|---|---|
CamelBehaviorDefaultImpl | copyVariablesToProperties | 将Flowable变量复制为Camel参数。 |
CamelBehaviorCamelBodyImpl | copyCamelBodyToBody | 只将名为"camelBody"的Flowable变量复制为Camel消息体。 |
CamelBehaviorBodyAsMapImpl | copyVariablesToBodyAsMap | 将一个map中的所有Flowable变量复制为Camel消息体。 |
上表展示了Flowable变量如何传递给Camel。下表展示Camel变量如何返回至Flowable。需要在路由URL中进行配置。
URL | 描述 |
---|---|
Default | 如果Camel消息体是一个map,则将其中每一对象复制为Flowable变量;否则将整个Camel消息体复制为"camelBody" Flowable变量。 |
copyVariablesFromProperties | 将Camel参数以同名复制为Flowable变量。 |
copyCamelBodyToBodyAsString | 与default相同,但如果Camel消息体不是map,则首先将其转换为字符串,然后再复制为"camelBody"。 |
copyVariablesFromHeader | 额外地,将Camel头复制为Flowable的同名变量。 |
返回变量
上面提到的变量传递,不论是从Camel到Flowable还是反向,都只用于变量传递的起始侧。 要特别注意,由于Flowable的行为是非阻塞的,Flowable不会自动向Camel返回变量。 为此提供了特殊的语法。可以在Camel路由URL中,以var.return.someVariableName
的格式,指定一个或多个参数。与这些参数同名(但没有var.return
部分)的变量会作为输出变量。因此将会以相同的名字复制回Camel参数。
例如在如下路由中:
from("direct:start").to("flowable:process?var.return.exampleVar").to("mock:result");
名为exampleVar
的Flowable变量会作为输出变量。因此会以同名复制回Camel参数。
异步连通性测试示例
上面的例子全都是同步的。流程实例等待,直到Camel路由结束并返回。有时,需要Flowable流程实例继续运行。这时可以使用Camel服务任务的异步功能。可以将Camel服务任务的async参数设置为true,启用这个功能。
<serviceTask flowable:type="camel" flowable:async="true"/>
设置这个参数后,Camel路由会由Flowable作业执行器异步启动。如果在Camel路由中定义了队列,Flowable流程实例会继续执行流程定义中Camel服务任务之后的活动。Camel路由会与流程执行完全异步地执行。如果需要在流程定义的某处等待Camel服务任务的响应,可以使用接收任务(receive task)。
<receiveTask name="Wait State" />
流程实例会等待,直到接收到来自Camel的信号。可以在Camel中向特定的Flowable端点发送消息,来为流程实例发送信号。
from("flowable:asyncPingProcess:serviceAsyncPing").to("flowable:asyncPingProcess:receiveAsyncPing");
“flowable”字符串常量
流程名
接收任务名
使用Camel路由实例化工作流
上面的例子都是先启动Flowable流程实例,然后在流程实例中启动Camel路由。也可以反过来,在已经启动的Camel路由中启动或调用流程实例。类似于为接收任务发送消息。例如,一个简单的路由:
from("direct:start").to("flowable:camelProcess");
可以看到,URL包含两部分:第一部分是“flowable”字符串常量,第二部分是流程定义的名字。当然,需要提前在Flowable引擎中部署这个流程定义。
也可以在Camel头中,将流程实例起动人设置为某个已认证用户ID。为此,首先需要在流程定义中指定启动人变量:
<startEvent flowable:initiator="initiator" />
然后使用Camel头CamelProcessInitiatorHeader指定用户ID。Camel路由定义如下:
from("direct:startWithInitiatorHeader")
.setHeader("CamelProcessInitiatorHeader", constant("kermit"))
.to("flowable:InitiatorCamelCallProcess?processInitiatorHeaderName=CamelProcessInitiatorHeader");
8.5.10. 手动任务
描述
手动任务(manual task)定义在BPM引擎之外的任务。它用于建模引擎不需要了解,也不需要提供系统或用户界面的工作。对于引擎来说,手动任务将按直接穿过活动处理,在流程执行到达手动任务时,自动继续执行流程。
图示
手动任务用左上角有一个小“手”图标的标准BPMN 2.0任务(圆角矩形)表示。
XML表示
<manualTask name="Call client for more information" />
8.5.11. Java接收任务
描述
接收任务(receive task),是等待特定消息到达的简单任务。目前,我们只为这个任务实现了Java语义。当流程执行到达接收任务时,流程状态将提交至持久化存储。这意味着流程将保持等待状态,直到引擎接收到特定的消息,触发流程穿过接收任务继续执行。
图示
接收任务用左上角有一个消息图标的标准BPMN 2.0任务(圆角矩形)表示。消息图标是白色的(对应的黑色消息图标代表发送的含义)。
XML表示
<receiveTask name="wait" />
要使流程实例从接收任务的等待状态中继续执行,需要使用到达接收任务的执行id,调用runtimeService.signal(executionId)。下面的代码片段展示了如何操作:
ProcessInstance pi = runtimeService.startProcessInstanceByKey("receiveTask");
Execution execution = runtimeService.createExecutionQuery()
.processInstanceId(pi.getId())
.activityId("waitState")
.singleResult();
assertNotNull(execution);
runtimeService.trigger(execution.getId());
8.5.12. Shell任务
描述
Shell任务(Shell task)可以运行Shell脚本与命令。请注意Shell任务不是BPMN 2.0规范的“官方”任务(因此也没有专用图标)。
定义Shell任务
Shell任务实现为特殊的服务任务,将服务任务的type定义为'shell'进行设置。
<serviceTask flowable:type="shell">
Shell任务通过字段注入配置。这些参数的值可以使用EL表达式,将在流程执行运行时解析。可以设置下列参数:
参数 | 必填? | 类型 | 描述 | 默认值 |
---|---|---|---|---|
command | 是 | String | 要执行的Shell命令。 | |
arg0-5 | 否 | String | 参数0至参数5 | |
wait | 否 | true/false | 是否等待Shell进程终止。 | true |
redirectError | 否 | true/false | 是否将标准错误(standard error)并入标准输出(standard output)。 | false |
cleanEnv | 否 | true/false | 是否避免Shell进程继承当前环境。 | false |
outputVariable | 否 | String | 保存输出的变量名 | 不会记录输出。 |
errorCodeVariable | 否 | String | 保存结果错误码的变量名 | 不会记录错误码。 |
directory | 否 | String | Shell进程的默认目录 | 当前目录 |
示例
下面的XML代码片段是使用Shell任务的例子。将会运行"cmd /c echo EchoTest" Shell脚本,等待其结束,并将其结果存入resultVar。
<serviceTask flowable:type="shell" >
<extensionElements>
<flowable:field name="command" stringValue="cmd" />
<flowable:field name="arg1" stringValue="/c" />
<flowable:field name="arg2" stringValue="echo" />
<flowable:field name="arg3" stringValue="EchoTest" />
<flowable:field name="wait" stringValue="true" />
<flowable:field name="outputVariable" stringValue="resultVar" />
</extensionElements>
</serviceTask>
8.5.13. 执行监听器
执行监听器(execution listener)可以在流程执行中发生特定的事件时,执行外部Java代码或计算表达式。可以被捕获的事件有:
流程实例的启动和结束。
流程执行转移。
活动的启动和结束。
网关的启动和结束。
中间事件的启动和结束。
启动事件的结束,和结束事件的启动。
下面的流程定义包含了三个执行监听器:
<process>
<extensionElements>
<flowable:executionListener
class="org.flowable.examples.bpmn.executionlistener.ExampleExecutionListenerOne"
event="start" />
</extensionElements>
<startEvent />
<sequenceFlow sourceRef="theStart" targetRef="firstTask" />
<userTask />
<sequenceFlow sourceRef="firstTask" targetRef="secondTask">
<extensionElements>
<flowable:executionListener
class="org.flowable.examples.bpmn.executionListener.ExampleExecutionListenerTwo" />
</extensionElements>
</sequenceFlow>
<userTask >
<extensionElements>
<flowable:executionListener
expression="${myPojo.myMethod(execution.event)}"
event="end" />
</extensionElements>
</userTask>
<sequenceFlow sourceRef="secondTask" targetRef="thirdTask" />
<userTask />
<sequenceFlow sourceRef="thirdTask" targetRef="theEnd" />
<endEvent />
</process>
第一个执行监听器将在流程启动时收到通知。这个监听器是一个外部Java类(ExampleExecutionListenerOne
),并且需要实现org.flowable.engine.delegate.ExecutionListener
接口。当该事件发生时(这里是start
事件),会调用notify(ExecutionListenerExecution execution)
方法。
public class ExampleExecutionListenerOne implements ExecutionListener {
public void notify(ExecutionListenerExecution execution) throws Exception {
execution.setVariable("variableSetInExecutionListener", "firstValue");
execution.setVariable("eventReceived", execution.getEventName());
}
}
也可以使用实现了org.flowable.engine.delegate.JavaDelegate
接口的委托类。这些委托类也可以用于其他的结构,如服务任务的委托。
第二个执行监听器在流程执行转移时被调用。请注意listener
元素并未定义event
,因为在转移上只会触发take
事件。当监听器定义在转移上时,event
属性的值将被忽略。
最后一个执行监听器在secondTask
活动结束时被调用。监听器声明中没有使用class
,而是定义了expression
。这个表达式将在事件触发时计算/调用。
<flowable:executionListener expression="${myPojo.myMethod(execution.eventName)}" event="end" />
与其他表达式一样,可以使用与解析execution变量。execution对象提供了露事件名参数,可以使用execution.eventName
向你的方法传递事件名。
与服务任务类似,执行监听器也支持使用delegateExpression
。
<flowable:executionListener event="start" delegateExpression="${myExecutionListenerBean}" />
较早之前,我们也引入了新的执行监听器类型,org.flowable.engine.impl.bpmn.listener.ScriptExecutionListener。这个脚本执行监听器可以为执行监听器事件执行一段脚本代码。
<flowable:executionListener event="start"
class="org.flowable.engine.impl.bpmn.listener.ScriptExecutionListener">
<flowable:field name="script">
<flowable:string>
def bar = "BAR"; // 局部变量
foo = "FOO"; // 将变量放入执行上下文
execution.setVariable("var1", "test"); // 测试访问执行实例
bar // 隐式返回值
</flowable:string>
</flowable:field>
<flowable:field name="language" stringValue="groovy" />
<flowable:field name="resultVariable" stringValue="myVar" />
</flowable:executionListener>
执行监听器上的字段注入
使用通过class
属性配置的执行监听器时,可以使用字段注入。与服务任务字段注入使用完全相同的机制,可以在那里看到字段注入的各种用法。
下面的代码片段展示了一个简单的示例流程,带有一个使用了字段注入的执行监听器。
<process>
<extensionElements>
<flowable:executionListener
class="org.flowable.examples.bpmn.executionListener.ExampleFieldInjectedExecutionListener"
event="start">
<flowable:field name="fixedValue" stringValue="Yes, I am " />
<flowable:field name="dynamicValue" expression="${myVar}" />
</flowable:executionListener>
</extensionElements>
<startEvent />
<sequenceFlow sourceRef="theStart" targetRef="firstTask" />
<userTask />
<sequenceFlow sourceRef="firstTask" targetRef="theEnd" />
<endEvent />
</process>
public class ExampleFieldInjectedExecutionListener implements ExecutionListener {
private Expression fixedValue;
private Expression dynamicValue;
public void notify(ExecutionListenerExecution execution) throws Exception {
execution.setVariable("var", fixedValue.getValue(execution).toString() +
dynamicValue.getValue(execution).toString());
}
}
ExampleFieldInjectedExecutionListener
类将连接两个字段(一个是固定值-fixedValue,另一个是动态值-dynamicValue),并将其存储在'var
'流程变量中。
@Deployment(resources = {
"org/flowable/examples/bpmn/executionListener/ExecutionListenersFieldInjectionProcess.bpmn20.xml"})
public void testExecutionListenerFieldInjection() {
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("myVar", "listening!");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
"executionListenersProcess", variables);
Object varSetByListener = runtimeService.getVariable(processInstance.getId(), "var");
assertNotNull(varSetByListener);
assertTrue(varSetByListener instanceof String);
// 结果为固定注入字段及注入表达式的连接
assertEquals("Yes, I am listening!", varSetByListener);
}
请注意,与服务任务使用相同的线程安全规则。请阅读相应章节了解更多信息。
8.5.14. 任务监听器
任务监听器(task listener)用于在特定的任务相关事件发生时,执行自定义的Java逻辑或表达式。
任务监听器只能在流程定义中作为用户任务的子元素。请注意,任务监听器是一个Flowable自定义结构,因此也需要作为BPMN 2.0 extensionElements,放在flowable命名空间下。
<userTask name="My Task" >
<extensionElements>
<flowable:taskListener event="create" class="org.flowable.MyTaskCreateListener" />
</extensionElements>
</userTask>
任务监听器包含下列属性:
event(事件)(必填):触发任务监听器的任务事件类型。可用的事件有:
create(创建):当任务已经创建,并且所有任务参数都已经设置时触发。
assignment(指派):当任务已经指派给某人时触发。请注意:当流程执行到达用户任务时,在触发create事件之前,会首先触发assignment事件。这顺序看起来不太自然,但是有实际原因的:当收到create事件时,我们通常希望能看到任务的所有参数,包括办理人。
complete(完成):当任务已经完成,从运行时数据中删除前触发。
delete(删除):在任务即将被删除前触发。请注意任务由completeTask正常完成时也会触发。
class:需要调用的委托类。这个类必须实现
org.flowable.engine.delegate.TaskListener
接口。
public class MyTaskCreateListener implements TaskListener {
public void notify(DelegateTask delegateTask) {
// 这里是要实现的业务逻辑
}
}
也可以使用字段注入,为委托类传递流程变量或执行。请注意委托类的实例在流程部署时创建(与Flowable中其它的委托类一样),这意味着该实例会在所有流程实例执行中共享。
expression:(不能与class属性一起使用):指定在事件发生时要执行的表达式。可以为被调用的对象传递
DelegateTask
对象与事件名(使用task.eventName
)作为参数。
<flowable:taskListener event="create" expression="${myObject.callMethod(task, task.eventName)}" />
delegateExpression:指定一个能够解析为
TaskListener
接口实现类的对象的表达式。与服务任务类似。
<flowable:taskListener event="create" delegateExpression="${myTaskListenerBean}" />
较早之前,我们也引入了新的执行监听器类型,org.flowable.engine.impl.bpmn.listener.ScriptTaskListener。这个脚本任务监听器可以为一个任务监听器事件执行一段脚本代码。
<flowable:taskListener event="complete" class="org.flowable.engine.impl.bpmn.listener.ScriptTaskListener" >
<flowable:field name="script">
<flowable:string>
def bar = "BAR"; // 局部变量
foo = "FOO"; // 将变量放入执行上下文
task.setOwner("kermit"); // 测试访问任务实例
bar // 隐式返回值
</flowable:string>
</flowable:field>
<flowable:field name="language" stringValue="groovy" />
<flowable:field name="resultVariable" stringValue="myVar" />
</flowable:taskListener>
8.5.15. 多实例(for each)
描述
多实例活动(multi-instance activity)是在业务流程中,为特定步骤定义重复的方式。在编程概念中,多实例类似for each结构:可以为给定集合中的每一条目,顺序或并行地,执行特定步骤,甚至是整个子流程。
多实例是一个普通活动,加上定义(被称作“多实例特性的”)额外参数,会使得活动在运行时被多次执行。下列活动可以成为多实例活动:
用户任务
脚本任务
Java服务任务
Web服务任务
业务规则任务
邮件任务
人工任务
接收任务
(嵌入式)子流程
调用活动
网关与事件不能设置为多实例。
按照BPMN2.0规范的要求,用于为每个实例创建执行的父执行,会提供下列变量:
nrOfInstances:实例总数。
nrOfActiveInstances:当前活动的(即未完成的),实例数量。对于顺序多实例,这个值总为1。
nrOfCompletedInstances:已完成的实例数量。
可以调用execution.getVariable(x)
方法获取这些值。
另外,每个被创建的执行,都有局部变量(对其他执行不可见,也不存储在流程实例级别):
loopCounter:给定实例在for-each循环中的index。可以通过Flowable的elementIndexVariable属性为loopCounter变量重命名。
图示
如果一个活动是多实例,将通过在该活动底部的三条短线表示。三条竖线代表实例会并行执行,而三条横线代表顺序执行。
XML表示
要将活动变成多实例,该活动的XML元素必须有multiInstanceLoopCharacteristics
子元素
<multiInstanceLoopCharacteristics isSequential="false|true">
...
</multiInstanceLoopCharacteristics>
isSequential属性代表了活动的实例为顺序还是并行执行。
实例的数量在进入活动时,计算一次。有几种不同方法可以配置数量。一个方法是通过loopCardinality子元素,直接指定数字。
<multiInstanceLoopCharacteristics isSequential="false|true">
<loopCardinality>5</loopCardinality>
</multiInstanceLoopCharacteristics>
也可以使用解析为正整数的表达式:
<multiInstanceLoopCharacteristics isSequential="false|true">
<loopCardinality>${nrOfOrders-nrOfCancellations}</loopCardinality>
</multiInstanceLoopCharacteristics>
另一个定义实例数量的方法,是使用loopDataInputRef
子元素,指定一个集合型流程变量的名字。对集合中的每一项,都会创建一个实例。可以使用inputDataItem
子元素,将该项设置给该实例的局部变量。在下面的XML示例中展示:
<userTask name="My Task ${loopCounter}" flowable:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="false">
<loopDataInputRef>assigneeList</loopDataInputRef>
<inputDataItem name="assignee" />
</multiInstanceLoopCharacteristics>
</userTask>
假设变量assigneeList
包含[kermit, gonzo, fozzie]
。上面的代码会创建三个并行的用户任务。每一个执行都有一个名为assignee
的(局部)流程变量,含有集合中的一项,并在这个例子中被用于指派用户任务。
loopDataInputRef
与inputDataItem
的缺点是名字很难记,并且由于BPMN 2.0概要的限制,不能使用表达式。Flowable通过在multiInstanceCharacteristics
上提供collection与elementVariable属性解决了这些问题:
<userTask name="My Task" flowable:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="true"
flowable:collection="${myService.resolveUsersForTask()}" flowable:elementVariable="assignee" >
</multiInstanceLoopCharacteristics>
</userTask>
请注意collection
属性会作为表达式进行解析。如果表达式解析为字符串而不是一个集合,不论是因为本身配置的就是静态字符串值,还是表达式计算结果为字符串,这个字符串都会被当做变量名,在流程变量中用于获取实际的集合。
例如,下面的代码片段会要求集合存储在assigneeList
流程变量中:
<userTask name="My Task" flowable:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="true"
flowable:collection="assigneeList" flowable:elementVariable="assignee" >
</multiInstanceLoopCharacteristics>
</userTask>
假如myService.getCollectionVariableName()
返回字符串值,引擎就会用这个值作为变量名,获取流程变量保存的集合。
<userTask name="My Task" flowable:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="true"
flowable:collection="${myService.getCollectionVariableName()}" flowable:elementVariable="assignee" >
</multiInstanceLoopCharacteristics>
</userTask>
多实例活动在所有实例都完成时结束。然而,也可以指定一个表达式,在每个实例结束时进行计算。当表达式计算为true时,将销毁所有剩余的实例,并结束多实例活动,继续执行流程。这个表达式必须通过completionCondition子元素定义。
<userTask name="My Task" flowable:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="false"
flowable:collection="assigneeList" flowable:elementVariable="assignee" >
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6 }</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
在这个例子里,会为assigneeList
集合中的每个元素创建并行实例。当60%的任务完成时,其他的任务将被删除,流程继续运行。
边界事件与多实例
多实例是普通活动,因此可以在其边界定义边界事件。如果是中断边界事件,当其捕获事件时,会销毁活动中的所有实例。以下面的多实例子流程为例:
当定时器触发时,子流程的所有实例都会被销毁,无论有多少实例,或者实例的内部活动是否完成。
多实例与执行监听器
执行监听器与多实例一起使用时需要特别注意。以下面的BPMN 2.0 XML代码片段为例。这段XML定义在与multiInstanceLoopCharacteristics XML元素相同的级别:
<extensionElements>
<flowable:executionListener event="start" class="org.flowable.MyStartListener"/>
<flowable:executionListener event="end" class="org.flowable.MyEndListener"/>
</extensionElements>
对于普通的BPMN活动,会在活动开始与结束时调用一次监听器。
但是当该活动为多实例时,行为有区别:
当进入多实例活动时,在任何内部活动执行前,抛出一个启动事件。loopCounter变量还未设置(为null)。
进入每个实际执行的活动时,抛出一个启动事件。loopCounter变量已经设置。
结束事件类似:
离开每个实际执行的活动后,抛出一个结束事件。loopCounter变量已经设置。
多实例活动整体完成后,抛出一个结束事件。loopCounter变量未设置。
例如:
<subProcess name="Sub Process">
<extensionElements>
<flowable:executionListener event="start" class="org.flowable.MyStartListener"/>
<flowable:executionListener event="end" class="org.flowable.MyEndListener"/>
</extensionElements>
<multiInstanceLoopCharacteristics isSequential="false">
<loopDataInputRef>assignees</loopDataInputRef>
<inputDataItem name="assignee"></inputDataItem>
</multiInstanceLoopCharacteristics>
<startEvent name="Start"></startEvent>
<endEvent name="End"></endEvent>
<sequenceFlow name="" sourceRef="startevent2" targetRef="endevent2"></sequenceFlow>
</subProcess>
在这个例子中,假设assignees有三项。在运行时会发生如下事情:
多实例整体抛出一个启动事件。调用一次start执行监听器,loopCounter与assignee变量均未设置(即为null)。
每一个活动实例抛出一个启动事件。调用三次start执行监听器,loopCounter与assignee变量均已设置(也就是说不为null)。
因此启动执行监听器总共被调用四次。
请注意,即使multiInstanceLoopCharacteristics不是定义在子流程上,也是一样。例如,如果上面的例子中只是一个简单的用户任务,抛出事件的行为也是一样。
8.5.16. 补偿处理器
描述
如果要使用一个活动补偿另一个活动的影响,可以将其声明为补偿处理器(compensation handler)。补偿处理器不在正常流程中执行,而只在流程抛出补偿事件时才会执行。
补偿处理器不得有入口或出口顺序流。
补偿处理器必须通过单向的连接,关联一个补偿边界事件。
图示
如果一个活动是补偿处理器,则会在其下部中间显示补偿事件图标。下面摘录的流程图展示了一个带有补偿边界事件的服务任务,并关联至一个补偿处理器。请注意补偿处理器图标显示在"cancel hotel reservation(取消酒店预订)"服务任务的下部中间。
XML表示
要将一个活动声明为补偿处理器,需要将isForCompensation
属性设置为true:
<serviceTask isForCompensation="true" flowable:class="...">
</serviceTask>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论