网关

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

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

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

博客地址

网关是 BPMN2 规范中的流程定义元素,可控制流程的执行流向,常用于拆分或合并复杂的流 程流场景。网关的图形标记为菱形图形。网关内部一般会有一个小图标,用来表 示网关的类型。

Flowable常用网关可分为以下几种类型:

  • 排他网关(Exclusive Gateway)
  • 并行网关(Parallel Gateway)
  • 包容网关(Inclusive Gateway)
  • 事件网关(Event Gateway)

排他网关

定义

排他网关,也叫异或(XOR)网关,是 BPMN 中使用的最常见的网关之一,用来在流转中实 现发散分支决策。排他网关需要和条件顺序流搭配使用,当流程执行到排他网关,所有流出的顺序 流都会被按顺序求解计算一遍,其中第一个条件解析为 true 的顺序流会被选中(当多个顺序流程的 条件为 true 时,只有第一个会被选中)并且不再计算其它流出分支,让流程沿着第一条被选中的执 行线运行;如果所有顺序流条件计算结果都为 false 且该网关定义了一个默认顺序流,那么该默认 顺序流将被执行;如果所有顺序流条件计算结果都为 false 且没有定义默认顺序流,则抛出异常, 中断执行,在流程设计时应该避免这种情况的发生,至少需要确保有一条分支的顺序流计算结果为true。 排他网关的流程分支的顺序流建议配置条件,未设置条件的顺序流被计算为 true。 排他网关没有合并的效果,只要有一个流入的顺序流到达,该网关流出的顺序流被激活开始执 行计算。如果前置可能会有多个正在执行的分支,排他网关之后的路径将在每个分支到达时被重复 实例化(除非业务需求的确如此,否则应避免这种情况的发生)。

图形标记

排他网关用内部带有“X”图标的标准网关(菱形)表示,“X”图标表示异或(XOR)语义。 排他网关的图形标志如图所示。

XML内容

排他网关用一行定义了网关,条件表达式定义在流出顺序流中:

<exclusiveGateway id="Gateway_1gse3sj" default="Flow_1gdshzv"/>

示例

 <process id="ServiceSpringCloudBackServiceProcess" name="服务-Springcloud回调服务" isExecutable="true">
    <startEvent id="a39b8c7712bb742a7aa081cf731563c96" />
    <userTask id="ad7ca5657f18f4e64aa6e309db12fc47b" name="费用申请">
      <extensionElements>
        <flowable:formData />
        <flowable:assigneeType>static</flowable:assigneeType>
      </extensionElements>
      <outgoing>Flow_0tmo406</outgoing>
    </userTask>
    <sequenceFlow id="a653af6d1d2774d02afc0301d2f0121e2" sourceRef="a39b8c7712bb742a7aa081cf731563c96" targetRef="ad7ca5657f18f4e64aa6e309db12fc47b" />
    <endEvent id="Event_1vogsar">
      <incoming>Flow_0odeyuq</incoming>
    </endEvent>
    <exclusiveGateway id="Gateway_1gse3sj" default="Flow_1gdshzv">
      <incoming>Flow_0tmo406</incoming>
      <outgoing>Flow_1gdshzv</outgoing>
      <outgoing>Flow_0mdfhgf</outgoing>
    </exclusiveGateway>
    <userTask id="Activity_0b7092y" name="经理">
      <extensionElements>
        <flowable:formData />
        <flowable:assigneeType>static</flowable:assigneeType>
      </extensionElements>
      <incoming>Flow_1gdshzv</incoming>
      <outgoing>Flow_0c58ztv</outgoing>
    </userTask>
    <userTask id="Activity_1gvbrzi" name="总监">
      <extensionElements>
        <flowable:formData />
        <flowable:assigneeType>static</flowable:assigneeType>
      </extensionElements>
      <incoming>Flow_0c58ztv</incoming>
      <incoming>Flow_0mdfhgf</incoming>
      <outgoing>Flow_0ovnf34</outgoing>
    </userTask>
    <sequenceFlow id="Flow_0c58ztv" sourceRef="Activity_0b7092y" targetRef="Activity_1gvbrzi" />
    <userTask id="Activity_0ol5zjn" name="总经理">
      <extensionElements>
        <flowable:formData />
        <flowable:assigneeType>static</flowable:assigneeType>
      </extensionElements>
      <incoming>Flow_0ovnf34</incoming>
      <outgoing>Flow_0odeyuq</outgoing>
    </userTask>
    <sequenceFlow id="Flow_0ovnf34" sourceRef="Activity_1gvbrzi" targetRef="Activity_0ol5zjn" />
    <sequenceFlow id="Flow_0odeyuq" sourceRef="Activity_0ol5zjn" targetRef="Event_1vogsar" />
    <sequenceFlow id="Flow_0tmo406" sourceRef="ad7ca5657f18f4e64aa6e309db12fc47b" targetRef="Gateway_1gse3sj" />
    <sequenceFlow id="Flow_1gdshzv" sourceRef="Gateway_1gse3sj" targetRef="Activity_0b7092y" />
    <sequenceFlow id="Flow_0mdfhgf" name="大于10000" sourceRef="Gateway_1gse3sj" targetRef="Activity_1gvbrzi">
      <conditionExpression xsi:type="tFormalExpression">${money&gt;10000}</conditionExpression>
    </sequenceFlow>
  </process>

在以上代码中,加粗代码定义了排他网关 Gateway_1gse3sj,它流出的条件顺序流有 Flow_1gdshzv、Flow_0mdfhgf,分别定义了条件 默认流、Flow_0mdfhgf。

演示demo

排他网关

并行网关

定义

并行网关能在一个流程里用来对并发进行建模处理,它能把单条线路拆分成多个路径并行执 行,或者将多个路径合并处理。在一个流程模型里引入并发最直接的网关就是并行网关,它基于进 入和外出顺序流,有分支(Fork)和合并(Join)两种行为,允许将流程拆分成多条分支,也可以 将多条分支合并(Join)到一起:

(1)分支(Fork) 即并行拆分,从并行网关流出后,会为所有外出顺序流分别创建一个并发分支。需要注意的是, 并行网关对外出顺序流上的条件是忽略的,它不会解析条件,即使顺序流中定义了条件,也会被忽 略,它的每个后继分支路径都被无条件执行。

(2)合并(Join) 即并行合并,所有到达并行网关的分支路径都汇聚于此等待,只有当所有进入顺序流的分支都 到达后,流程才会通过并行网关。如果其中有分支未到达或中断,那么该并行网关将处于永久等待 状态。

并行网关允许多进多出。如果同一个并行网关有多个进入和多个外出顺序流,那么它就同时具 有分支和合并功能。这种情况下,并行网关会先合并所有进入的顺序流,然后再拆分成多个并行分 支往外流出。

图形标记

并行网关显示成一个普通网关(菱形),内部是一个“+”图标,表示“与(AND)”语义, 如图所示:

XML内容

<parallelGateway id="parallelGateway1" />

并行网关实际的行为(分支,合并或同时分支合并),是由并行网关的流出和流入顺序流决定 的。

使用示例

在使用并行网关的时候,很多初学者都有一个误区,认为同步节点一定需要成对出现,即由一 个并行网关 Fork 出来的分支,必须有一个对应的并行网关来 Join。其实是没有这个要求的,从上 面的例子中也能看出来,并行网关只是等待所有进入顺序流,并为每个外出顺序流创建并发分支, 并不要求成对出现。

演示demo

并行网关

包容网关

定义

包容网关可以看做是排他网关和并行网关的结合体。和排他网关一样,你可以在外出顺序流上 定义条件,但与排他网关所不同的是,当进行决策判断时,所有条件为 true 的后继分支都会被依次 执行。如果所有分支条件决策都为 false 且该网关定义了一个默认的连线,那么该默认分支将被执 行。如果没有可执行的分支,则会抛出异常,在流程设计上应避免这种情况发生。包容网关有分支 和合并两种行为:

(1)分支(Fork) 即包容拆分,所有外出顺序流的条件都会被解析,结果为 true 的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。如果后继分支可能存在都不通过的情况,应该合理的选择一个默认 路径,否则引擎执行到该网关的分支将被中断于此。

(2)合并(Join) 即包容合并,所有到达包容网关的活动分支路径都汇聚于此等待,直到所有“可以到达”包容 网关的分支路径全部“到达”包容网关,流程才会通过包容网关。这点与并行网关的合并策略是不 同的。引擎判断是否“可以到达”并行网关的路径的逻辑为:计算当前流程实例中的所有执行路径, 检查从其位置是否有一条到达包容网关的路径(忽略顺序流上的任何条件)。如果存在这样的执行 (可到达但尚未到达),则不会触发包容网关的合并行为。

包容网关允许多进多出,如果同一个包容节点有多个进入和外出顺序流,那么它会同时具有分 支和合并功能。网关会先合并所有“可以到达”包容网关的进入顺序流,再根据条件判断结果为 true 的外出顺序流,为它们生成多条并行分支。

图形标记

包容网关显示为一个普通网关(菱形),内部包含一个圆圈图标,如图所示:

inclusiveGateway

XML内容

定义包容网关的 XML 定义如下:

<inclusiveGateway id="inclusiveGateway1" />

包容网关实际的行为(分支,合并或同时分支合并),是由包容网关的流出和流入顺序流决定的。

使用示例

inclusiveGateway

<process id="InclusiveGatewayTest" name="网关-包容网关" isExecutable="true">
    <startEvent id="a63caeb191435462eb8fc789b998b0b02" />
    <userTask id="a2a3d2965e93841769298862145315216" name="请假申请">
        <extensionElements>
            <flowable:formData />
            <flowable:assigneeType>static</flowable:assigneeType>
        </extensionElements>
    </userTask>
    <sequenceFlow id="a10afa5b876a14ef1927568c25d82532d" sourceRef="a63caeb191435462eb8fc789b998b0b02" targetRef="a2a3d2965e93841769298862145315216" />
    <sequenceFlow id="Flow_01gl4se" sourceRef="a2a3d2965e93841769298862145315216" targetRef="Gateway_0wdovry" />
    <inclusiveGateway id="Gateway_0wdovry" />
    <userTask id="Activity_0n2utd2" name="HR实习生审批">
        <extensionElements>
            <flowable:formData />
            <flowable:assigneeType>static</flowable:assigneeType>
        </extensionElements>
    </userTask>
    <sequenceFlow id="Flow_0poec43" name="天数&#60;3" sourceRef="Gateway_0wdovry" targetRef="Activity_0n2utd2">
        <conditionExpression xsi:type="tFormalExpression">${leaveDays&lt;3}</conditionExpression>
    </sequenceFlow>
    <userTask id="Activity_1jqu87g" name="HR助理审批">
        <extensionElements>
            <flowable:formData />
            <flowable:assigneeType>static</flowable:assigneeType>
        </extensionElements>
    </userTask>
    <sequenceFlow id="Flow_048awuq" name="天数&#62;=3" sourceRef="Gateway_0wdovry" targetRef="Activity_1jqu87g">
        <conditionExpression xsi:type="tFormalExpression">${leaveDays&gt;=3}</conditionExpression>
    </sequenceFlow>
    <userTask id="Activity_0i4f84q" name="直属领导审批">
        <extensionElements>
            <flowable:formData />
            <flowable:assigneeType>static</flowable:assigneeType>
        </extensionElements>
    </userTask>
    <sequenceFlow id="Flow_1ifcgzs" name="天数&#62;=1" sourceRef="Gateway_0wdovry" targetRef="Activity_0i4f84q">
        <conditionExpression xsi:type="tFormalExpression">${leaveDays&gt;=1}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="Flow_0evatt3" sourceRef="Activity_0n2utd2" targetRef="Gateway_1byuti3" />
    <inclusiveGateway id="Gateway_1byuti3" />
    <sequenceFlow id="Flow_1t3shqg" sourceRef="Activity_1jqu87g" targetRef="Gateway_1byuti3" />
    <userTask id="Activity_1ox2rws" name="HR经理审批">
        <extensionElements>
            <flowable:formData />
            <flowable:assigneeType>static</flowable:assigneeType>
        </extensionElements>
    </userTask>
    <sequenceFlow id="Flow_0tc6xkx" sourceRef="Gateway_1byuti3" targetRef="Activity_1ox2rws" />
    <sequenceFlow id="Flow_0o8l6c2" sourceRef="Activity_1ox2rws" targetRef="Gateway_02v0l7w" />
    <inclusiveGateway id="Gateway_02v0l7w" />
    <endEvent id="Event_0cvo3tf" />
    <sequenceFlow id="Flow_120xxfs" sourceRef="Gateway_02v0l7w" targetRef="Event_0cvo3tf" />
    <sequenceFlow id="Flow_0s50mvn" sourceRef="Activity_0i4f84q" targetRef="Gateway_02v0l7w" />
</process>

演示demo

包容网关

事件网关

定义

通常网关根据连线条件来决定后继路径,但事件网关不同,它提供了根据事件做选择的方式。 事件网关的每个外出顺序流都需要连接至一个捕获中间事件。当流程执行到达事件网关时,网关类 似处于等待的状态,暂停执行,并为每个外出顺序流创建相对的事件订阅。事件网关只有分支行为, 流程的走向完全是由于中间事件的选择,它允许从多个候选分支中选择事件最先触发的分支(如时 间事件、消息事件),并取消其他分支。 事件网关的外出顺序流和普通顺序流不同,这些顺序流从不实际被执行。相反,它们允许流程 引擎决定,当执行到达一个事件网关时,需要订阅什么事件。事件网关的使用需要注意以下几个约 束条件:

1、一个事件网关,必须有两条或以上外出顺序流。

2、事件网关后只能连接 intermediateCatchEvent(中间捕获事件)类型的元素。在 Flowable 中, 事件网关后还不支持连接接收任务(Receive Task)。

3、连接到事件网关的中间捕获事件,必须只有一个入口顺序流。

图形标记

事件网关和其他 BPMN 网关一样显示成一个菱形,内部包含指定图标,如图所示:

eventBasedGateway

XML内容

定义包容网关的 XML 定义如下:

<eventBasedGateway id="exclusiveGateway1" />

使用示例

eventBasedGateway

  <signal id="alertSignal" name="alert" flowable:scope="global" />
  <process id="EventBasedGatwayTest" name="网关-事件网关" isExecutable="true">
    <startEvent id="ab90dd1d6552e49c09dec232671467890" />
    <userTask id="a53b48e07e88d4adb8b65bf3ab3460e0a" name="客户投诉">
      <extensionElements>
        <flowable:formData />
        <flowable:assigneeType>static</flowable:assigneeType>
      </extensionElements>
    </userTask>
    <sequenceFlow id="a6171b16076dd41b6a67d25fd838218c4" sourceRef="ab90dd1d6552e49c09dec232671467890" targetRef="a53b48e07e88d4adb8b65bf3ab3460e0a" />
    <sequenceFlow id="Flow_0h5e59j" sourceRef="a53b48e07e88d4adb8b65bf3ab3460e0a" targetRef="Gateway_0pspvvy" />
    <eventBasedGateway id="Gateway_0pspvvy" />
    <intermediateCatchEvent id="Event_0f966w5" name="3分钟">
      <timerEventDefinition>
        <timeDuration>PT2M</timeDuration>
      </timerEventDefinition>
    </intermediateCatchEvent>
    <sequenceFlow id="Flow_0ajw2tf" sourceRef="Gateway_0pspvvy" targetRef="Event_0f966w5" />
    <intermediateCatchEvent id="Event_0g20b6y" name="信号">
      <signalEventDefinition signalRef="alertSignal" />
    </intermediateCatchEvent>
    <sequenceFlow id="Flow_1w1y6zy" sourceRef="Gateway_0pspvvy" targetRef="Event_0g20b6y" />
    <userTask id="Activity_0aug760" name="二级客服处理">
      <extensionElements>
        <flowable:formData />
        <flowable:assigneeType>static</flowable:assigneeType>
      </extensionElements>
    </userTask>
    <sequenceFlow id="Flow_0sqnwot" sourceRef="Event_0f966w5" targetRef="Activity_0aug760" />
    <userTask id="Activity_1wlis7z" name="一级客户处理">
      <extensionElements>
        <flowable:formData />
        <flowable:assigneeType>static</flowable:assigneeType>
      </extensionElements>
    </userTask>
    <sequenceFlow id="Flow_1x3g9hf" sourceRef="Event_0g20b6y" targetRef="Activity_1wlis7z" />
    <endEvent id="Event_139njtd" />
    <sequenceFlow id="Flow_143bcf9" sourceRef="Activity_0aug760" targetRef="Event_139njtd" />
    <sequenceFlow id="Flow_1i2cmpp" sourceRef="Activity_1wlis7z" targetRef="Event_139njtd" />
  </process>

演示demo

事件网关