多实例

本文中内容和案例节选自贺波老师的《深入Activiti流程引擎:核心原理与高阶实战》一书。这本书中的内容更为全面、详尽,对系统学习和深入掌握Activiti/Flowable的用法非常有帮助,推荐给大家。

QQ交流群1:633168411 已满
QQ交流群2: 582010059

参照本书录制的讲解视频地址

博客地址

BPMN2.0 中引入了多实例的概念,它是在业务流程中定义“重复”环节的一个方法,Flowable 对其予以了支持。配置为多实例的活动在流程运行时会创建多个活动实例,既可以顺序依次执行也 可以并行同时执行,效果相当于在这个活动上循环反复执行,并在满足设置的结束条件后退出循环。 BPMN 中的多种节点可以设置为多实例,从而在流程中实现各种“重复”执行的特性,满足特定的 需求场景。本章将介绍 Flowable 中的多实例的配置方法,并结合示例介绍多实例在多种常见的活动 或子流程上的应用。

概述

BPMN 中的多实例活动是实现循环的方式之一。虽然循环总是可以通过活动连接网关将后续顺 序流指向自己或前面的活动来实现,但是多实例活动是可以在某些情况下实现起来更简单。如果想 让某些特定的活动重复执行多次,我们可以将该活动设置为多实例,让其按照配置来执行相应的次 数。

概念

多实例活动是在业务流程中定义重复环节的方式。从开发角度讲,多实例相当于循环,它可以 根据给定的集合,为每个元素顺序或并行地执行某个环节,甚至是一个子流程。 所谓多实例是在一个普通的活动上添加了额外的属性定义(被称作“多实例特性”),这样活 动在运行时被执行多次。以下几种活动可以设置为多实例活动:

  • 用户任务(User Task)
  • 脚本任务(Script Task)
  • 服务任务(Java Service Task)
  • Web 服务任务(Web Service Task)
  • 业务规则任务(Business Rule Task)
  • 电子邮件任务(Email Task)
  • 手动任务(Manual Task)
  • 接收任务(Receive Task)
  • (嵌入式)子流程(Embedded Sub-Process)
  • 调用活动(Call Activity) 需要注意的是,网关(Gateway)和事件(Event)不能设置为多实例的。 按照 BPMN 2.0 规范的要求,在 Flowable 的设计中,为每个实例创建的执行的父执行都有如表所示的变量:

多实例的父执行内置变量

变量名含义
nrOfInstances实例总数。
nrOfActiveInstances当前活动的(尚未完成的)实例数量。对于串行多实例来说,这个值始终是 1。
nrOfCompletedInstances已经完成的实例的数量。

以上这三个变量值可以通过 execution.getVariable(x)方法获取。 另外,每个创建的执行都会有一个执行本地变量,如表 24.2 所示,它对于其它执行不可见, 并且不存储在流程实例级别:

多实例的子执行内置变量

变量名含义
loopCounter表示特定实例的在循环的索引值。loopCounter 变量可以使用 flowable 的 elementIndexVariable 属性重命名。

多实例配置

图形标记

如果一个活动被设置为多实例,则在活动底部用三条短线表示。通过短线的朝向决定多实例的 类型,三条竖线表示是并行多实例(并行执行),三条横线表示串行多实例(顺序执行)。如图所示:

multiInstance

XML内容

如果要将一个活动设置为多实例活动 ,需要为该活动的XML元素必须设置一个 multiInstanceLoopCharacteristics 子元素:

<multiInstanceLoopCharacteristics isSequential="false|true">
...
</multiInstanceLoopCharacteristics>
  1. 多实例类型的配置

其中,isSequential属性表示多实例的类型,isSequential="false"表示是并行多实例 , isSequential="true"表示是串行多实例。

  1. 多实例的数量计算 在进入活动时会计算一次多实例的数量,Flowable 提供了多种配置方法进行配置:

2.1. 使用 loopCardinality 子元素指定 这种方法是使用 loopCardinality 子元素直接指定一个数字作为多实例的数量:

    <multiInstanceLoopCharacteristics isSequential="false|true">
         <loopCardinality>6</loopCardinality>
    </multiInstanceLoopCharacteristics>

使用这种方式时,也可以配置为执行结果为整数的表达式:

<multiInstanceLoopCharacteristics isSequential="false|true">
    <loopCardinality>${nrOfOrders-nrOfCancellations}</loopCardinality>
</multiInstanceLoopCharacteristics>

2.2、使用 loopDataInputRef 子元素指定 另一种定义实例数的方法是使用 loopDataInputRef 子元素指定设置一个类型为集合的流程变量 名。对于集合中的每个元素,都会创建一个实例。另外,也可以使用 inputDataItem 子元素配置存 储集合元素的变量名(可选)。以用户任务为例,使用方式见如下的示例:

<userTask id="userTask1" name="多实例用户任务" flowable:assignee="${assignee}">
    <multiInstanceLoopCharacteristics isSequential="false">
        <loopDataInputRef>assigneeList</loopDataInputRef>
        <inputDataItem name="assignee" />
    </multiInstanceLoopCharacteristics>
</userTask>

上面通过 loopDataInputRef 子元素指定了类型为集合的 assigneeList 流程变量,同时通过 inputDataItem 子元素设置 assignee。假设 assigneeList 变量的值包括 zhangsan、lisi、wangwu,那么 在以上的配置中,三个用户任务会同时创建(并行多实例)。并且,每个执行都会拥有一个名为 assignee 的流程变量,它会包含集合中的对应元素,在本例中用于分配用户任务。 使用这种方式配置,存在以下两个缺点:1)loopDataInputRef 和 inputDataItem 的名称不太好 记;2)根据 BPMN 2.0 格式定义,它们不能包含表达式。为了解决这个问题,Flowable 提供了下面 的第3种方式。 2.3、通过 collection 和 elementVariable 属性指定 为了解决第②种方式中存在的问题,Flowable 为 multiInstanceCharacteristics 引入了 collection 和 elementVariable 属性:

  <userTask id="userTask1" name="多实例用户任务" flowable:assignee="${assignee}">
  <multiInstanceLoopCharacteristics isSequential="true"
  flowable:collection="${myTaskUserService.getUsersOfTask()}"
  flowable:elementVariable="assignee" >
  </multiInstanceLoopCharacteristics>
  </userTask>

对比一下可以看出,这里其实是使用 collection 属性替代了第2种方式的 loopDataInputRef 子 元素,使用 elementVariable 属性替代了 inputDataItem 子元素,不同之处是 collection 属性可以配置 为一个表达式,使用起来更加灵活。 需要注意的是,collection 属性会作为表达式进行解析。如果表达式执行结果为字符串而不是 集合,不论是因为本身配置的就是静态字符串值,还是表达式执行结果为字符串,这个字符串都会 被当做变量名,其变量值为实际的集合。我们看下面的示例:

<userTask id="userTask1" name="多实例用户任务" flowable:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="true"
flowable:collection="assigneeList" flowable:elementVariable="assignee" >
</multiInstanceLoopCharacteristics>
</userTask>

在以上配置中,需要将集合存储在 assigneeList 流程变量中。 为了进一步说明,我们再看下一个示例:

<userTask id="userTask1" name="多实例用户任务" flowable:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="true"
flowable:collection="${myTaskUserService.getCollectionVariableName()}"
flowable:elementVariable="assignee" >
</multiInstanceLoopCharacteristics>
</userTask>

在以上配置中,如果表达式 myTaskUserService.getCollectionVariableName()执行结果是一个字 符串值,引擎就会用这个字符串值作为变量名,获取流程变量保存的集合。 3、多实例结束条件配置 Flowable 默认在所有的实例都完成后,多实例活动才结束。 同时, Flowable 提供了 completionCondition 子元素用于配置评估是否结束多实例的表达式,这个表达式在每个实例结束时 执行一次,如果表达式计算结果为 true,则当前多实例中所有剩余的实例将被销毁,并且多实例活 动结束,流程离开当前活动继续执行;如果表达式计算结果为 false,则继续等待剩下的实例完成。 我们看下面一个示例:

<userTask id="miTasks" name=" " flowable:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="false"
flowable:collection="assigneeList" flowable:elementVariable="assignee" >
<completionCondition>${nrOfCompletedInstances/nrOfInstances >=
0.5 }</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>

在以上配置中,将会为 assigneeList 集合的每个元素创建一个并行的实例。当 50%的任务完成 时,其他任务就会被删除,流程继续往下执行。

界面操作

演示demo

用户多实例