# docs
**Repository Path**: Flowable_BPMN/docs
## Basic Information
- **Project Name**: docs
- **Description**: bpmn2.0文档
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 11
- **Forks**: 18
- **Created**: 2019-01-26
- **Last Updated**: 2024-03-01
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
------
环境:
[jkd8+]()
[mysql5.6+]()
[本内容参考官网](https://www.flowable.org/docs/userguide/index.html#bpmnCustomExtensions)
## 1 活动
事件用于模拟在流程生命周期中发生的事情。事件总是可视化为圆圈。在BPMN 2.0中,存在两个主要事件类别:*捕获*和*抛出*事件。
- **捕获:**当流程执行到达事件时,它将等待触发发生。触发器的类型由XML中的内部图标或类型声明定义。通过未填充的内部图标(它只是白色)在视觉上区分捕捉事件。
- **投掷:**当进程执行到达事件时,触发器被触发。触发器的类型由XML中的内部图标或类型声明定义。通过填充黑色的内部图标在视觉上区分投掷事件。
## 1.1.1 事件定义
事件定义定义事件的语义。没有事件定义,事件“没有什么特别之处”。例如,没有事件定义的启动事件没有指定究竟启动进程的内容。如果我们向start事件添加一个事件定义(例如,一个timer事件定义),我们声明事件的“type”启动过程(在一个定时器事件定义的情况下,某个时间点的事实是到达)。
### 1.1.2 定时器事件定义
计时器事件是由定义的计时器触发的事件。它们可以用作[开始事件](https://www.flowable.org/docs/userguide/index.html#bpmnTimerStartEvent),[中间事件](https://www.flowable.org/docs/userguide/index.html#bpmnIntermediateCatchingEvent)或[边界事件](https://www.flowable.org/docs/userguide/index.html#bpmnTimerBoundaryEvent)。时间事件的行为取决于使用的业务日历。每个计时器事件都有一个默认的业务日历,但业务日历也可以作为计时器事件定义的一部分给出。
```xml
...
```
其中businessCalendarName指向流程引擎配置中的业务日历。省略业务日历时,将使用默认业务日历。
计时器定义必须具有以下一个元素:
- **timeDate**。此格式以[ISO 8601](http://en.wikipedia.org/wiki/ISO_8601#Dates)格式指定固定日期,此时将触发触发器。例如:
```xml
2011-03-11T12:13:14
```
- **timeDuration**。要指定计时器在触发之前应运行多长时间,可以将*timeDuration*指定为*timerEventDefinition*的子元素。使用的格式是[ISO 8601](http://en.wikipedia.org/wiki/ISO_8601#Durations)格式(根据BPMN 2.0规范的要求)。例如(间隔持续10天):
```xml
P10D
```
- **timeCycle**。指定重复间隔,这对于定期启动进程或为过期用户任务发送多个提醒非常有用。时间周期元素可以是两种格式之一。首先,是[ISO 8601](http://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals)标准规定的经常性持续时间的格式。示例(3个重复间隔,每个持续10个小时):
也可以将*endDate*指定为*timeCycle*上的可选属性,或者在时间表达式的末尾指定,如下所示:`R3/PT10H/${EndDate}`。到达endDate时,应用程序将停止为此任务创建其他作业。它接受静态值[ISO 8601](http://en.wikipedia.org/wiki/ISO_8601#Dates)标准的值,例如*“2015-02-25T16:42:11 + 00:00”*,或变量,例如*$ {EndDate}*
```xml
R3/PT10H
R3/PT10H/${EndDate}
```
如果同时指定了两者,则系统将使用指定为属性的endDate。
目前,只有*BoundaryTimerEvents*和*CatchTimerEvent*支持*EndDate*功能。
此外,您可以使用cron表达式指定时间周期; 以下示例显示从完整小时开始每5分钟触发一次:
```shell
0 0/5 * * *?
```
请参阅[本教程](http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/crontrigger.html)以了解如何使用cron表达式。
**注意:**第一个符号表示秒,而不是正常Unix cron中的分钟。
循环持续时间更适合处理相对定时器,相对于某个特定时间点(例如,用户任务启动的时间)计算相对定时器,而cron表达式可以处理绝对定时器,这对于[计时器启动事件](https://www.flowable.org/docs/userguide/index.html#timerStartEventDescription)。
您可以将表达式用于计时器事件定义,通过这样做,您可以根据流程变量影响计时器定义。对于适当的计时器类型,过程变量必须包含ISO 8601(或循环类型的cron)字符串。另外,对于持续时间,`java.time.Duration`可以使用返回的类型或表达式的变量。
```xml
${duration}
```
**注:**计时器,只有当启用了异步执行(烧制*asyncExecutorActivate*必须设置为`true`中`flowable.cfg.xml`,因为异步执行默认情况下禁用)。
### 1.1.3 错误事件定义
### **重要说明:** BPMN错误与Java异常不同。事实上,两者没有任何共同之处。BPMN错误事件是一种建模*业务异常的方法*。Java异常[以其自己的特定方式](https://www.flowable.org/docs/userguide/index.html#serviceTaskExceptionHandling)处理。
```xml
```
### 1.1.4 信号事件定义
信号事件是引用命名信号的事件。信号是全局范围的事件(广播语义),并传递给所有活动的处理程序(等待进程实例/捕获信号事件)。
使用该`signalEventDefinition`元素声明信号事件定义。该属性`signalRef`引用`signal`声明为`definitions`根元素的子元素的元素。以下是一个过程的摘录,其中信号事件被中间事件抛出并捕获。
```xml
...
...
```
所述`signalEventDefinition`参考中相同的`signal`元件。
##### 投掷信号事件
信号可以由流程实例使用BPMN构造抛出,也可以使用java API以编程方式抛出。以下方法`org.flowable.engine.RuntimeService`可用于以编程方式抛出信号:
```java
RuntimeService.signalEventReceived(String signalName);
RuntimeService.signalEventReceived(String signalName, String executionId);
```
`signalEventReceived(String signalName)`和之间的区别在于`signalEventReceived(String signalName, String executionId)`第一种方法将信号全局抛出到所有预订处理程序(广播语义),第二种方法仅将信号传递给特定执行。
##### 捕捉信号事件
信号事件可以被中间捕获信号事件或信号边界事件捕获。
##### 查询信号事件订阅
可以查询已订阅特定信号事件的所有执行:
```java
List executions = runtimeService.createExecutionQuery()
.signalEventSubscriptionName("alert")
.list();
```
然后我们可以使用该`signalEventReceived(String signalName, String executionId)`方法将信号传递给这些执行。
##### 信号事件范围
默认情况下,信号是*广播流程引擎*。这意味着您可以在流程实例中抛出信号事件,而具有不同流程定义的其他流程实例可以对此事件的发生做出反应。
但是,有时需要仅在*同一个流程实例中*对信号事件作出反应。例如,用例是当两个或多个活动互斥时流程实例中的同步机制。
要限制信号事件的*范围*,请将(非BPMN 2.0标准!)*范围属性添加*到信号事件定义中:
```xml
```
此属性的默认值为*“global”*。
##### 信号事件示例
以下是使用信号进行通信的两个独立进程的示例。如果更新或更改保险单,则启动第一个流程。在人类参与者审查了更改后,将抛出信号事件,表明策略已更改:

现在,所有感兴趣的流程实例都可以捕获此事件。以下是订阅该事件的流程示例。

**注意:**了解信号事件向**所有**活动处理程序广播非常重要。这意味着,在上面给出的示例的情况下,捕获信号的过程的所有实例将接收事件。在这种情况下,这就是我们想要的。但是,也存在无意中广播行为的情况。请考虑以下过程:

BPMN不支持上述过程中描述的模式。这个想法是,执行“执行某事”任务时抛出的错误被边界错误事件捕获,使用信号throw事件传播到并行执行路径,然后中断“并行执行”任务。到目前为止,Flowable将按预期执行。信号将传播到捕获边界事件并中断任务。**但是,由于信号的广播语义,它还将传播到已订阅信号事件的所有其他进程实例。**在这种情况下,这可能不是我们想要的。
**注意:** signal事件不会对特定流程实例执行任何类型的关联。相反,它会广播到所有流程实例。如果只需要向特定流程实例发送信号,请手动执行关联并`signalEventReceived(String signalName, String executionId)`与相应的[查询机制](https://www.flowable.org/docs/userguide/index.html#bpmnSignalEventDefinitionQuery)一起使用。
Flowable确实有办法通过将*scope*属性添加到设置为*processInstance*的signal事件来解决此问题。
### 1.1.5 消息事件定义
消息事件是引用命名消息的事件。消息具有名称和有效负载。与信号不同,消息事件始终指向单个接收器。
使用该`messageEventDefinition`元素声明消息事件定义。该属性`messageRef`引用`message`声明为`definitions`根元素的子元素的元素。以下是一个过程的摘录,其中两个消息事件由start事件和中间捕获消息事件声明和引用。
```xml
...
...
```
##### 抛出一个消息事件
作为可嵌入的流程引擎,Flowable并不关心实际接收消息。这将取决于环境并且需要特定于平台的活动,例如连接到JMS(Java消息服务)队列/主题或处理Web服务或REST请求。因此,接收消息是您必须实现的过程引擎嵌入的应用程序或基础结构的一部分。
在应用程序中收到消息后,您必须决定如何处理它。如果消息应触发新流程实例的启动,请在运行时服务提供的以下方法之间进行选择:
```java
ProcessInstance startProcessInstanceByMessage(String messageName);
ProcessInstance startProcessInstanceByMessage(String messageName, Map processVariables);
ProcessInstance startProcessInstanceByMessage(String messageName, String businessKey,
Map processVariables);
```
这些方法使用引用的消息启动流程实例。
如果消息需要由现有流程实例接收,则首先必须将消息关联到特定流程实例(请参阅下一节),然后触发等待执行的继续。运行时服务提供以下方法,用于根据消息事件订阅触发执行:
```java
void messageEventReceived(String messageName, String executionId);
void messageEventReceived(String messageName, String executionId, HashMap processVariables);
```
##### 查询消息事件订阅
- 在消息启动事件的情况下,消息事件订阅与特定的*流程定义*相关联。可以使用以下命令查询此类消息订阅`ProcessDefinitionQuery`:
```java
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.messageEventSubscription("newCallCenterBooking")
.singleResult();
```
由于特定邮件订阅只能有一个流程定义,因此查询始终返回零或一个结果。如果更新了流程定义,则只有最新版本的流程定义才能订阅消息事件。
- 在中间捕获消息事件的情况下,消息事件订阅与特定*执行*相关联。可以使用以下命令查询此类消息事件订阅`ExecutionQuery`:
```java
Execution execution = runtimeService.createExecutionQuery()
.messageEventSubscriptionName("paymentReceived")
.variableValueEquals("orderId", message.getOrderId())
.singleResult();
```
此类查询称为相关查询,通常需要有关进程的知识(在这种情况下,给定orderId最多只有一个流程实例)。
##### 消息事件示例
以下是可以使用两个不同消息启动的进程示例:

如果流程需要替代方法来响应不同的启动事件,但最终以统一的方式继续,这将非常有用。
### 1.1.6 开始活动
开始事件表示进程的开始位置。类型开始事件(处理开始于消息的到达,在特定的时间间隔,等等),定义*如何*在处理开始时,被示出为在事件的视觉表示一个小图标。在XML表示中,类型由子元素的声明给出。
开始事件**总是捕获**:从概念上讲,事件(在任何时候)等待直到某个触发发生。
在start事件中,可以指定以下Flowable特定属性:
- **initiator**:标识进程启动时将在其中存储经过身份验证的用户ID的变量名称。例如:
```xml
```
必须使用`IdentityService.setAuthenticatedUserId(String)`try-finally块中的方法设置经过身份验证的用户,如下所示:
```xml
try {
identityService.setAuthenticatedUserId("bono");
runtimeService.startProcessInstanceByKey("someProcessKey");
} finally {
identityService.setAuthenticatedUserId(null);
}
```
此代码已粘贴到Flowable应用程序中,因此它与[Forms](https://www.flowable.org/docs/userguide/index.html#forms)结合使用。
### 1.1.7 无开始活动
##### 描述
一个*没有*启动事件技术上意味着启动过程的实例的触发是不确定的。这意味着引擎无法预测何时必须启动流程实例。通过调用*startProcessInstanceByXXX*方法之一,通过API启动流程实例时,将使用none start事件。
```xml
ProcessInstance processInstance = runtimeService.startProcessInstanceByXXX();
```
*注意:*子进程始终具有无启动事件。
##### 图形符号
无启动事件可视化为没有内部图标的圆圈(换句话说,没有触发类型)。

##### XML表示
无启动事件的XML表示是没有任何子元素的正常启动事件声明(其他启动事件类型都具有声明该类型的子元素)。
```xml
```
##### 无启动事件的自定义扩展
**formKey**:引用用户在启动新流程实例时必须填写的表单定义。更多信息可以在[表单部分](https://www.flowable.org/docs/userguide/index.html#forms)找到示例:
```xml
```
#### 1.1.8 定时器启动事件
##### 描述
计时器启动事件用于在给定时间创建流程实例。它既可以用于应该只启动一次的进程,也可以用于应该以特定时间间隔启动的进程。
*注意:*子进程不能有计时器启动事件。
*注意:*一旦部署了进程,就会安排启动计时器事件。不需要调用startProcessInstanceByXXX,虽然调用start process方法不受限制,并且会在startProcessInstanceByXXX调用时再引发一个进程。
*注意:*当部署具有启动计时器事件的新版本的进程时,将删除与先前计时器对应的作业。原因是通常不希望自动启动旧版本流程的新流程实例。
##### 图形符号
计时器启动事件可视化为带有时钟内部图标的圆圈。

##### XML表示
计时器启动事件的XML表示是具有计时器定义子元素的正常启动事件声明。有关配置详细信息,请参阅[计时器定义](https://www.flowable.org/docs/userguide/index.html#timerEventDefinitions)。
示例:从2011年3月11日12:13开始,过程将以5分钟为间隔开始4次
```xml
R4/2011-03-11T12:13/PT5M
```
示例:进程将在所选日期开始一次
```xml
2011-03-11T12:13:14
```
### 1.1.9 消息开始事件
##### 描述
一个[消息](https://www.flowable.org/docs/userguide/index.html#bpmnMessageEventDefinition)开始事件可用于使用已命名的信息来启动一个过程实例。这有效地允许我们使用消息名称从一组备选启动事件中*选择*正确的启动事件。
在使用一个或多个消息启动事件**部署**流程定义时,以下注意事项适用:
- 消息启动事件的名称在给定的流程定义中必须是唯一的。流程定义不能具有多个具有相同名称的消息启动事件。Flowable在部署包含两个或多个引用相同消息的消息启动事件的流程定义时抛出异常,或者两个或多个消息启动事件引用具有相同消息名称的消息时抛出异常。
- 消息启动事件的名称在所有已部署的流程定义中必须是唯一的。Flowable在部署包含一个或多个消息启动事件的流程定义时抛出异常,该消息启动事件引用与已由不同流程定义部署的消息启动事件同名的消息。
- 流程版本控制:部署新版本的流程定义后,将删除先前版本的启动消息订阅。
当**启动**一个流程实例,可以使用在下面的方法来触发的消息开始的事件`RuntimeService`:
```xml
ProcessInstance startProcessInstanceByMessage(String messageName);
ProcessInstance startProcessInstanceByMessage(String messageName, Map processVariables);
ProcessInstance startProcessInstanceByMessage(String messageName, String businessKey,
Map
...
```
#### 1.1.10 信号启动事件
##### 描述
一个[信号](https://www.flowable.org/docs/userguide/index.html#bpmnSignalEventDefinition)启动事件可用于使用命名信号来启动一个过程实例。可以使用中间信号throw事件或通过API(*runtimeService.signalEventReceivedXXX*方法)从流程实例中*触发*信号。在这两种情况下,将启动具有相同名称的信号启动事件的所有流程定义。
请注意,在这两种情况下,还可以在流程实例的同步和异步启动之间进行选择。
在`signalName`必须在API中传递是在给定的名称`name`的属性`signal`被引用的元素`signalRef`的属性`signalEventDefinition`。
##### 图形符号
信号开始事件可视化为具有信号事件符号的圆圈。符号未填充,表示捕获(接收)行为。

##### XML表示
信号启动事件的XML表示是具有signalEventDefinition子元素的正常启动事件声明:
```xml
```
### 1.1.11 错误开始事件
##### 描述
一个[错误](https://www.flowable.org/docs/userguide/index.html#bpmnErrorEventDefinition)启动事件可用于触发事件的子过程。**错误启动事件不能用于启动流程实例**。
错误启动事件总是在中断。
##### 图形符号
错误开始事件可视化为带有错误事件符号的圆圈。符号未填充,表示捕获(接收)行为。

##### XML表示
错误启动事件的XML表示形式是带有errorEventDefinition子元素的正常启动事件声明:
```xml
```
### 1.1.12 结束事件
结束事件表示流程或子流程中路径的结束。终结事件**总是在抛出**。这意味着当流程执行到达结束事件时,将抛出*结果*。结果的类型由事件的内部黑色图标描绘。在XML表示中,类型由子元素的声明给出。
### 1.1.13 无结束事件
##### 描述
一个*没有*终点事件意味着*结果*当达到该事件是不确定的抛出。因此,除了结束当前的执行路径之外,引擎不会做任何额外的事情。
##### 图形符号
无端事件可视化为具有粗边框但没有内部图标(无结果类型)的圆。

##### XML表示
无结束事件的XML表示是正常的结束事件声明,没有任何子元素(其他结束事件类型都有一个声明该类型的子元素)。
```xml
```
### 1.1.14 错误结束事件
##### 描述
当流程执行到达**错误结束事件时**,当前执行路径结束并抛出错误。[匹配的中间边界错误事件](https://www.flowable.org/docs/userguide/index.html#bpmnBoundaryErrorEvent)可能会[捕获](https://www.flowable.org/docs/userguide/index.html#bpmnBoundaryErrorEvent)此错误。如果未找到匹配的边界错误事件,则将引发异常。
##### 图形符号
错误结束事件可视化为典型的结束事件(具有粗边框的圆圈),其中包含错误图标。错误图标完全是黑色,表示其抛出语义。

##### XML表示
错误结束事件表示为结束事件,带有*errorEventDefinition*子元素。
```xml
```
所述*errorRef*属性可以引用*错误*指在过程之外定义元件:
```xml
...
...
```
该**的errorCode**的的*错误*将用于查找匹配的捕捉边界错误事件。如果*errorRef*与任何已定义的*错误*不匹配,则*errorRef*将用作*errorCode*的快捷方式。这是一个Flowable特定的快捷方式。更具体地说,以下片段在功能上是等同的。
```xml
...
...
...
```
相当于
```xml
```
请注意,*errorRef*必须符合BPMN 2.0模式,并且必须是有效的QName。
### 1.1.5 终止结束事件
##### 描述
当达到*终止结束事件*时,将*终止*当前流程实例或子流程。从概念上讲,当执行到达终止结束事件时,将确定并结束第一*范围*(流程或子流程)。请注意,在BPMN 2.0中,子流程可以是嵌入式子流程,调用活动,事件子流程或事务子流程。此规则通常适用:例如,当存在多实例调用活动或嵌入式子流程时,仅该实例将结束,其他实例和流程实例不受影响。
可以添加可选属性*terminateAll*。如果为*true*,则无论在流程定义中是否放置终止结束事件,并且无论是否处于子流程(甚至是嵌套),(根)流程实例都将终止。
##### 图形符号
取消结束事件可视化为典型的结束事件(具有粗轮廓的圆圈),内部带有完整的黑色圆圈。

##### XML表示
终止事件事件表示为end事件,具有*terminateEventDefinition*子元素。
请注意,*terminateAll*属性是可选的(默认情况下为*false*)。
```xml
```
### 1.1.16 取消结束事件
##### 描述
取消结束事件只能与BPMN事务子流程结合使用。当到达取消结束事件时,抛出取消事件,必须由取消边界事件捕获。取消边界事件然后取消交易并触发补偿。
##### 图形符号
取消结束事件可视化为典型的结束事件(具有粗轮廓的圆圈),其中包含取消图标。取消图标完全是黑色,表示其抛出语义。

##### XML表示
取消结束事件表示为结束事件,具有*cancelEventDefinition*子元素。
```xml
```
### 1.1.17 边界事件
边界事件*捕获*附加到活动的事件(边界事件永远不会抛出)。这意味着当活动正在运行时,事件正在*侦听*某种类型的触发器。*捕获*事件*时*,活动将中断,并且将遵循从事件中退出的序列流。
所有边界事件都以相同的方式定义:
```xml
```
边界事件定义为
- 唯一标识符(流程范围内)
- 通过**attachedToRef**属性引用事件的活动的引用。请注意,边界事件的定义与它们所附加的活动处于同一级别(换句话说,在活动中不包含边界事件)。
- 形式为*XXXEventDefinition*的XML子元素(例如,*TimerEventDefinition*,*ErrorEventDefinition*等),用于定义边界事件的类型。有关详细信息,请参阅特定边界事件类型。
#### 1.1.18 定时器边界事件
##### 描述
计时器边界事件充当秒表和闹钟。当执行到达附加边界事件的活动时,启动计时器。当计时器触发时(例如,在指定的间隔之后),活动被中断并且遵循边界事件之外的顺序流。
##### 图形符号
计时器边界事件可视化为典型的边界事件(边界上的圆圈),内部有计时器图标。

##### XML表示
计时器边界事件被定义为[常规边界事件](https://www.flowable.org/docs/userguide/index.html#bpmnBoundaryEvent)。在这种情况下,特定类型的子元素是**timerEventDefinition**元素。
```xml
PT4H
```
有关定时器配置的详细信息,请参阅[定时器事件定义](https://www.flowable.org/docs/userguide/index.html#timerEventDefinitions)。
在图形表示中,圆圈的线条点缀,如上例所示:

典型的用例是在一段时间后发送升级电子邮件,但不会影响正常的流程。
中断和非中断定时器事件之间存在关键差异。不间断意味着原始活动**不会**中断但保持原样。中断行为是默认行为。在XML表示中,*cancelActivity*属性设置为false:
```xml
```
**注:**边界计时器事件仅当启用了异步执行发生(*asyncExecutorActivate*需要被设置为`true`在`flowable.cfg.xml`,因为异步执行默认情况下禁用)。
##### 边界事件的已知问题
在使用任何类型的边界事件时,存在关于并发性的已知问题。目前,不可能将多个传出序列流附加到边界事件。该问题的解决方案是使用一个传递到并行网关的传出序列流。

#### 1.1.19 错误边界事件
##### 描述
活动边界上的中间*捕获*错误或简称**边界错误事件**捕获在定义它的活动范围内引发的错误。
定义边界错误事件对[嵌入式子流程](https://www.flowable.org/docs/userguide/index.html#bpmnSubProcess)或[调用活动](https://www.flowable.org/docs/userguide/index.html#bpmnCallActivity)最有意义,因为子流程为子流程内的所有活动创建范围。错误[结束事件](https://www.flowable.org/docs/userguide/index.html#bpmnErrorEndEvent)引发[错误](https://www.flowable.org/docs/userguide/index.html#bpmnErrorEndEvent)。这样的错误将向上传播其父作用域,直到找到定义了与错误事件定义匹配的边界错误事件的作用域。
捕获错误事件时,将销毁定义边界事件的活动,同时销毁(并发活动,嵌套子流程等)中的所有当前执行。在边界事件的输出序列流之后继续执行流程。
##### 图形符号
边界错误事件可视化为边界上的典型中间事件(内部具有较小圆圈的圆圈),其中包含错误图标。错误图标为白色,表示其*捕获*语义。

##### XML表示
边界错误事件被定义为典型的[边界事件](https://www.flowable.org/docs/userguide/index.html#bpmnBoundaryEvent):
```xml
```
与[错误结束事件一样](https://www.flowable.org/docs/userguide/index.html#bpmnErrorEndEvent),*errorRef*引用在process元素外部定义的错误:
```xml
...
...
```
该**的errorCode**用来匹配被发现的错误:
- 如果*errorRef*被省略,边界错误事件将捕获**任何错误事件**,而不管errorCode的*错误*。
- 如果提供了*errorRef*并且它引用了现有*错误*,则边界事件将**仅捕获具有相同错误代码的错误**。
- 如果提供了*errorRef*,但BPMN 2.0文件中没有定义*错误*,则**errorRef用作errorCode**(类似于错误结束事件)。
##### 例
以下示例流程显示了如何使用错误结束事件。当通过没有提供足够的信息来完成*“审查盈利能力”*用户任务时,会引发错误。如果在子流程的边界上发现此错误,则*“审核销售线索”*子流程中的所有活动活动都将被销毁(即使*“审核客户评级”*尚未完成),并且*“提供其他详细信息” '*用户任务已创建。

此过程在演示设置中作为示例提供。可以在*org.flowable.examples.bpmn.event.error*包中找到进程XML和单元测试。
#### 1.1.20 信号边界事件
##### 描述
在活动边界上的附加中间*捕获* [信号](https://www.flowable.org/docs/userguide/index.html#bpmnSignalEventDefinition)或简称**边界信号事件**捕获具有与参考信号定义相同的信号名称的信号。
**注意:**与其他事件(例如边界错误事件)相反,边界信号事件不仅捕获从其附加的范围抛出的信号事件。相反,信号事件具有全局范围(广播语义),这意味着信号可以从任何地方抛出,甚至可以从不同的流程实例抛出。
**注意:**与其他事件(例如错误事件)相反,如果捕获了信号,则不会消耗该信号。如果有两个活动信号边界事件捕获相同的信号事件,则两个边界事件都会被触发,即使它们是不同流程实例的一部分。
##### 图形符号
边界信号事件可视化为边界上的典型中间事件(内部具有较小圆的圆),其中具有信号图标。信号图标为白色(未填充),表示其*捕获*语义。

##### XML表示
边界信号事件被定义为典型的[边界事件](https://www.flowable.org/docs/userguide/index.html#bpmnBoundaryEvent):
```xml
```
##### 例
请参阅有关[信号事件定义](https://www.flowable.org/docs/userguide/index.html#bpmnSignalEventDefinition)的部分。
#### 1.1.21 消息边界事件
##### 描述
在活动边界上附加的中间*捕获* [消息](https://www.flowable.org/docs/userguide/index.html#bpmnMessageEventDefinition)或简称**边界消息事件**捕获具有与引用的消息定义相同的消息名称的消息。
##### 图形符号
边界消息事件可视化为边界上的典型中间事件(内部具有较小圆圈的圆圈),其中包含消息图标。消息图标为白色(未填充),以指示其*捕获*语义。

请注意,边界消息事件可以是中断(右侧)和非中断(左侧)。
##### XML表示
边界消息事件被定义为典型的[边界事件](https://www.flowable.org/docs/userguide/index.html#bpmnBoundaryEvent):
```xml
```
##### 例
请参阅有关[消息事件定义](https://www.flowable.org/docs/userguide/index.html#bpmnMessageEventDefinition)的部分。
#### 1.1.22 取消边界事件
##### 描述
当事务被取消时,触发事务子流程**边界**或简称**边界取消事件**的附加中间*捕获*取消事件。触发取消边界事件时,它首先中断当前作用域中的所有活动执行。接下来,它开始补偿交易范围内的所有有效补偿边界事件。补偿是同步进行的,换句话说,边界事件在离开交易之前等待补偿之前等待。当补偿完成时,使用在取消边界事件之外的任何序列流来保留事务子过程。
**注意:**事务子流程只允许一个取消边界事件。
**注意:**如果事务子流程承载嵌套的子流程,则仅对已成功完成的子流程触发补偿。
**注意:**如果在具有多实例特征的事务子流程上放置取消边界事件,则如果一个实例触发取消,则边界事件将取消所有实例。
##### 图形符号
取消边界事件可视化为边界上的典型中间事件(内部具有较小圆圈的圆圈),其中具有取消图标。取消图标为白色(未填充),表示其*捕获*语义。

##### XML表示
取消边界事件被定义为典型的[边界事件](https://www.flowable.org/docs/userguide/index.html#bpmnBoundaryEvent):
```xml
```
由于取消边界事件始终在中断,因此`cancelActivity`不需要该属性。
### 1.1.23 补偿边界事件
##### 描述
附加的中间*捕获*的活动或边界上的补偿**补偿边界事件**对于短的,可以被用来补偿处理程序附加到活动。
补偿边界事件必须使用定向关联引用单个补偿处理程序。
补偿边界事件与其他边界事件具有不同的激活策略。其他边界事件(例如信号边界事件)在启动它们所附加的活动时被激活。活动结束后,将停用它们并取消相应的事件订阅。补偿边界事件是不同的。补偿边界事件在附加的活动**成功完成**时激活。此时,创建对补偿事件的相应订阅。在触发补偿事件或相应的流程实例结束时,将删除订阅。由此可见:
- 触发补偿时,与补偿边界事件关联的补偿处理程序的调用次数与成功完成的活动的次数相同。
- 如果将补偿边界事件附加到具有多个实例特征的活动,则会为每个实例创建补偿事件订阅。
- 如果补偿边界事件附加到循环内包含的活动,则每次执行活动时都会创建补偿事件订阅。
- 如果流程实例结束,则取消对补偿事件的订阅。
**注意:**嵌入式子流程不支持补偿边界事件。
##### 图形符号
补偿边界事件可视化为边界上的典型中间事件(内部具有较小圆的圆),其中具有补偿图标。补偿图标为白色(未填充),表示其*捕获*语义。除了补偿边界事件之外,下图还显示了使用单向关联与边界事件关联的补偿处理程序:

##### XML表示
补偿边界事件被定义为典型的[边界事件](https://www.flowable.org/docs/userguide/index.html#bpmnBoundaryEvent):
```xml
```
由于在活动成功完成后激活补偿边界事件,因此`cancelActivity`不支持该属性。
#### 1.1.24 中间捕获事件
所有中间捕获事件都以相同的方式定义:
```xml
```
中间捕获事件定义为:
- 唯一标识符(流程范围内)
- 形式为*XXXEventDefinition*的XML子元素(例如,*TimerEventDefinition*),用于定义中间捕获事件的类型。有关详细信息,请参阅特定捕获事件类型。
#### 1.1.25 定时器中间捕捉事件
##### 描述
计时器中间事件充当秒表。当执行到达捕获事件活动时,启动计时器。当计时器触发时(例如,在指定的间隔之后),遵循从计时器中间事件发出的顺序流。
##### 图形符号
计时器中间事件可视化为中间捕获事件,内部有计时器图标。

##### XML表示
计时器中间事件被定义为[中间捕获事件](https://www.flowable.org/docs/userguide/index.html#bpmnIntermediateCatchingEvent)。在这种情况下,特定类型子元素是**timerEventDefinition**元素。
```xml
PT5M
```
请参阅[计时器事件定义](https://www.flowable.org/docs/userguide/index.html#timerEventDefinitions)以获取配置详
### 1.1.26 信号中间捕捉事件
##### 描述
中间*捕获* [信号](https://www.flowable.org/docs/userguide/index.html#bpmnSignalEventDefinition)事件捕获具有与参考信号定义相同的信号名称的信号。
**注意:**与其他事件(例如错误事件)相反,如果捕获了信号,则不会消耗该信号。如果有两个活动信号边界事件捕获相同的信号事件,则两个边界事件都会被触发,即使它们是不同流程实例的一部分。
##### 图形符号
中间信号捕获事件可视化为典型的中间事件(内部具有较小圆圈的圆圈),其中具有信号图标。信号图标为白色(未填充),表示其*捕获*语义。

##### XML表示
信号中间事件被定义为[中间捕获事件](https://www.flowable.org/docs/userguide/index.html#bpmnIntermediateCatchingEvent)。在这种情况下,特定类型子元素是**signalEventDefinition**元素。
```xml
```
##### 例
请参阅有关[信号事件定义](https://www.flowable.org/docs/userguide/index.html#bpmnSignalEventDefinition)的部分。
### 1.1.27 消息中间捕获事件
##### 描述
中间*捕获* [消息](https://www.flowable.org/docs/userguide/index.html#bpmnMessageEventDefinition)事件捕获具有指定名称的消息。
##### 图形符号
中间捕获消息事件可视化为典型的中间事件(内部具有较小圆圈的圆圈),其中包含消息图标。消息图标为白色(未填充),以指示其*捕获*语义。

##### XML表示
消息中间事件被定义为[中间捕获事件](https://www.flowable.org/docs/userguide/index.html#bpmnIntermediateCatchingEvent)。在这种情况下,特定类型子元素是**messageEventDefinition**元素。
```xml
```
##### 例
请参阅有关[消息事件定义](https://www.flowable.org/docs/userguide/index.html#bpmnMessageEventDefinition)的部分。
### 1.1.28 中间抛出事件
所有中间抛出事件都以相同的方式定义:
```xml
```
中间抛出事件定义为:
- 唯一标识符(流程范围内)
- 形式为*XXXEventDefinition*的XML子元素(例如,*signalEventDefinition*),用于定义中间抛出事件的类型。有关详细信息,请参阅特定投掷事件类型。
1.1.29 中间抛出无事件
以下流程图显示了一个中间无事件的简单示例,该事件通常用于指示流程中实现的某些状态。

通过添加[执行侦听](https://www.flowable.org/docs/userguide/index.html#executionListeners)器,这可以成为监视某些KPI的好钩子。
```xml
```
在这里,您可以添加一些自己的代码,以便将一些事件发送到您的BAM工具或DWH。在这种情况下,引擎本身不做任何事情,它只是通过。
### 1.1.30 信号中间抛出事件
##### 描述
中间*抛出* [信号](https://www.flowable.org/docs/userguide/index.html#bpmnSignalEventDefinition)事件为定义的信号抛出信号事件。
在Flowable中,信号被广播给所有活动的处理程序(换句话说,所有捕获信号事件)。信号可以同步或异步发布。
- 在默认配置中,信号是**同步**传送的。这意味着抛出流程实例会等待信号传递到所有捕获流程实例。捕获流程实例也在与抛出流程实例相同的事务中得到通知,这意味着如果其中一个通知的实例产生技术错误(抛出异常),则所有涉及的实例都会失败。
- 信号也可以**异步**传送。在这种情况下,确定在达到抛出信号事件时哪些处理者是活动的。对于每个活动的处理程序,JobExecutor存储并传递异步通知消息(Job)。
##### 图形符号
中间信号投掷事件可视化为典型的中间事件(内部具有较小圆圈的圆圈),其中包含信号图标。信号图标为黑色(填充),表示其*抛出*语义。

##### XML表示
信号中间事件被定义为[中间抛出事件](https://www.flowable.org/docs/userguide/index.html#bpmnIntermediateThrowEvent)。在这种情况下,特定类型子元素是**signalEventDefinition**元素。
```xml
```
异步信号事件看起来像这样:
```xml
```
##### 例
请参阅有关[信号事件定义](https://www.flowable.org/docs/userguide/index.html#bpmnSignalEventDefinition)的部分。
### 1.1.31 补偿中间抛出事件
##### 描述
中间抛出补偿事件可用于触发补偿。
**触发补偿:**可以针对指定活动或托管补偿事件的范围触发补偿。通过执行与**活动相关联的补偿处理程序**来执行补偿。
- 当为活动抛出补偿时,相关的补偿处理程序执行的次数与活动成功完成的次数相同。
- 如果对当前范围进行补偿,则补偿当前范围内的所有活动,包括并发分支上的活动。
- 分层次地触发补偿:如果要补偿的活动是子流程,则对子流程中包含的所有活动触发补偿。如果子流程具有嵌套活动,则递归抛出补偿。但是,补偿不会传播到流程的“上层”:如果在子流程中触发补偿,则不会将补偿传播到子流程范围之外的活动。BPMN规范规定对“相同级别的子流程”的活动触发补偿。
- 在Flowable中,补偿以相反的执行顺序执行。这意味着最后完成的任何活动都会首先得到补偿,依此类推。
- 中间抛出补偿事件可用于补偿成功竞争的交易子过程。
**注意:**如果在包含子流程的范围内抛出补偿,并且子流程包含具有补偿处理程序的活动,则补偿仅在抛出补偿时成功完成后传播到子流程。如果嵌套在子流程中的某些活动已完成并附加了补偿处理程序,则如果尚未完成包含这些活动的子流程,则不会执行补偿处理程序。请考虑以下示例:

在这个过程中,我们有两个并发执行:一个执行嵌入式子流程,另一个执行“充值信用卡”活动。假设两个执行都已启动,并且第一个并发执行正在等待用户完成“审核预订”任务。第二次执行执行“充值信用卡”活动并抛出错误,这导致“取消预订”事件触发补偿。此时并行子流程尚未完成,这意味着补偿事件不会传播到子流程,因此不执行“取消酒店预订”补偿处理程序。如果用户任务(以及嵌入式子流程)在执行“取消预订”之前完成,
**流程变量:**当补偿嵌入的子流程时,用于执行补偿处理程序的执行可以访问子流程在子流程完成执行时所处的状态的本地流程变量。为实现此目的,采用与范围执行相关联的过程变量的快照(为执行子流程而创建的执行)。从这一点来看,有几个含义如下:
- 补偿处理程序无权访问添加到子流程范围内创建的并发执行的变量。
- 与层次结构中较高层次的执行相关联的流程变量(例如,与流程实例执行相关联的流程变量)不包含在快照中:补偿处理程序可以在抛出补偿时访问它们所处的状态中的这些流程变量。
- 变量快照仅用于嵌入式子流程,而不用于其他活动。
**目前的限制:**
- `waitForCompletion="false"`目前不受支持。当使用中间抛出补偿事件触发补偿时,仅在补偿成功完成后才会留下事件。
- 补偿本身目前由并发执行执行。并发执行以补偿活动完成的相反顺序启动。
- 补偿不会传播到由调用活动生成的子流程实例。
##### 图形符号
中间补偿抛出事件可视化为典型的中间事件(内部具有较小圆圈的圆圈),内部具有补偿图标。补偿图标为黑色(填充),表示其*抛出*语义。

##### XML表示
补偿中间事件被定义为[中间抛出事件](https://www.flowable.org/docs/userguide/index.html#bpmnIntermediateThrowEvent)。在这种情况下,特定类型子元素是**compensateEventDefinition**元素。
```xml
```
此外,可选参数`activityRef`可用于触发特定范围或活动的补偿:
```xml
```
## 1.2 连线(序列流)
#### 1.2.1 描述
连线是流程的两个元素之间的连接器。在流程执行期间访问元素后,将遵循所有传出的连线。这意味着BPMN 2.0的默认特性是并行:两个出线将创建两个独立的并行执行路径。
1.2.2 图形符号
连线可视化为从源元素朝向目标元素的箭头。箭头始终指向目标。

#### 1.2.3 XML表示
连线需要具有进程唯一**ID**并引用现有**源**和**目标**元素。
```xml
```
#### 1.2.4 条件序列流
##### 描述
序列流可以在其上定义条件。当只有BPMN 2.0活动时,默认行为是评估传出序列流的条件。当条件评估为*true时*,选择该出线。当以这种方式选择多个序列流时,将生成多个*执行实例*并且该流程将以并行方式继续执行。
**注意:**上述内容适用于BPMN 2.0活动(和事件),但不适用于网关。网关将根据网关类型以特定方式处理具有条件的顺序流。
##### 图形符号
条件序列流可视化为规则序列流,开头有一个小钻石。条件表达式显示在序列流程旁边。

##### XML表示
条件序列流在XML中表示为常规序列流,包含**conditionExpression**子元素。请注意,目前仅支持*tFormalExpressions*,省略*xsi:type =“”*定义将默认为唯一受支持的表达式类型。
```xml
100 && order.price < 250}]]>
```
目前,conditionalExpressions **只能与UEL一起使用**。有关这些的详细信息,请参阅“ [表达式](https://www.flowable.org/docs/userguide/index.html#apiExpressions) ”一节。使用的表达式应该解析为布尔值,否则在评估条件时会抛出异常。
- 下面的示例通过getter以典型的JavaBean样式引用流程变量的数据。
```xml
100 && order.price < 250}]]>
```
- 此示例调用解析为布尔值的方法。
```xml
```
Flowable分布包含使用值和方法表达式的以下示例流程(请参阅*org.flowable.examples.bpmn.expression)*:
## 
#### 1.2.5 默认顺序流程
##### 描述
所有BPMN 2.0任务和网关都可以具有**默认的序列流**。当且仅当不能选择其他序列流时,才将该序列流选择为该活动的输出序列流。始终忽略默认顺序流的条件。
##### 图形符号
默认序列流可视化为常规序列流,开头带有*斜杠*标记。

##### XML表示
某个活动的默认顺序流由该活动的**默认属性**定义。以下XML片段显示了一个独占网关的示例,该网关具有默认顺序流,即*流2*。仅当*conditionA*和*conditionB*都评估为false时,才会选择它作为网关的传出顺序流。
```xml
${conditionA}
${conditionB}
```
这与以下图形表示相对应:
## 1.3 网关
网关用于控制执行流程(或者如BPMN 2.0描述的那样,执行的*令牌*)。网关能够*消耗*或*生成*令牌。
网关以图形方式显示为菱形,内部带有图标。该图标显示网关的类型。

### 1.3.1 排他网关
##### 描述
排他网关(也称为*XOR网关*或更专业的*基于数据*的*排他网关*)用于对流程中的**决策**建模。当执行到达此网关时,将按照定义它们的顺序评估所有传出序列流。选择条件评估为真(或没有条件集,在概念上具有在序列流上定义的*'真'*)的第一序列流以继续该过程。
**注意,在这种情况下,输出序列流的语义与BPMN 2.0中的一般情况的语义不同。通常,选择条件评估为真的所有序列流以并行方式继续,而在使用排他网关时仅选择一个序列流。如果多个序列流具有计算结果为true的条件,则选择XML中定义的第一个(并且只有那个!)以继续该过程。如果不能选择序列流,则会抛出异常。**
##### 图形符号
独家网关可视化为典型网关(菱形),内部带有*X*图标,指的是*XOR*语义。请注意,没有图标的网关默认为独占网关。BPMN 2.0规范不允许在同一流程定义中使用带有和不带X的菱形。

##### XML表示
专用网关的XML表示是直接的:定义网关的一行和在输出序列流上定义的条件表达式。请参阅有关[条件序列流](https://www.flowable.org/docs/userguide/index.html#bpmnConditionalSequenceFlow)的部分,以了解哪些选项可用于此类表达式。
举例来说,以下模型:

其中用XML表示如下:
```xml
${input == 1}
${input == 2}
${input == 3}
```
#### 1.3.2 并行网关
##### 描述
网关还可用于对流程中的并发进行建模。在流程模型中引入并发性的最直接的网关是**并行网关**,它允许您*分叉*到多个执行路径或*连接*多个传入的执行路径。
并行网关的功能基于传入和传出顺序流:
- **fork:**并行执行所有传出序列流,为每个序列流创建一个并发执行。
- **join:**到达并行网关的所有并发执行在网关中等待,直到每个传入的序列流都到达执行。然后,该过程继续经过加入网关。
请注意,如果同一并行网关有多个传入和传出顺序流,并行网关可以**同时具有分叉和连接行为**。在这种情况下,网关将首先连接所有传入的序列流,然后再分成多个并发的执行路径。
**与其他网关类型的一个重要区别是并行网关不评估条件。如果在与并行网关连接的顺序流上定义条件,则忽略它们。**
##### 图形符号
并行网关可视化为网关(菱形),内部带有*加号*,引用*AND*语义。

##### XML表示
定义并行网关需要一行XML:
```xml
```
实际行为(fork,join或两者)由连接到并行网关的顺序流定义。
例如,上面的模型归结为以下XML:
```xml
```
在上面的示例中,在启动进程后,将创建两个任务:
```java
ProcessInstance pi = runtimeService.startProcessInstanceByKey("forkJoin");
TaskQuery query = taskService.createTaskQuery()
.processInstanceId(pi.getId())
.orderByTaskName()
.asc();
List tasks = query.list();
assertEquals(2, tasks.size());
Task task1 = tasks.get(0);
assertEquals("Receive Payment", task1.getName());
Task task2 = tasks.get(1);
assertEquals("Ship Order", task2.getName());
```
当这两个任务完成后,第二个并行网关将加入两个执行,并且由于只有一个传出序列流,因此不会创建并发执行路径,只有*存档顺序*任务才会处于活动状态。
注意,并行网关不需要*平衡*(对应的并行网关的输入/输出序列流的匹配数量)。并行网关将简单地等待所有传入的序列流,并为每个传出序列流创建并发执行路径,而不受流程模型中其他构造的影响。因此,以下过程在BPMN 2.0中是合法的:

### 1.3.3 包容网关
##### 描述
可以把**包容网关(inclusive gateway)**看做排他网关与并行网关的组合。与排他网关一样,可以在包容网关的出口顺序流上定义条件,包容网关会计算条件。然而主要的区别是,包容网关与并行网关一样,可以同时选择多于一条出口顺序流。
包容网关的功能取决于其入口与出口顺序流:
- **分支:**流程会计算所有出口顺序流的条件。对于每一条计算为true的顺序流,流程都会创建一个并行执行。
- **合并:**所有到达包容网关的并行执行,都会在网关处等待。直到每一条具有流程标志(process token)的入口顺序流,都有一个执行到达。这是与并行网关的重要区别。换句话说,包容网关只会等待可以被执行的入口顺序流。在合并后,流程穿过合并并行网关继续。
请注意,如果包容网关同时具有多条入口与出口顺序流,可以**同时具有分支与合并的行为**。在这种情况下,网关首先合并所有具有流程标志的入口顺序流,然后为每一个条件计算为true的出口顺序流分裂出并行执行路径。
> 包容网关的汇聚行为比并行网关更复杂。所有到达包容网关的并行执行,都会在网关等待,直到所有“可以到达”包容网关的执行都“到达”包容网关。 判断方法为:计算当前流程实例中的所有执行,检查从其位置是否有一条到达包容网关的路径(忽略顺序流上的任何条件)。如果存在这样的执行(可到达但尚未到达),则不会触发包容网关的汇聚行为。
##### 图形符号
包含网关可视化为网关(菱形),内部带有*圆圈*符号。

##### XML表示
定义包含网关需要一行XML:
```xml
```
实际行为(fork,join或两者)由连接到包含网关的顺序流定义。
例如,上面的模型归结为以下XML:
```xml
${paymentReceived == false}
${shipOrder == true}
```
在上面的示例中,在启动流程后,如果流程变量paymentReceived == false和shipOrder == true,则将创建两个任务。如果这些过程变量中只有一个等于true,则只创建一个任务。如果没有条件计算为true,则抛出异常。这可以通过指定默认的传出顺序流来防止。在以下示例中,将创建一个任务,即船舶订单任务:
```java
HashMap variableMap = new HashMap();
variableMap.put("receivedPayment", true);
variableMap.put("shipOrder", true);
ProcessInstance pi = runtimeService.startProcessInstanceByKey("forkJoin");
TaskQuery query = taskService.createTaskQuery()
.processInstanceId(pi.getId())
.orderByTaskName()
.asc();
List tasks = query.list();
assertEquals(1, tasks.size());
Task task = tasks.get(0);
assertEquals("Ship Order", task.getName());
```
完成此任务后,第二个包含网关将加入两个执行,并且由于只有一个传出顺序流,因此不会创建并发执行路径,并且只有*存档顺序*任务将处于活动状态。
注意,不需要*平衡*包含网关(对应的包含网关的传入/传出序列流的匹配数量)。包容性网关将简单地等待所有传入的序列流,并为每个传出序列流创建并发执行路径,而不受流程模型中其他构造的影响。
### 1.3.4 基于事件的网关
##### 描述
基于事件的网关(event-based gateway)提供了根据事件做选择的方式。网关的每一条出口顺序流都需要连接至一个捕获中间事件。当流程执行到达基于事件的网关时,与等待状态类似,网关会暂停执行,并且为每一条出口顺序流创建一个事件订阅。
请注意:基于事件的网关的出口顺序流与一般的顺序流不同。这些顺序流从不实际**执行**。相反,它们用于告知流程引擎:当执行到达一个基于事件的网关时,需要订阅什么事件。有以下限制:
- 一个基于事件的网关,必须有两条或更多的出口顺序流。
- 基于事件的网关,只能连接至`intermediateCatchEvent(捕获中间事件)`类型的元素(Flowable不支持在基于事件的网关之后连接“接收任务 Receive Task”)。
- 连接至基于事件的网关的`intermediateCatchEvent`,必须只有一个入口顺序流。
##### 图形符号
基于事件的网关,用内部带有特殊图标的网关(菱形)表示。

##### XML表示
用于定义基于事件的网关的XML元素是`eventBasedGateway`。
##### 例子)
下面是一个带有基于事件的网关的示例流程。当执行到达基于事件的网关时,流程执行暂停。流程实例订阅alert信号事件,并创建一个10分钟后触发的定时器。流程引擎会等待10分钟,并同时等待信号事件。如果信号在10分钟内触发,则会取消定时器,流程沿着信号继续执行,激活Handle alert用户任务。如果10分钟内没有触发信号,则会继续执行,并取消信号订阅。

```xml
PT10M
```
### 1.4 任务
### 1.4.1 用户任务
##### 描述
一个*用户任务*被用来模拟需要由人来完成的工作。当流程执行到达此类用户任务时,将在分配给该任务的任何用户或组的任务列表中创建新任务。
##### 图形符号
用户任务可视化为典型任务(圆角矩形),左上角有一个小用户图标。

##### XML表示
用户任务在XML中定义如下。该*ID*是必需的属性,在*名称*属性是可选的。
```xml
```
用户任务也可以具有描述。实际上,任何BPMN 2.0元素都可以有描述。通过添加**文档**元素来定义描述。
```xml
Schedule an engineering meeting for next week with the new hire.
```
可以使用标准Java方式从任务中检索描述文本:
```java
task.getDescription()
```
##### 截止日期
每个任务都可以使用一个字段标志该任务的到期日期(due date)。可以使用查询API,查询在给定日期前或后到期的任务。
可以在任务定义中使用扩展指定表达式,以在任务创建时设定到期日期。该表达式**必须解析为java.util.Date,java.util.String (ISO8601格式),ISO8601时间长度(例如PT50M),或者null**。例如,可以使用在流程里前一个表单中输入的日期,或者由前一个服务任务计算出的日期。如果使用的是时间长度,则到期日期基于当前时间加上给定长度计算。例如当dueDate使用“PT30M”时,任务在从现在起30分钟后到期。
```xml
```
任务的截止日期也可以使用传递的`TaskService`或`TaskListener`使用传递来更改`DelegateTask`。
##### 用户分配
用户任务可以直接分配给用户。这是通过定义**humanPerformer**子元素来完成的。这样的*humanPerformer*定义需要一个实际定义用户的**resourceAssignmentExpression**。目前,仅支持**formalExpressions**。
```xml
...
kermit
```
**只能指定一个**用户作为任务的*humanPerformer*。在Flowable术语中,这个用户被称作**办理人(assignee)**。拥有办理人的任务,在其他人的任务列表中不可见,而只能在该办理人的**个人任务列表**中看到。
可以通过TaskService获取特定用户办理的任务:
```xml
1List tasks = taskService.createTaskQuery().taskAssignee("kermit").list();
```
任务也可以放在用户的**候选任务列表**中。在这个情况下,需要使用**potentialOwner(潜在用户)**结构。用法与*humanPerformer*结构类似。请注意需要指定表达式中的每一个元素为用户还是组(引擎无法自行判断)。
```xml
...
user(kermit), group(management)
```
使用*潜在所有者*构造定义的任务可以按如下方式检索(或与受让人的任务类似的*TaskQuery*用法):
```java
List tasks = taskService.createTaskQuery().taskCandidateUser("kermit").list();
```
这将检索kermit是**候选用户的**所有任务,换句话说,正式表达式包含*user(kermit)*。这还将检索**分配给kermit所属的组的**所有任务(例如,*组(管理)*,如果kermit是该组的成员并且使用了Flowable标识组件)。用户的组在运行时解析,可以通过[IdentityService](https://www.flowable.org/docs/userguide/index.html#apiEngine)进行管理。
如果没有给出关于给定文本字符串是用户还是组的具体信息,则引擎默认为分组。以下内容与*宣布组(会计)*时相同。
```xml
accountancy
```
##### 用于任务指派的Flowable扩展
很明显,当指派关系不复杂时,这种用户与组的指派方式十分笨重。为避免这种复杂性,可以在用户任务上使用[自定义扩展](https://tkjohn.github.io/flowable-userguide/#bpmnCustomExtensions)。
- **assignee(办理人)属性**:这个自定义扩展用于直接将用户指派至用户任务。
```xml
1
```
与[上面](https://tkjohn.github.io/flowable-userguide/#bpmnUserTaskAssignment)定义的**humanPerformer**结构效果完全相同。
- **candidateUsers(候选用户)属性**:这个自定义扩展用于为任务指定候选用户。
```xml
```
与使用[上面](https://tkjohn.github.io/flowable-userguide/#bpmnUserTaskAssignment)定义的**potentialOwner**结构效果完全相同。请注意不需要像在*potentialOwner*中一样,使用*user(kermit)*的声明,因为这个属性只能用于用户。
- **candidateGroups(候选组)attribute**:这个自定义扩展用于为任务指定候选组。
```xml
```
与使用[上面](https://tkjohn.github.io/flowable-userguide/#bpmnUserTaskAssignment)定义的**potentialOwner**结构效果完全相同。请注意不需要像在*potentialOwner*中一样,使用*group(management)*的声明,因为这个属性只能用于组。
- 可以定义在一个用户任务上同时定义*candidateUsers*与*candidateGroups*。
请注意:尽管Flowable提供了[IdentityService](https://tkjohn.github.io/flowable-userguide/#apiEngine)身份管理组件,但并不会检查给定的用户是否实际存在。这是为了便于将Flowable嵌入应用时,与已有的身份管理解决方案进行集成。
##### 自定义身份关联类型
在[用户指派](https://tkjohn.github.io/flowable-userguide/#bpmnUserTaskAssignment)中已经介绍过,BPMN标准支持单个指派用户即**hunamPerformer**,或者由一组用户构成**potentialOwners**潜在用户池。另外,Flowable为用户任务定义了[扩展属性元素](https://tkjohn.github.io/flowable-userguide/#bpmnUserTaskUserAssignmentExtension),用于代表任务的**办理人**或者**候选用户**。
Flowable支持的身份关联(identity link)类型有:
```java
public class IdentityLinkType {
/* Flowable native roles */
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的身份管理实现并不适用于生产环境,而需要在支持的认证概要下自行扩展。
如果需要添加额外的关联类型,可按照下列语法,使用自定义资源作为扩展元素:
```xml
user(kermit), group(management)
```
自定义关联表达式添加至*TaskDefinition*类:
```java
protected Map> customUserIdentityLinkExpressions =
new HashMap>();
protected Map> customGroupIdentityLinkExpressions =
new HashMap>();
public Map> getCustomUserIdentityLinkExpressions() {
return customUserIdentityLinkExpressions;
}
public void addCustomUserIdentityLinkExpression(
String identityLinkType, Set idList) {
customUserIdentityLinkExpressions.put(identityLinkType, idList);
}
public Map> getCustomGroupIdentityLinkExpressions() {
return customGroupIdentityLinkExpressions;
}
public void addCustomGroupIdentityLinkExpression(
String identityLinkType, Set idList) {
customGroupIdentityLinkExpressions.put(identityLinkType, idList);
}
```
这些方法将在运行时,由*UserTaskActivityBehavior*的*handleAssignments*方法调用。
最后,需要扩展*IdentityLinkType*类,以支持自定义身份关联类型:
```java
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事件上的[任务侦听器](https://www.flowable.org/docs/userguide/index.html#taskListeners)委托自定义分配逻辑:
```xml
```
将`DelegateTask`传递到`TaskListener`执行可以设置受让人和候选用户/组:
```java
public class MyAssignmentHandler implements TaskListener {
public void notify(DelegateTask delegateTask) {
// Execute custom identity lookups here
// and then for example call following methods:
delegateTask.setAssignee("kermit");
delegateTask.addCandidateUser("fozzie");
delegateTask.addCandidateGroup("management");
...
}
}
```
当使用Spring时,可以按上面章节的介绍使用自定义指派属性,并交由使用[任务监听器](https://tkjohn.github.io/flowable-userguide/#taskListeners)、带有[表达式](https://tkjohn.github.io/flowable-userguide/#springExpressions)的Spring bean,监听任务*创建*事件。在下面的例子中,通过调用`ldapService` Spring bean的`findManagerOfEmployee`方法设置办理人。传递的*emp*参数是一个流程变量。
```xml
```
这也适用于候选用户和组:
```xml
```
请注意,这仅在调用方法的返回类型为`String`或`Collection`(对于候选用户和组)时才有效:
```java
public class FakeLdapService {
public String findManagerForEmployee(String employee) {
return "Kermit The Frog";
}
public List findAllSales() {
return Arrays.asList("kermit", "gonzo", "fozzie");
}
}
```
#### 1.4.2 脚本任务
##### 描述
脚本任务是一种自动活动。当进程执行到达脚本任务时,将执行相应的脚本。
属性:
- **name**:任务属性,用于指示任务的名称
- **type**:任务属性,其值必须为“script”以指示任务的类型
- **scriptFormat**:指示脚本语言的扩展属性(例如,javascript,groovy)
- **script**:要执行的脚本,定义为名为“script”的字段元素中的字符串
- **autoStoreVariables**:可选的任务属性标志(默认值:false),指示脚本中定义的变量是否将存储在执行上下文中(请参阅下面的注释)
- **resultVariableName**:可选的任务属性,当存在时,将在Execution上下文中使用脚本评估结果存储具有指定名称的变量(请参阅下面的注释)
##### 图形符号
脚本任务可视化为典型的BPMN 2.0任务(圆角矩形),矩形的左上角有一个小*脚本*图标。

##### XML表示
通过指定**脚本**和**scriptFormat**来定义脚本任务。
```javascript
```
**scriptFormat**属性的值,必须是兼容[JSR-223](http://jcp.org/en/jsr/detail?id=223)(Java平台脚本)的名字。默认情况下,JavaScript包含在每一个JDK中,因此不需要添加任何JAR文件。如果想使用其它(兼容JSR-223的)脚本引擎,则需要在classpath中添加相应的jar,并使用适当的名字。例如,Flowable单元测试经常使用Groovy,因为其语法与Java十分相似。
请注意Groovy脚本引擎与groovy-all JAR捆绑在一起。在Groovy 2.0版本以前,脚本引擎是Groovy JAR的一部分。因此,必须添加如下依赖:
```xml
org.codehaus.groovy
groovy-jsr223
2.x.x
```
##### 脚本中的变量
可以在脚本中使用通过脚本任务中到达的执行可访问的所有过程变量。在该示例中,脚本变量*“inputArray”*实际上是一个过程变量(整数数组)。
```javascript
```
也可以简单地调用*execution.setVariable("variableName", variableValue)*,在脚本中设置流程变量。默认情况下,变量不会自动储存(**请注意,在一些早期版本中是会储存的!**)。可以将`scriptTask`的`autoStoreVariables`参数设置为`true`,以自动保存任何在脚本中定义的变量(例如上例中的*sum*)。然而这并不是最佳实践。**最佳实践是显式调用execution.setVariable()**,因为在JDK近期的一些版本中,某些脚本语言不能自动保存变量。查看[这个链接](http://www.jorambarrez.be/blog/2013/03/25/bug-on-jdk-1-7-0_17-when-using-scripttask-in-activiti/)了解更多信息。
```xml
```
此参数的缺省值是`false`,这意味着如果从脚本任务定义中省略该参数,则所有声明的变量将仅在脚本的持续时间内存在。
以下是如何在脚本中设置变量的示例:
```xml
```
注意:以下名称是保留的,**不能用作**变量名:**out,out:print,lang:import,context,elcontext**。
##### 脚本任务的结果
脚本任务的返回值,可以通过为脚本任务定义的*'flowable:resultVariable'*属性设置为流程变量。可以是已经存在的,或者新的流程变量。如果指定为已存在的流程变量,则流程变量的值会被脚本执行的结果值覆盖。如果不指定结果变量名,则脚本结果值将被忽略。
```xml
```
在上面的示例中,脚本执行的结果(已解析表达式*'#{echo}'的值*)在脚本完成后设置为名为*'myVar'*的流程变量。
##### 安全
当使用*javascript*作为脚本语言来使用*安全脚本*时也是可能的。请参阅[安全脚本部分](https://www.flowable.org/docs/userguide/index.html#advancedSecureScripting)。
#### 1.4.3 Java服务任务
##### 描述
Java服务任务用于调用外部Java类。
##### 图形符号
服务任务可视化为圆角矩形,左上角有一个小齿轮图标。

##### XML表示
有四种方法声明如何调用Java逻辑:
- 指定实现了JavaDelegate或ActivityBehavior的类
- 调用解析为委托对象(delegation object)的表达式
- 调用方法表达式(method expression)
- 对值表达式(value expression)求值
要指定在流程执行期间调用的类,需要由**flowable:class**属性提供完全限定的类名。
```xml
```
有关如何使用此类的更多详细信息,请参阅[实现部分](https://www.flowable.org/docs/userguide/index.html#bpmnJavaServiceTaskImplementation)。
也可以使用解析为对象的表达式。此对象必须遵循与`flowable:class`使用该属性时创建的对象相同的规则(请参阅[参考资料](https://www.flowable.org/docs/userguide/index.html#bpmnJavaServiceTaskImplementation))。
```xml
```
这里`delegateExpressionBean`是一个实现`JavaDelegate`接口的bean,在例如Spring容器中定义。
要指定应评估的UEL方法表达式,请使用属性**flowable:expression**。
```xml
```
`printMessage`将在名为的命名对象上调用方法(不带参数)`printer`。
也可以使用表达式中使用的方法传递参数。
```xml
```
`printMessage`将在名为的对象上调用方法`printer`。传递的第一个参数是`DelegateExecution`,默认情况下可用于表达式上下文中,可用作`execution`。传递的第二个参数是`myVar`当前执行中具有name的变量的值。
要指定应评估的UEL值表达式,请使用属性**flowable:expression**。
```xml
```
财产的getter方法`ready`,`getReady`(不带参数),将在叫的bean被调用`split`。命名对象在执行的过程变量中解析,并在Spring上下文中(如果适用)。
##### 实现
要实现可在流程执行期间调用的类,该类需要实现*org.flowable.engine.delegate.JavaDelegate*接口并在*execute*方法中提供所需的逻辑。当流程执行到达此特定步骤时,它将执行该方法中定义的逻辑,并以默认的BPMN 2.0方式离开活动。
例如,让我们创建一个Java类,可用于将流程变量String更改为大写。这个类需要实现*org.flowable.engine.delegate.JavaDelegate*接口,这需要我们实现*execute(DelegateExecution)*方法。这个操作将由引擎调用,并且需要包含业务逻辑。可以通过[DelegateExecution](http://www.flowable.org/javadocs/org/flowable/engine/delegate/DelegateExecution.html)接口访问和操作流程实例信息(如流程变量)(单击链接以获取其操作的详细Javadoc)。
```java
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)*。这意味着该类不能有任何成员变量,并需要是线程安全的,因为它可能会在不同线程中同时执行。这也影响了[字段注入](https://tkjohn.github.io/flowable-userguide/#serviceTaskFieldInjection)的使用方法。(译者注:原文可能较老,不正确。5.21中,flowable:class指定的类,会在流程实例启动时,为每个活动分别进行实例化。不过,当该活动在流程中重复执行,或者为多实例时,使用的都会是同一个类实例。)
在流程定义中引用(如`flowable:class`)的类,**不会在部署时实例化**。只有当流程执行第一次到达该类使用的地方时,才会创建该类的实例。如果找不到这个类,会抛出`FlowableException`。这是因为部署时的环境(更准确的说*classpath*),与实际运行的环境经常不一样。例如当使用*ant*或者Flowable应用中业务存档上传的方式部署的流程,其classpath中不会自动包含流程引用的类。
[[内部:非公开实现类\]](https://tkjohn.github.io/flowable-userguide/#internal)也可以使用实现了*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元素**。
```xml
```
该类`ToUpperCaseFieldInjected`有一个`text`类型的字段`org.flowable.engine.delegate.Expression`。调用时`text.getValue(execution)`,`Hello World`将返回配置的字符串值:
```java
public class ToUpperCaseFieldInjected implements JavaDelegate {
private Expression text;
public void execute(DelegateExecution execution) {
execution.setVariable("var", ((String)text.getValue(execution)).toUpperCase());
}
}
```
或者,对于长文本(例如,内联电子邮件),可以使用*'flowable:string'*子元素:
```xml
This is a long string with a lot of words and potentially way longer even!
```
可以使用表达式在运行时动态解析注入的值。这种表达式可以使用流程变量,或者(若使用Spring)Spring定义的Bean。在[服务任务实现](https://tkjohn.github.io/flowable-userguide/#bpmnJavaServiceTaskImplementation)中提到过,当服务任务中使用*flowable:class*属性时,该Java类的实例在所有流程实例中共享。要动态地为字段注入值,可以在`org.flowable.engine.delegate.Expression`中注入值或方法表达式,它们会通过`execute`方法传递的`DelegateExecution`计算/调用。
下面的示例类使用了注入的表达式,并使用当前的`DelegateExecution`解析它们。调用*genderBean*方法时传递的是*gender*变量。完整的代码与测试可以在`org.flowable.examples.bpmn.servicetask.JavaServiceTaskTest.testExpressionFieldInjection`中找到
```java
${genderBean.getGenderString(gender)}
Hello ${gender == 'male' ? 'Mr.' : 'Mrs.'} ${name}
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更简洁。
```xml
```
##### 字段注入与线程安全
通常情况下,在服务任务中使用Java委托与字段注入是线程安全的。然而,有些情况下不能保证线程安全。这取决于设置,或Flowable运行的环境。
当使用*flowable:class*属性时,使用字段注入总是线程安全的(译者注:仍不完全安全,如对于多实例服务任务,使用的是同一个实例)。对于引用了某个类的每一个服务任务,都会实例化新的实例,并且在创建实例时注入一次字段。在不同的任务或流程定义中多次使用同一个类没有问题。
当使用*flowable:expression*属性时,不能使用字段注入。只能通过方法调用传递变量。总是线程安全的。
当使用*flowable:delegateExpression*属性时,委托实例的线程安全性,取决于表达式解析的方式。如果该委托表达式在多个任务或流程定义中重复使用,并且表达式总是返回相同的示例,则字段注入**不是线程安全的**。让我们看几个例子。
假设表达式为*${factory.createDelegate(someVariable)}*,其中factory为引擎可用的Java bean(例如使用Spring集成时的Spring bean),并在每次表达式解析时创建新的实例。这种情况下,使用字段注入时,没有线程安全性问题:每次表达式解析时,都会注入新实例的字段。
然而,如果表达式为*${someJavaDelegateBean}*,解析为JavaDelegate的实现,并且在创建单例的环境(如Spring)中运行。当在不同的任务或流程定义中使用这个表达式时,表达式总会解析为相同的实例。这种情况下,使用字段注入不是线程安全的。例如:
```xml
```
这段示例代码有两个服务任务,使用同一个委托表达式,但是*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:
```xml
```
第一个bean是一个普通的Spring bean,因此是一个单例。第二个*原型*作为作用域,每次请求bean时,Spring容器都会返回一个新实例。
给定以下流程定义:
```xml
```
我们有四个服务任务,第一个和第二个使用*$ {prototypeDelegateExpressionBean}*委托表达式,第三个和第四个使用*$ {singletonDelegateExpressionBean}*委托表达式。
让我们先看看原型bean:
```java
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}*时都会创建一个新实例。可以在这里注入字段而没有任何问题,我们可以在这里看到三个*表达式*成员字段。
然而,单例bean看起来略有不同:
```java
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**:
```xml
```
其中*testExecutionListener*解析为ExecutionListener接口的一个实现的实例:
```java
@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'*,则会将结果值设置为局部变量。 如果不指定结果变量名,则服务任务的结果值将被忽略。
```xml
```
在上例中,服务执行的结果(流程变量或Spring bean中,使用*'myService'*名字所获取的对象,调用*'doSomething()'*方法的返回值),在服务执行完成后,会设置为名为*'myVar'*的流程变量。
##### 可触发
一种常见的模式是发送JMS消息或HTTP调用触发外部服务,然后流程实例进入等待状态。之后外部系统会回复响应,流程实例继续执行下一个活动。在默认的BPMN中,需要使用服务任务和接收任务(receive task)。但是这样会引入竞争条件:外部服务的响应可能会早于流程实例持久化及接收任务激活。为了解决这个问题,Flowable为服务任务增加了triggerable(可触发)属性,可以将服务任务转变为执行服务逻辑,并在继续执行之前等待外部触发的任务。如果在可触发服务任务上同时设置异步(async 为 true),则流程实例会先持久化,然后在异步作业中执行服务任务逻辑。在BPMN XML中,可以这样实现可触发服务任务:
```xml
```
外部服务可以同步或异步地触发等待中的流程实例。为了避免乐观锁异常,最好使用异步触发。默认情况下,异步作业是排他的,也就是说流程实例会被锁定,以保证流程实例中的其他活动不会影响到触发器的逻辑。可以使用RuntimeService的triggerAsync方法,异步触发等待中的流程实例。当然还是可以使用RuntimeService的trigger方法,同步触发。
##### 处理异常
当执行自定义逻辑时,通常需要捕获并在流程中处理特定的业务异常。Flowable提供了多种选择。
###### 抛出BPMN错误
可以在服务任务或脚本任务的用户代码中抛出BPMN错误。可以在Java委托、脚本、表达式与委托表达式中,抛出特殊的FlowableException:*BpmnError*。引擎会捕获这个异常,并将其转发至合适的错误处理器,如错误边界事件或错误事件子流程。
```java
public class ThrowBpmnErrorDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) throws Exception {
try {
executeBusinessLogic();
} catch (BusinessException e) {
throw new BpmnError("BusinessExceptionOccurred");
}
}
}
```
构造函数的参数是错误代码。错误代码决定了处理这个错误的错误处理器。参见[错误边界事件](https://tkjohn.github.io/flowable-userguide/#bpmnBoundaryErrorEvent)了解如何捕获BPMN错误。
这个机制**只应该用于业务错误**,需要通过流程中定义的错误边界事件或错误事件子流程处理。技术错误应该通过其他异常类型表现,并且通常不在流程内部处理。
###### 异常映射
也可以使用`mapException`扩展,直接将Java异常映射至业务异常(错误)。单映射是最简单的形式:
```xml
org.flowable.SomeException
```
在上面的代码中,如果服务任务抛出`org.flowable.SomeException`的实例,引擎会捕获该异常,并将其转换为带有给定errorCode的BPMN错误。然后就可以像普通BPMN错误完全一样地处理。其他的异常没有映射,仍将抛出至API调用处。
也可以在单行中使用`includeChildExceptions`属性,映射特定异常的所有子异常。
```xml
org.flowable.SomeException
```
上面的代码中,Flowable会将`SomeException`的任何直接或间接的子类,转换为带有指定错误代码的BPMN错误。 当未指定`includeChildExceptions`时,视为“false”。
默认映射最泛用。默认映射是一个不指定类的映射,可以匹配任何Java异常:
```xml
```
除了默认映射,会按照从上至下的顺序检查映射,使用第一个匹配的映射。只在所有映射都不能成功匹配时使用默认映射。 只有第一个不指定类的映射会作为默认映射。默认映射忽略`includeChildExceptions`。
###### 异常顺序流
[[内部:非公开实现类\]](https://tkjohn.github.io/flowable-userguide/#internal)
也可以选择在发生异常时,将流程执行路由至另一条路径。下面是一个例子。
```xml
```
这里,服务任务有两个输出序列流,名为`exception`和`no-exception`。如果存在异常,序列流ID将用于指导流程流程:
```java
public class ThrowsExceptionBehavior implements ActivityBehavior {
public void execute(DelegateExecution execution) {
String var = (String) execution.getVariable("想`");
String sequenceFlowToTake = null;
try {
executeLogic(var);
sequenceFlowToTake = "no-exception";
} catch (Exception e) {
sequenceFlowToTake = "exception";
}
DelegateHelper.leaveDelegate(execution, sequenceFlowToTake);
}
}
```
##### 在JavaDelegate中使用Flowable服务
对于某些用例,可能需要在Java服务任务中使用Flowable服务(例如,如果callActivity不适合您的需要,则通过RuntimeService启动流程实例)。
```java
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。
```java
@Component("startProcessInstanceDelegate")
public class StartProcessInstanceTestDelegateWithInjection {
@Autowired
private RuntimeService runtimeService;
public void startProcess() {
runtimeService.startProcessInstanceByKey("oneTaskProcess");
}
}
```
**重要技术提示:**由于服务调用是在当前事务中完成的,因此在服务任务执行前产生或修改的数据尚未存入数据库。所有API调用都基于数据库数据处理,这意味着这些未提交的修改在服务任务的API调用中“不可见”。
#### 1.4.4 Web服务任务
##### 描述
Web Service任务用于同步调用外部Web服务。
##### 图形符号
Web服务任务的可视化方式与Java服务任务相同。

##### XML表示
要使用Web服务,我们需要导入其操作和复杂类型。这可以通过使用指向Web服务的WSDL的import标记自动完成:
```xml
```
按照上面的声明,Flowable会导入定义,但不会创建条目定义(item definition)与消息。如果需要调用一个名为’prettyPrint’的方法,则需要先为请求及回复消息创建对应的消息与条目定义:
```xml
```
在声明服务任务前,需要定义实际引用Web服务的BPMN接口与操作。基本上,是定义“接口”与所需的“操作”。对每一个操作都可以重复使用之前定义的“传入”与“传出”消息。例如,下面的声明定义了“counter”接口及“prettyPrintCountOperation”操作:
```xml
tns:prettyPrintCountRequestMessage
tns:prettyPrintCountResponseMessage
```
然后,我们可以使用## WebService实现和对Web服务操作的引用来声明Web服务任务。
```xml
```
##### Web服务任务IO规范
除非使用简化方法处理输入与输出数据关联(见下),否则需要为每个Web服务任务声明IO规范,指出任务的输入与输出是什么。这个方法很简单,也兼容BPMN 2.0。在prettyPrint例子中,根据之前声明的条目定义,定义输入与输出:
```xml
dataInputOfServiceTask
dataOutputOfServiceTask
```
##### Web服务任务数据输入关联
有两种指定数据输入关联的方式:
- 使用表达式
- 使用简化方法
使用表达式指定数据输入关联,需要定义源及目标条目,并指定每个条目与字段的关联。下面的例子中,我们针对每个条目,指定prefix与suffix字段:
```xml
dataInputOfProcess
dataInputOfServiceTask
${dataInputOfProcess.prefix}
${dataInputOfServiceTask.prefix}
${dataInputOfProcess.suffix}
${dataInputOfServiceTask.suffix}
```
也可以使用更简单明了的简化方法。'sourceRef’元素是一个Flowable变量名,'targetRef’是条目定义的参数。在下面的例子里,将’PrefixVariable’变量的值关联至’prefix’字段,并将’SuffixVariable’变量的值关联至’suffix’字段。
```xml
PrefixVariable
prefix
SuffixVariable
suffix
```
##### Web服务任务数据输出关联
有两种方法可以指定数据输出关联:
- 使用表达式
- 使用简单的方法
使用表达式指定数据输出关联,需要定义目标变量及源表达式。这种方法很简单,与数据输入关联类似:
```xml
dataOutputOfProcess
${dataOutputOfServiceTask.prettyPrint}
```
也可以使用更简单明了的简化方法。'sourceRef’是条目定义的参数,'targetRef’元素是Flowable变量名。这种方法很简单,与数据输入关联类似:
```xml
prettyPrint
OutputVariable
```
#### 1.4.5 业务规则任务
##### 描述
业务规则任务(business rule task)用于同步地执行一条或多条规则。Flowable使用名为Drools Expert的Drools规则引擎执行业务规则。目前,业务规则中包含的.drl文件,必须与定义了业务规则服务并执行规则的流程定义一起部署。这意味着流程中使用的所有.drl文件都需要打包在流程BAR文件中,与任务表单等类似。要了解如何为Drools Expert创建业务规则,请访问位于[JBoss Drools](http://www.jboss.org/drools/documentation)的Drools文档。
如果想要使用自己的规则任务实现,比如希望通过不同方法使用Drools,或者想使用完全不同的规则引擎,则可以使用BusinessRuleTask的class或expression属性。这样它会与[服务任务](https://tkjohn.github.io/flowable-userguide/#bpmnJavaServiceTask)的行为完全相同。
##### 图示
业务规则任务显示为带有表格图标的圆角矩形

##### XML表示
要执行与流程定义在同一个BAR文件中部署的一条或多条业务规则,需要定义输入与结果变量。输入变量可以用流程变量的列表定义,使用逗号分隔。输出变量只能有一个变量名,将执行业务规则后的输出对象存储至流程变量。请注意结果变量会包含对象的List。如果没有指定结果变量名,默认为org.flowable.engine.rules.OUTPUT。
下面的业务规则任务,执行与流程定义一起部署的所有业务规则:
```xml
```
业务规则任务还可以配置为仅从部署的.drl文件中执行一组定义的规则。必须为此指定由逗号分隔的规则名称列表。
```xml
```
在这种情况下,只执行rule1和rule2。
也可以定义需要从执行中排除的规则列表。
```xml
```
这个例子中,除了rule1与rule2之外,其它所有与流程定义一起部署在同一个BAR文件中的规则都会被执行。
前面提到过,还可以自行指定BusinessRuleTask的实现:
```xml
```
现在,BusinessRuleTask的行为与ServiceTask完全相同,但仍然保持BusinessRuleTask图标可视化我们在此处进行业务规则处理。
### 1.4.6 邮件任务
Flowable让你可以通过自动的邮件服务任务(email task),增强业务流程。可以向一个或多个收信人发送邮件,支持cc,bcc,HTML文本,等等。请注意邮件任务**不是**BPMN 2.0规范的“官方”任务(所以也没有专用图标)。因此,在Flowable中,邮件任务实现为一种特殊的服务任务。
##### 配置邮件服务器
Flowable引擎使用支持SMTP的外部邮件服务器发送邮件。为了发送邮件,引擎需要了解如何连接邮件服务器。可以在*flowable.cfg.xml*配置文件中设置下面的参数:
| 属性 | 需要? | 描述 |
| --------------------- | -------------------------- | ------------------------------------------------------------ |
| mailServerHost | 没有 | 邮件服务器的主机名(如mail.mycorp.com)。默认为`localhost` |
| mailServerPort | 是的,如果没有在默认端口上 | 邮件服务器的SMTP端口。默认值为*25* |
| mailServerForceTo | 没有 | 如果设置,它将在发送电子邮件时用作邮件任务中配置的to,cc和/或bcc的替代。 |
| mailServerDefaultFrom | 没有 | 电子邮件发件人的默认电子邮件地址,如果用户未提供。默认情况下,这是*flowable@flowable.org* |
| mailServerUsername | 若服务器需要 | 部分邮件服务器发信时需要进行认证。默认为空。 |
| mailServerPassword | 若服务器需要 | 部分邮件服务器发信时需要进行认证。默认为空。 |
| mailServerUseSSL | 若服务器需要 | 部分邮件服务器要求ssl通信。默认设置为false。 |
| mailServerUseTLS | 若服务器需要 | 部分邮件服务器要求TLS通信(例如gmail)。默认设置为false。 |
##### 定义邮件任务
邮件任务实现为特殊的[服务任务](https://tkjohn.github.io/flowable-userguide/#bpmnJavaServiceTask),将服务任务的*type*定义为*'mail'*进行设置。
```xml
```
邮件任务通过[字段注入](https://tkjohn.github.io/flowable-userguide/#serviceTaskFieldInjection)配置。这些参数的值可以使用EL表达式,并将在流程执行运行时解析。可以设置下列参数:
| 参数 | 必填? | 描述 |
| --------------------- | ----- | ------------------------------------------------------------ |
| to | 是 | 邮件的收信人。可以使用逗号分隔的列表定义多个接收人 |
| from | 否 | 邮件的发信人地址。如果不设置,会使用[默认配置](https://tkjohn.github.io/flowable-userguide/#bpmnEmailTaskServerConfiguration)的地址 |
| subject | 否 | 邮件的主题 |
| cc | 否 | 邮件的抄送人。可以使用逗号分隔的列表定义多个接收人 |
| bcc | 否 | 邮件的密送人。可以使用逗号分隔的列表定义多个接收人 |
| charset | 否 | 可以指定邮件的字符集,对许多非英语语言很必要。 |
| html | 否 | 邮件的HTML文本 |
| text | 否 | 邮件的内容,用于纯文本邮件。对于不支持富文本内容的客户端,可以与*html*一起使用。邮件客户端可以回退为显式纯文本格式。 |
| htmlVar | 否 | 存储邮件HTML内容的流程变量名。与*html*参数的最大区别,是这个参数会在邮件任务发送前,使用其内容进行表达式替换。 |
| textVar | 否 | 存储邮件纯文本内容的流程变量名。与*text*参数的最大区别,是这个参数会在邮件任务发送前,使用其内容进行表达式替换。 |
| ignoreException | 否 | 处理邮件失败时,是忽略还是抛出FlowableException。默认设置为false。 |
| exceptionVariableName | 否 | 如果设置*ignoreException = true*,而处理邮件失败时,则使用给定名字的变量保存失败信息 |
##### 示例 usage
下面的XML代码片段是使用邮件任务的示例。
```xml
Hello ${male ? 'Mr.' : 'Mrs.' } ${recipientName},
As of ${now}, your order has been processed and shipped.
Kind regards,
TheCompany.