任务是流程中最重要的组成部分。Flowable提供了多种任务类型,以满足实际需求。
常用任务类型有:
- 用户任务
- Java Service任务
- 脚本任务
- 业务规则任务
- 执行监听器
- 任务监听器
- 多实例
集成扩展的任务类型有:
- 手动任务
- Java接收任务
- Shell任务
- 补偿处理器
- Web Service任务
- 邮件任务
- Http任务
- Camel任务
- Mule任务
任务的图形都是以一个圆角矩形为基础,在左上角添加具体类型的图标。
一、常用的任务类型
1.1 用户任务
1.1.1 描述
“用户任务(user task)”指需要人工执行的任务。当流程执行到达用户任务时,流程实例会停止等待,直到用户触发完成任务动作。
1.1.2 图示
用户任务用左上角有一个小用户图标的标准任务(圆角矩形)表示。
1.1.3 XML表示
用户任务在XML中如下定义。其中id是必须属性,name是可选属性。
1.1.4 到期日期
每个任务都可以设置到期日期(due date)。
可以指定固定时间或相对时间,比如,当dueDate为“PT30M”时,表示到达任务30分钟后到期。
到期日期必须符合java.util.Date或java.util.String(ISO8601格式)。
实际应用,我们指定为变量值。
任务的到期日期可以使用TaskService,或者在TaskListener中使用传递的DelegateTask修改。
1.1.5 任务指派
- 指派确定的办理人
- 指派潜在办理人
- 指派潜在办理组
更多任务指派的内容,已在“用户和组”的篇章中介绍,这里不再赘述。
1.2 Java Service任务
1.2.1 描述
Java Service任务(Java service task)用于调用Java类。Java Service不属于BPMN2.0规范,而是Flowable的自定义扩展。
1.2.2 图示
服务任务用左上角有一个小齿轮图标的圆角矩形表示。
1.2.3 XML表示
有三种方法声明如何调用Java逻辑,下面分别介绍:
- 调用固定的类
使用flowable:class属性提供全限定类名(fully qualified classname),指定流程执行时调用的类,该类必须实现JavaDelegate或ActivityBehavior接口。
- 调用动态类
使用flowable:delegateExpression属性提供委托对象(delegation object)的表达式。该功能和flowable:class类似,同样需要实现JavaDelegate或ActivityBehavior接口,只不过这里不是指定一个具体的实现类,而是查询指定名称的Bean对象。
myDelegateExpressionBean
是一个实现了JavaDelegate
接口的bean,定义在Spring容器中。
- 调用类的指定方法或属性值
使用flowable:expression属性指定类的方法或属性值。同样的,该类需要实现JavaDelegate或ActivityBehavior接口。
将在名为printer
的对象上调用printMessage
方法(不带参数)。当然也可以为表达式中使用的方法传递变量。
属性值示例:
会调用名为printer
的bean的ready
参数的getter方法,getReady
(不带参数)。该值会被解析为执行的流程变量。
1.2.4 具体实现实例
下面是一个Java类的示例,用于将流程变量String改为大写。这个类通过实现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);
}
}
如果实现org.flowable.engine.impl.delegate.ActivityBehavior接口,可以访问更强大的引擎功能,例如,可以影响流程的控制流程。但注意这并不是好的实践,需要避免这么使用。
1.2.5 任务的返回值
服务执行的返回值(仅对使用表达式的服务任务),可以通过为服务任务定义的'flowable:resultVariable'属性设置为流程变量。可以是已经存在的,或者新的流程变量。 如果指定为已存在的流程变量,则流程变量的值会被服务执行的返回值覆盖。 如果不指定结果变量名,则服务任务的返回值将被忽略。
在上例中,服务执行的结果(调用'doSomething()'方法的返回值),在服务执行完成后,会设置为名为'myVar'的流程变量。
1.2.6 异常处理
当执行自定义逻辑时,通常需要捕获并在流程中处理特定的业务异常。Flowable提供了多种方式。
1.2.6.1 抛出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");
}
}
}
构造函数的参数是错误代码。错误代码决定了处理这个错误的错误处理器。
这个机制只应该用于业务错误,需要通过流程中定义的错误边界事件或错误事件子流程处理。技术错误应该通过其他异常类型表现,并且通常不在流程内部处理。
1.2.6.2 异常映射
可以使用mapException
扩展,直接将Java异常映射至业务异常(错误)。单映射是最简单的形式:
com.example.SomeException
在上面的代码中,如果服务任务抛出org.flowable.SomeException
的实例,引擎会捕获该异常,并将其转换为带有给定errorCode的BPMN错误。然后就可以像普通BPMN错误完全一样地处理。其他的异常没有映射,仍将抛出至API调用处。
也可以在单行中使用includeChildExceptions
属性,映射特定异常的所有子异常。
com.example.SomeException
上面的代码中,Flowable会将SomeException
的任何直接或间接的子类,转换为带有指定错误代码的BPMN错误。 当未指定includeChildExceptions
时,视为“false”。
1.2.6.3 默认映射
默认映射最常用。默认映射是一个不指定类的映射,可以匹配任何Java异常:
除了默认映射,会按照从上至下的顺序检查映射,使用第一个匹配的映射。只在所有映射都不能成功匹配时使用默认映射。 只有第一个不指定类的映射会作为默认映射。默认映射忽略includeChildExceptions
。
1.2.6.4 异常顺序流
还有种推荐用法,在发生异常时,将流程执行路由至另一条路径。下面是一个例子。
服务任务有两条出口顺序流,命名为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);
}
}
1.3 脚本任务
1.3.1 描述
脚本任务(script task)是自动执行的活动。当流程执行到达脚本任务时,会执行相应的脚本。
1.3.2 图示
脚本任务用左上角有一个小“脚本”图标的标准BPMN 2.0任务(圆角矩形)表示。
1.3.3 XML表示
脚本任务使用script与scriptFormat元素定义。
默认情况下,JavaScript包含在每一个JDK中,因此不需要添加任何JAR文件。如果想使用其它脚本引擎,则需要在classpath中添加相应的jar,并使用适当的名字。例如,Flowable单元测试经常使用Groovy。Groovy脚本引擎与groovy-all JAR捆绑在一起。添加如下依赖:
org.codehaus.groovy
groovy-all
2.x.x
1.3.4 脚本中的变量
到达脚本引擎的执行中,所有的流程变量都可以在脚本中使用。在这个例子里,脚本变量'inputArray'实际上就是一个流程变量(一个integer的数组)。
在脚本中设置变量的例子:
注意:下列名字是保留字, 不能用于变量名: out,out:print,lang:import,context,elcontext。
1.3.5 脚本任务的结果
脚本任务的返回值,可以通过为脚本任务定义的'flowable:resultVariable'属性设置为流程变量。可以是已经存在的,或者新的流程变量。如果指定为已存在的流程变量,则流程变量的值会被脚本执行的结果值覆盖。如果不指定结果变量名,则脚本结果值将被忽略。
在上面的例子中,脚本执行的结果(解析表达式'#{echo}'的值),将在脚本完成后,设置为名为'myVar'的流程变量。
1.4 业务规则任务
1.4.1 描述
在企业应用中,推荐做法是使用可维护的规则库来管理复杂多变的业务规则,将业务代码和规则分开维护,一旦规则有变动,只需修改预设规则即可,而不会影响到业务代码。
业务规则任务可以根据流程变量的值处理预设的业务规则。Flowable支持目前最流行的规则引擎——Drools。只需把含有业务规则任务的流程文件和规则引擎文件“.drl”一同打包部署到系统中,同时添加Drools的jar包,即可实现Flowable驱动规则引擎。
1.4.2 图示
业务规则任务显示为带有表格图标的圆角矩形。
1.4.3 XML表示
要执行业务规则,需要定义输入与结果变量。输入变量可以用流程变量的列表定义,使用逗号分隔。输出变量只能有一个变量名,如果没有指定结果变量名,默认为org.flowable.engine.rules.OUTPUT。
也可以将业务规则任务配置为只执行部署的.drl文件中的一组规则。要做到这一点,需要指定规则名字的列表,用逗号分隔。
这样只会执行rule1与rule2。
也可以定义需要从执行中排除的规则列表。
这个例子中,除了rule1与rule2之外,其它所有与流程定义一起部署的规则都会被执行。
注意:集成Drools的业务规则任务,是企业应用中的重要内容,需要重点掌握。
1.5 执行监听器
1.5.1 描述
执行监听器(execution listener)可以在流程执行中发生特定的事件时,执行外部Java代码或计算表达式。可以被捕获的事件有:
- 流程实例的启动和结束。
- 流程执行转移。
- 活动的启动和结束。
- 网关的启动和结束。
- 中间事件的启动和结束。
- 启动事件的结束,和结束事件的启动。
1.5.2 XML表示
下面的流程定义包含了三个执行监听器:
第一个执行监听器将在流程启动时收到通知。这个监听器是一个外部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
。这个表达式将在事件触发时计算/调用。
与其他表达式一样,可以使用与解析execution变量。
1.5.3 执行监听器上的字段注入
使用通过class
属性配置的执行监听器时,可以使用字段注入。
下面的代码片段展示了一个简单的示例流程,带有一个使用了字段注入的执行监听器。
ExampleFieldInjectedExecutionListener
类将连接两个字段(一个是固定值-fixedValue,另一个是动态值-dynamicValue),并将其存储在'var
'流程变量中。
@Deployment(resources = {
"org/flowable/examples/bpmn/executionListener/ExecutionListenersFieldInjectionProcess.bpmn20.xml"})
public void testExecutionListenerFieldInjection() {
Map variables = new HashMap();
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);
}
1.6 任务监听器
1.6.1 描述
任务监听器(task listener)用于在特定的任务相关事件发生时,执行自定义的Java逻辑或表达式。
1.6.2 XML表示
任务监听器只能在流程定义中作为用户任务的子元素。请注意,任务监听器是一个Flowable自定义结构,因此也需要作为BPMN 2.0 extensionElements,放在flowable命名空间下。
1.6.3 任务监听器属性:
1.6.3.1 event
触发任务监听器的任务事件类型,必填项。可用的事件有:
- create(创建):当任务已经创建,并且所有任务参数都已经设置时触发。
- assignment(指派):当任务已经指派给某人时触发。请注意:当流程执行到达用户任务时,在触发create事件之前,会首先触发assignment事件。这顺序看起来不太自然,但是有实际原因的:当收到create事件时,我们通常希望能看到任务的所有参数,包括办理人。
- complete(完成):当任务已经完成,从运行时数据中删除前触发。
- delete(删除):在任务即将被删除前触发。请注意任务由completeTask正常完成时也会触发。
1.6.3.2 class
需要调用的委托类。这个类必须实现org.flowable.engine.delegate.TaskListener
接口。
public class MyTaskCreateListener implements TaskListener {
public void notify(DelegateTask delegateTask) {
// 这里是要实现的业务逻辑
}
}
也可以使用字段注入,为委托类传递流程变量或执行。请注意委托类的实例在流程部署时创建(与Flowable中其它的委托类一样),这意味着该实例会在所有流程实例执行中共享。
1.6.3.3 expression
指定在事件发生时要执行的表达式(不能与class属性一起使用)。可以为被调用的对象传递DelegateTask
对象与事件名(使用task.eventName
)作为参数。
1.6.3.4 delegateExpression
指定一个能够解析为TaskListener
接口实现类的对象的表达式。
1.7 多实例
1.7.1 描述
多实例活动(multi-instance activity)是在业务流程中,为特定步骤定义重复的方式。在编程概念中,多实例类似for each结构:可以为给定集合中的每一条目,顺序或并行地,执行特定步骤,甚至是整个子流程。
网关和事件不能设置为多实例。
按照BPMN2.0规范的要求,用于为每个实例创建执行的父执行,会提供下列变量:
- nrOfInstances:实例总数。
- nrOfActiveInstances:当前活动的(即未完成的)实例数量。对于顺序多实例,这个值总为1。
- nrOfCompletedInstances:已完成的实例数量。
可以调用execution.getVariable(x)
方法获取这些值。
另外,每个被创建的执行,都有局部变量(对其他执行不可见,也不存储在流程实例级别):
- loopCounter:给定实例在for-each循环中的index。
1.7.2 图示
如果一个活动是多实例,将通过在该活动底部的三条短线表示。三条竖线代表实例会并行执行,而三条横线代表顺序执行。
1.7.3 XML表示
要将活动变成多实例,该活动的XML元素必须有multiInstanceLoopCharacteristics
子元素
...
isSequential属性代表了活动的实例为顺序还是并行执行。
有4种不同方法可以配置数量。
1.7.3.1 指定数字
通过loopCardinality子元素,直接指定数字:
5
1.7.3.2 表达式
使用解析为正整数的表达式:
${nrOfOrders-nrOfCancellations}
1.7.3.3 指定集合
另一个定义实例数量的方法,是使用loopDataInputRef
子元素,指定一个集合型流程变量的名字。对集合中的每一项,都会创建一个实例。可以使用inputDataItem
子元素,将该项设置给该实例的局部变量。在下面的XML示例中展示:
assigneeList
假设变量assigneeList
包含[kermit, gonzo, fozzie]
。上面的代码会创建三个并行的用户任务。每一个执行都有一个名为assignee
的(局部)流程变量,含有集合中的一项,并在这个例子中被用于指派用户任务。
loopDataInputRef
与inputDataItem
的缺点是名字很难记,并且由于BPMN 2.0概要的限制,不能使用表达式。Flowable通过在multiInstanceCharacteristics
上提供collection与elementVariable属性解决了这些问题:
请注意collection
属性会作为表达式进行解析。如果表达式解析为字符串而不是一个集合,不论是因为本身配置的就是静态字符串值,还是表达式计算结果为字符串,这个字符串都会被当做变量名,在流程变量中用于获取实际的集合。
例如,下面的代码片段会让引擎查找存储在assigneeList
流程变量中的集合:
1.7.3.4 条件型数量
多实例活动在所有实例都完成时结束。然而,也可以指定一个表达式,在每个实例结束时进行计算。当表达式计算为true时,将销毁所有剩余的实例,并结束多实例活动,继续执行流程。这个表达式必须通过completionCondition子元素定义。
${nrOfCompletedInstances/nrOfInstances >= 0.6 }
在这个例子里,会为assigneeList
集合中的每个元素创建并行实例。当60%的任务完成时,其他的任务将被删除,流程继续运行。
二、集成扩展的任务类型
Flowable还有很多集成扩展型的任务,这类任务并不常用,初学读者可以略过,在需要时再回头查阅。
2.1 手动任务
2.1.1 描述
手动任务(manual task)用来定义在BPM引擎不能完成的任务。对于引擎来说,手动任务将当做一个空任务来处理,在流程执行到达手动任务时,自动继续执行流程。
2.1.2 图示
手动任务用左上角有一个小“手”图标的标准BPMN 2.0任务(圆角矩形)表示。
2.1.3 XML表示
2.2 Java接收任务
2.2.1 描述
接收任务(receive task),是等待特定消息到达的简单任务。当流程执行到达接收任务时,将保持等待状态,直到引擎接收到特定的消息,触发流程穿过接收任务继续执行。
2.2.2 图示
接收任务用左上角有一个消息图标的标准BPMN 2.0任务(圆角矩形)表示。消息图标是白色的(对应的黑色消息图标代表发送的含义)。
2.2.3 XML表示
2.2.4 使用方法
要使流程实例从接收任务的等待状态中继续执行,需要使用到达接收任务的执行id,调用runtimeService.signal(executionId)。下面的代码片段展示了如何操作:
ProcessInstance pi = runtimeService.startProcessInstanceByKey("receiveTask");
Execution execution = runtimeService.createExecutionQuery()
.processInstanceId(pi.getId())
.activityId("waitState")
.singleResult();
runtimeService.trigger(execution.getId());
2.3 Shell任务
2.3.1 描述
Shell任务(Shell task)可以运行Shell脚本与命令。请注意Shell任务不是BPMN 2.0规范的“官方”任务(因此也没有专用图标)。
2.3.2 定义Shell任务
Shell任务实现为特殊的服务任务,将服务任务的type定义为'shell'进行设置。
2.3.3 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进程的默认目录 | 当前目录 |
2.3.4 使用示例
下面的XML代码片段是使用Shell任务的例子。将会运行"cmd /c echo EchoTest" Shell脚本,等待其结束,并将其结果存入resultVar。
2.4 补偿处理器
2.4.1 描述
如果要使用一个活动补偿另一个活动的影响,可以将其声明为补偿处理器(compensation handler)。补偿处理器不在正常流程中执行,而只在流程抛出补偿事件时才会执行。
补偿处理器不得有入口或出口顺序流。
补偿处理器必须通过单向的连接,关联一个补偿边界事件。
2.4.2 图示
如果一个活动是补偿处理器,则会在其下部中间显示补偿事件图标。下面摘录的流程图展示了一个带有补偿边界事件的服务任务,并关联至一个补偿处理器。请注意补偿处理器图标显示在"cancel hotel reservation(取消酒店预订)"服务任务的下部中间。
2.4.3 XML表示
要将一个活动声明为补偿处理器,需要将isForCompensation
属性设置为true:
2.5 集成类任务
- Web Service任务:调用外部的Web Service资源。
- 邮件任务:用于发送邮件。
- Http任务:用于发出Http请求。
- Camel任务:集成消息路由框架Camel。
- Mule任务:集成企业系统总线框架Mule。
上面的集成类任务在后续篇章中会详细介绍集成内容,此处了解即可。
三、小结
本篇介绍了BPMN2.0规范下及Flowable自定义扩展的任务类型,Flowable提供的多种任务类型基本覆盖企业应用的需求。但还有不少问题需要我们关注,比如脚本任务中的脚本安全和多实例中的线程安全。