Activiti——BPMN2.0规范详解(二)

Activiti与BPMN2.0规范(二)

4、网关

网关(Gateway)用于控制流程走向(在BPMN2.0规范中称为“执行令牌”)。根据功能不同可以划分为以下4种网关:

  • 排他网关
  • 并行网关
  • 包容网关
  • 事件网关

4.1、排他网关

排他网关(Exclusive Gateway,也称做XOR Gateway)用来对流程中的决定进行建模。流程执行到该网关时,按照输出流的顺序逐个计算,当条件计算结果为tue时,继续执行
当前网关的输出流。

值得注意的是,在排他网关中,如果多个线路的计算结果都为true,那么只会执行第一个值为true的网关,忽略其他表达式的值为true的网关。如果多个网关计算结果没有为true的值,则引擎会抛出异常。

排他网关的图形表示形象地在菱形嵌入“X”,如图4-26所示:
在这里插入图片描述

排他网关的XML描述如下:

<exclusiveGateway default="flow1" id="exclusiveGateway" name="ExclusiveGateway"/>

排他网关需要和条件顺序流配合使用,一个排他网关可以连接多个条件顺序流,每个条件顺序流设置一个条件在运行时由引擎计算并根据结果是否为tue决定执行与否。典型
的排他网关用例如图所示,对应的XML描述如代码如下:
在这里插入图片描述

<startEvent id="startEvent" name="StartEvent"/>
<!-- 定义一个排他网关,当所有条件都不满足时,默认执行default指定的顺序流 -->
<exclusiveGateway id="exclusivegateway1" name="ExclusiveGateway" default="flow2"/>
<sequenceFlow id="flow1" name="" sourceRef="startEvent" targetRef="exclusivegateway1"/>
<userTask id="usertask1" name="任务一"/>
<sequenceFlow id="flow2" name="${type == 1}" sourceRef="exclusivegateway1" targetRef="usertask1">
  <conditionExpression xsi:type="tFormalExpression">
    <![CDATA[${type == 1}]]>
  </conditionExpression>
</sequenceFlow>
<userTask id="usertask2" name="任务二"/>
<sequenceFlow id="flow3" name="${type == 2}" sourceRef="exclusivegateway1" targetRef="usertask2">
  <conditionExpression xsi:type="tFormalExpression">
    <![CDATA[${type == 2}]]>
  </conditionExpression>
</sequenceFlow>
<userTask id="usertask3" name="任务三"/>
<sequenceFlow id="flow4" name="${type == 3}" sourceRef="exclusivegateway1" targetRef="usertask3">
  <conditionExpression xsi:type="tFormalExpression">
    <![CDATA[${type == 3}]]>
  </conditionExpression>
</sequenceFlow>

4.2、并行网关

并行网关用来对并发的任务进行流程建模,它能把单条线路任务拆分(fork)成多个路径并行执行或将多条线路合并(join)。

并行网关的图形化表示是在菱形中嵌入一个加号“+”,如图4-28所示:
在这里插入图片描述

对应的XML描述如下所示:

<parallelGateway id="fork" name="ParallelGatewayFork"/>

并行网关的功能取决于输入、输出顺序流。

  • 拆分:并行执行所有的输出顺序流,并且为每一条顺序流创建一个并行执行线路。
  • 合并:所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完成才继续向下执行。

注意:并行网关不会计算线路上设置的条件,如果设置了,则会被直接忽略。

并行网关的图形化表示和排他网关类似,把“X”换成了“十”,加号为逻辑“与”关系运算符。典型的并行网关用例如图所示,对应的XML如下所示:
在这里插入图片描述

<!-- 描述一个并行网关,id为fork,拆分出两条线路 -->
<parallelGateway id="fork" name="ParallelGatewayFork"/>
<!-- 描述一个并行网关,id为join,两个用户全部执行完之后在此处合并 -->
<parallelGateway id="join" name="ParallelGatewayJoin"/>
<userTask id="usertask11" name="部门领导审批"/>
<userTask id="usertask12" name="人事审批"/>
<userTask id="usertask14" name="请假申请"/>
<userTask id="usertask13" name="考勤归档"/>
<startEvent id="start" name="Start"/>
<endEvent id="end" name="End"/>
<sequenceFlow id="flow11" sourceRef="start" targetRef="usertask14"/>
<sequenceFlow id="flow12" sourceRef="usertask14" targetRef="fork"/>
<sequenceFlow id="flow13" sourceRef="fork" targetRef="usertask11"/>
<sequenceFlow id="flow14" sourceRef="fork" targetRef="usertask12"/>
<sequenceFlow id="flow15" sourceRef="usertask11" targetRef="join"/>
<sequenceFlow id="flow16" sourceRef="usertask12" targetRef="join"/>
<sequenceFlow id="flow17" sourceRef="join" targetRef="usertask13"/>
<sequenceFlow id="flow18" sourceRef="usertask13" targetRef="end"/>

并行网关还允许在线路上嵌套并行网关,也就是在fork拆分的线路上再添加n个fork线路,只要保证最后有一个join点合并拆分的线路即可。下图展示了嵌套并行网关。
在这里插入图片描述

图中的并行网关有3个,所有并行网关不需要“成对出现”,保证“有始有终”即可。

如果一个并行网关有多个输入、输出流,那么它同时具备fork和join的功能,对于这样的并行网关,首先会合并被拆分的并行网关线路,然后再拆分合并网关之后的线路。

图中“部门领导审批”和“人事审批”节点之后的并行网关就是如此,假如“部门领导审批”节点先执行完成,此时到达“考勤归档”节点前面的并行网关,直到“人事审批”节点执行完成之后才拆分后续的“考勤归档”网关。

4.3、包容网关

包容网关融合了排他网关和并行网关的特性,排他网关允许在每条线路上设置条件,并行网关可以同时执行多条线路,包容网关既可以同时执行多条线路,又允许在网关上设置条件。

包容网关的图形表示形象地在菱形中嵌人“圆”,其相关的输出流均会被执行,如图4-31所示,对应的XML描述如下所示:
在这里插入图片描述

<inclusiveGateway id="inclusivegateway1" name="InclusiveGatewayFork"/>

和并行网关一样,包容网关的功能也取决于输入、输出顺序流。

  • 拆分:计算每条线路上的表达式,当表达式计算结果为tue时,创建一个并行线路并继续执行。
  • 合并:所有从并行网关拆分并执行完成的线路均在此等候,直到所有的线路都执行完才继续向下执行。

下图定义了一个包容网关版本的请假流程,该流程在启动的时候根据条件顺序流判断是否需要领导或人事审批,如果两个条件都满足,则“部门领导审批”和“人事审批”
任务都会被创建。

下图是并行网关的基本表现形式(和并行网关类似),用inclusiveGateway描述一个并行网关,有一个fork开始点拆分条件表达式计算结果为true的线路,在最后一个join点
合并在fork点拆分的两条线路。

在这里插入图片描述

<!-- 定义了fork类型的包容网关 -->
<inclusiveGateway id="igFork" name="InclusiveGatewayFork"/>
<!-- 定义了join类型的包容网关 -->
<inclusiveGateway id="igJoin" name="InclusiveGatewayJoin"/>
<userTask id="ut1" name="部门领导审批"/>
<userTask id="ut2" name="人事审批"/>
<userTask id="ut3" name="考勤归档"/>
<userTask id="ut4" name="请假申请"/>
<startEvent id="st" name="开始"/>
<endEvent id="en" name="结束"/>
<sequenceFlow id="fl1" name="" sourceRef="st" targetRef="ut4"/>
<sequenceFlow id="fl2" name="" sourceRef="ut4" targetRef="igFork"/>
<sequenceFlow id="fl3" name="需要部门领导审批?" sourceRef="igFork" targetRef="ut1">
  <conditionExpression xsi:type="tFormalExpression">
    <![CDATA[${leader == true}]]>
  </conditionExpression>
</sequenceFlow>
<sequenceFlow id="fl4" name="需要人事审批?" sourceRef="igFork" targetRef="ut2">
  <conditionExpression xsi:type="tFormalExpression">
    <![CDATA[${hr == true}]]>
  </conditionExpression>
</sequenceFlow>
<sequenceFlow id="fl5" name="" sourceRef="ut1" targetRef="igJoin"/>
<sequenceFlow id="fl6" name="" sourceRef="ut2" targetRef="igJoin"/>
<sequenceFlow id="fl7" name="" sourceRef="igJoin" targetRef="ut3"/>
<sequenceFlow id="fl8" name="" sourceRef="ut3" targetRef="en"/>

4.4、事件网关

事件网关是专门为中间捕获事件设置的,它允许设置多个输出流指向多个不同的中间捕获事件(最少两个)。在流程执行到事件网关后,流程处于“等待”状态,因为中间捕获事件需要依赖中间抛出事件触发才能更改“等待”状态为“活动”状态,当然,定时器捕获事件除外(它由时间驱动)。

事件网关的图形化表示是在菱形中嵌入了一个特殊符号(双线圆中嵌套一个多边形),如图4-33所示,对应的XML描述如代码如下所示:
在这里插入图片描述

<eventBasedGateway id="eventgateway" name="EventGateway"/>

下图展示了事件网关的典型应用,其对应的XML描述如下所示:
在这里插入图片描述

<process id="EventGateway" name="EventGateway">
<startEvent id="start11" name="Start"/>
  <endEvent id="end11" name="End"/>
  <!-- 
    定义一个事件网关,分别指向两个输出流,分别为定时器中间捕获事件和信号中间捕获事件。
    在流程启动之后两个输出流均处于【等待】状态。 定时器中间事件通过timeDuration指定
    定时器在1秒钟之后执行;信号中间事件设置了signalRef="alertSignal",等待其他流程
    中的信号抛出事件抛出alertSignal的信号。中间捕获事件在满足了条件后会被自动触发继续执行输出流的活动。
  -->
  <eventBasedGateway id="eventgateway" name="EventGateway"/>
  <intermediateCatchEvent id="timerintermediatecatechevent1" name="TimerCatchEvent">
    <timerEventDefinition>
      <timeDuration>PT1S</timeDuration>
    </timerEventDefinition>
  </intermediateCatchEvent>
  <scriptTask id="scripttask1" name="定时器任务执行之后" scriptFormat="groovy">
    <script>
      <![CDATA[out:println "after time event";]]>
    </script>
  </scriptTask>
  <intermediateCatchEvent id="signalintermediatecatchevent1" name="SignalCatchEvent">
    <signalEventDefinition signalRef="alertSignal"></signalEventDefinition>
  </intermediateCatchEvent>
  <scriptTask id="scripttask2" name="信号捕获事件之后" scriptFormat="groovy">
    <script>
      <![CDATA[out:println "after signal event";]]>
    </script>
  </scriptTask>
  <sequenceFlow id="f1" name="" sourceRef="start11" targetRef="eventgateway"/>
  <sequenceFlow id="f2" name="" sourceRef="eventgateway" targetRef="timerintermediatecatechevent1"/>
  <sequenceFlow id="f3" name="" sourceRef="eventgateway" targetRef="signalintermediatecatchevent1"/>
  <sequenceFlow id="f4" name="" sourceRef="timerintermediatecatechevent1" targetRef="scripttask1"/>
  <sequenceFlow id="f5" name="" sourceRef="signalintermediatecatchevent1" targetRef="scripttask2"/>
  <sequenceFlow id="f6" name="" sourceRef="scripttask1" targetRef="end11"/>
  <sequenceFlow id="f7" name="" sourceRef="scripttask2" targetRef="end11"/>
</process>

<signal id="alertSignal" name="alert"/>

关于事件网关,需要注意几点:

  • 事件网关的输出流数量必须大于2个。
  • 事件网关的输出流类型只能是中间捕获事件,Activiti不支持接受任务后面的事件网关。
  • 中间捕获事件的输出流只能有一个。

5、子流程与调用活动

在实际的企业应用中,业务流程往往比较复杂,仔细分析之后一般可以将其划分为多个不同的阶段,可以把这些阶段规划为一个子流程作为主流程的一部分,BPMN2.0的子流程规范正是满足此需求的选择之一。

在企业中还有一些通用的业务流程,例如,付款流程作为公司业务运作的核心流程之一,在业务设计及架构设计上会保持通用,或者在业务架构中作为一个通用的模块,不同的业务根据财务流程的规范传入指定的参数就可以使用付款流程。调用活动的特点和子流程类似,但是子流程嵌入在主流程中,要保持通用需要把付款流程作为活动由主流程“调用”,如此调用活动既包含了子流程的特性又保持通用。

5.1、子流程

子流程可以包含BPMN2.0规范中的大部分模型,把一系列需要处理的任务归结到一起,作为一个大流程中的一部分。因为子流程嵌人在主流程中,所以也把子流程称之为“嵌入式子流程”。

子流程需要把一系列模型包含在一个实线矩形中,形成如图4-35所示的子流程,对应的XML描述如下所示:

在这里插入图片描述

<subProcess  id="subprocess" name="SubProcess" />

对于子流程BPMN2.0对其做了一些限制。

  • 只能且仅能包含一个空启动事件。
  • 至少要有一个结束事件(每个流程都要“有始有终”)。
  • 在子流程中顺序流不能直接设置输出流到子流程之外的活动上,如果需要的,可以通过边界事件代替。
<startEvent id="startevent1" name="Start"/>
<endEvent id="endevent1" name="End"/>
<userTask id="usertask1" name="下单"/>
<sequenceFlow id="flow1" name="" sourceRef="startevent1" targetRef="usertask1"/>
<sequenceFlow id="flow2" name="" sourceRef="usertask1" targetRef="subprocess1"/>
<subProcess id="subprocess1" name="付款子流程">
  <startEvent id="startevent2" name="Start"/>
  <endEvent id="endevent2" name="End"/>
  <userTask id="usertask2" name="银行付款"/>
  <serviceTask id="mailtask1" name="发送邮件通知" activiti:type="mail">
    <!-- ...... -->
  </serviceTask>
  <sequenceFlow id="flow3" name="" sourceRef="startevent2" targetRef="usertask2"/>
  <sequenceFlow id="flow4" name="发送付款结果" sourceRef="usertask2" targetRef="mailtask1"/>
  <sequenceFlow id="flow5" name="" sourceRef="mailtask1" targetRef="endevent1"/>
</subProcess>

在实际运行中流程引擎会自动为主流程和子流程建立关联关系,子流程可以通过API获取主流程的一些信息及变量。
子流程不能直接越界指定输出流,也就是说子流程中的输出流只能指向在实线边框内的活动,如果因为异常需要终止子流程的执行可以通过边界事件处理。

子流程同时也支持多实例特性,例如部门领导为几个业务员分配任务,而每项任务都是一个子流程,部门领导下发任务之后每个业务员都会拥有一个独立的子流程实例,等所有的业务员都处理完成之后再汇总给部门领导。

5.2、调用活动

调用活动(Call Activity)解决的问题是流程的通用性。和子流程的作用一致,但是表现方式不同,使用一个调用活动取代嵌入子流程方式的活动即可,通过创建一个调用活动模型并指定外部流程的ID方式作为主流程的一个子活动:
在这里插入图片描述

图4-36展示了调用活动的图形化表示,对应的XML描述如下所示:

<callActivity id="callactivity1" name="CallActivity" calledElement="payment"/>

下表列出了调用活动和子流程的区别:

区别子流程调用活动
表现形式直接嵌入在主流程中,使用subProcess定义子流程作为一个普通的模型,定义外部流程的ID
模型约束启动事件只能使用空启动事件无任何限制,被调用的外部流程本身就是一个完整的流程
流程变量子流程共享主流程的所有变量需要指定输入、输出变量

下面展示了调用活动的用例,其对应的XML描述如下:
在这里插入图片描述

<callActivity id="callactivity1" name="调用付款流程" calledElement="payment">
  <extensionElements>
    <activiti:in source="amount" target="amount"/>
    <activiti:out source="paid" target="paid"/>
    <activiti:out sourceExpression="${payTime}" target="payTime"/>
  </extensionElements>
</callActivity>

代码中调用了外部的付款流程,名称为payment;指定了输入、输出参数。下表列出了调用活动的属性及Activiti的扩展元素。

  • calledElement:流程的ID,对应的流程应独立存在
<callActivity id="callactivity1" name="调用付款流程" calledElement="payment">
  • activiti:in:调用外部流程时传入的变量,被调用活动需要获取主流程的信息(必须显示定义,否则不能获
    取)。
<activiti:in source="amount" target="amount">
</activiti:in>
<!--
 source:主流程中的变量名称,也可以在使用表单时计算
 target:在将变量传递给调用活动时使用的名称,一般和source同名避免错误的出现
-->
  • activiti:out:调用活动执行完成后的结果。
<activiti:in source="amount" target="amount">
</activiti:in>
<!--
 source:主流程中的变量名称,也可以在使用表单时计算
 target:在将变量传递给调用活动时使用的名称,一般和source同名避免错误的出现
-->

5.3、事件子流程

事件子流程和子流程类似,把一系列的活动归结到一起处理,不同的是事件子流程不能直接启动,而要“被动”地由其他的事件触发启动;在图形化表示上有所不同,子流程
使用的是实线边框,而事件子流程使用的是虚线矩形,如图4-38所示:

在这里插入图片描述

对应的XML描述如下所示:

<subProcess id="eventsubprocess1" name="完善账号信息" triggeredByEvent="true">
  
</subProcess>

事件子流程在XML描述上和子流程不同的是添加了一个triggeredByEvent属性表示此子流程只能由事件触发后被动启动。事件子流程在企业中使用频率很高,它可以由异常事件、信号事件、消息事件、定时器事件、补偿事件等触发,从而启动一个子流程。

事件子流程和子流程不同,子流程作为主流程中输出流的一个输出活动,而事件子流程是独立在主流程中的,下图是用付款业务模拟的一个事件子流程的应用场景。

图4-38的用例在支付失败时用排他网关把输出流转向异常结束事件,主流程中的“支付费用失败一完善账号信息”作为一个异常处理子流程存在,此例也恰好演示了两者的联合应用。

异常结束事件和异常启动事件的errorRef要保持一直。如果主流程中有多个异常可以添加多个事件子流程,那么要分别处理。

在这里插入图片描述

<startEvent id="start_event" name="Start"/>
<!-- 省略中间 -->
<endEvent id="end_event" name="End">
  <!--使用一个异常结束事件 -->
  <errorEventDefinition errorRef="A002"/>
</endEvent>
<!-- subProcess标签添加属性triggeredByEvent=true定义了一个事件子流程 -->
<subProcess id="eventsubprocess1" name="支付费用失败-完善账号信息" triggeredByEvent="true">
  <startEvent id="errorstartevent1" name="ErrorStart">
    <!-- 
      捕获一个异常事件,因为errorRef的值为“A002”,所以当主流程执行到ID为 
      “end_event”的异常结束事件时引擎会启动子流程“eventsubprocess1”。
    -->  
    <errorEventDefinition errorRef="A002"/>
  </startEvent>
  <userTask id="usertask222" name="完善账号信息"/>
  <sequenceFlow id="flow11" name="" sourceRef="errorstartevent1" targetRef="usertask222" />
  <endEvent id="endevent222" name="End"/>
  <sequenceFlow id="flow22" name="" sourceRef="usertask222" targetRef="endevent222"/> 
</subProcess>

5.4、事务子流程

事务子流程也称为“事务块”,用来处理一组必须在同一个事务中完成的活动,这些活动要么一起成功,要么一起失败。事务子流程中的活动具有ACID特性,如果其中有一个活动失败或取消,则整个事务子流程的所有活动全部回滚。

事务子流程的图形化表示采用双线矩形,如图4-41所示:
在这里插入图片描述

对应的XML描述如下所示:

<transaction id="myTransaction">
  
</transaction>

下图展示了事务子流程的一个应用场景——银行汇款:

在这里插入图片描述

图模拟了一个简单的银行汇款流程,在主流程中嵌入了一个事务子流程把汇款业务放在一个事务中处理。根据事务执行结果可以把执行结果划分为以下几种。

  • 事务成功:事务子流程中的两个任务全部成功,正常结束。
  • 事务取消:在事务执行过程中出现异常,任务“向受款方账户汇款”抛出了异常,接着被附加在此任务边界上的异常边界事件捕获,然后执行后续输出流的取消结束事件,最后被附加在事务子流程中的取消边界事件捕获执行“通知客户汇款失败”任务;与此同时会触发子流程中的补偿边界事件执行事务取消的补偿逻辑(任务“退回已扣金额”)。在图中,在“向受款方账户汇款”节点的执行过程出现异常被捕获之后执行取消结束事件,随即被附加在事务子流程的取消边界事件捕获。
  • 事务失败:事务执行时遇到不可处理的异常,既不能补偿又不能回滚事务。在图中捕获到异常之后会执行“记录系统异常信息”活动。

6、边界与中间事件

除了启动和结束事件之外,中间事件提供的特殊功能可以用来处理流程执行过程中抛出、捕获的事件,具体包括边界事件、中间捕获事件、中间抛出事件。每种中间事件的图形表示都有一个共同特点:以双线圆形表示,如图4-42所示:
在这里插入图片描述

6.1、边界事件

边界事件是绑定在活动上的“捕获型”事件,会一直监听所有处于运行中活动的某种事件的触发,在捕获到事件之后中断活动,然后从边界事件类型的输出流继续执行。

值得注意的是一旦触发边界事件,当前的活动就会被中断,然后按照边界事件之后的输出流执行。

边界事件和所关联的活动有一个特殊的关系“附加”,而且一个活动只能绑定一个边界事件;每个边界事件类型都是通过属性attachedToRef指定“附加”到抛出边界事件的活
动上。

下面列出了边界事件的基本XML描述:

<boundaryEvent id="boundary" attachedToRef="someActivity" cancelActivity="true|false">
  <xxxEventDefinition/>
</boundaryEvent>

所有的边界事件子类型均需要包含在boundaryEvent标签中。cancelActivity属性可以取true、false两个值,用来指定在捕获到边界事件之后是否取消执行输出流指定的活动,不过此属性仅适用部分边界事件,在讲解过程中会特别说明。

6.1.1、定时器边界事件

定时器边界事件和定时启动事件类似,只不过两者的应用场合不同,定时启动事件用于在一个指定的时间启动一个新的流程,而定时器边界事件需要附属在一个非自动任务
(用户任务等)、调用活动、子流程上,在上游任务执行完成之后开始倒计时预设的时间,到达预设时间之后触发定时器边界事件的输出流(输出流可以是并行)。

定时器边界事件的图形化表示和定时器启动事件类似,在边界事件的基础上添加一个事件图标,如图4-43所示。

在这里插入图片描述

图4-44展示了定时器边界事件在请假流程中的应用,对应的XML描述如下所示:
在这里插入图片描述

<userTask id="hrAudit" name="人事审批"/>
<boundaryEvent id="boundarytimer1" attachedToRef="hrAudit" cancelActivity="false">
  <timerEventDefinition>
    <timeDuration>P3D</timeDuration>
  </timerEventDefinition>
</boundaryEvent>
6.1.2、异常边界事件

异常边界事件用来捕获嵌入子流程或调用活动抛出的异常。异常在抛出之后被主流程的异常边界事件捕获,同时嵌入子流程或调用活动中的活动也被中断执行。

异常边界事件的图形化表示是在中间事件的基础上嵌入了一个“雷电”符号,如图4-45所示。

在这里插入图片描述

图4-46展示了一个网上商场的模拟流程,包含了异常边界事件处理。
在这里插入图片描述

<subProcess id="subprocess1" name="付款子流程">
  <!-- 部分省略... -->
  <endEvent id="endevent2" name="ErrorEnd">
    <!-- 抛出一个异常结束事件 -->
    <errorEventDefinition errorRef="A001"/>
  </endEvent>
</subProcess>
<!-- 
  attachedToRef指定到了子流程subprocess1,在子流程中"银行付款"节点出现异常之后退出付款子流程,
  紧接着被"附加"在子流程的异常边界事件捕获并执行唯一的输出流"处理异常"任务
-->
<boundaryEvent id="boundaryerror1" attachedToRef="subprocess1" cancelActivity="false">
  <!-- 捕获一个异常事件 -->
  <errorEventDefinition errorRef="A001"/>
</boundaryEvent>
6.1.3、信号边界事件

信号边界事件可以捕获流程执行过程中抛出的信号,可以“附加”在各种活动和子流程上。信号边界事件不仅可以捕获本流程的信号,还可以捕获到其他流程的信号事件,而且如果在一个活动或子流程上定义了多个信号边界事件并监听同一个信号,则会同时触发,因为对应的信号抛出
事件是全局的。

信号边界事件的图形化表示在边界事件的基础上嵌入了三角形,如图4-48所示,对应的XML描述如下所示:

在这里插入图片描述

<boundaryEvent id="boundary" attachedToRef="task" cancelActivity="true">
  <signalEventDefinition signalRef="alertSignal"/>
</boundaryEvent>
6.1.4、取消边界事件

取消边界事件是专门针对事务子流程所设立的,用来捕获子流程中抛出的取消事件,取消边界事件的图形表示在中间事件的基础上嵌入空心的“X”,如图4-48所示,对应的XML描述如代码清单4-45所示。
在这里插入图片描述

<boundaryEvent id="boundary2" attachedToRef="transaction">
  <cancelEventDefinition/>
</boundaryEvent>

值得注意的是,取消边界事件只能和事务子流程结合使用,不能附加在其他的活动上。

在结合事务子流程使用时需要注意几点:

  • 一个事务子流程只允许附加一个取消边界事件。
  • 如果事务子流程中嵌套了子流程,仅仅触发已经完成了的子流程的补偿事件。
  • 对于多实例的事务子流程,如果其中一个实例触发了取消事件,那么其他的实例也同样会被触发取消边界事件。

取消边界事件不需要设置cancelActivity属性,因为它总是“中断”取消边界事件附加任务后续活动的执行。

6.1.5、补偿边界事件

补偿边界事件用于事务子流程(嵌入子流程不支持补偿边界事件)中针对事务失败后的业务逻辑进行补偿。

补偿边界事件的图形化表示在中间事件的基础上嵌套了
两个左三角符号。和其他的边界事件不同的是,补偿边界事件的输出流不是一个顺序流而是一个“关联”,用虚线表示。

如图4-49展示了补偿边界事件及关联补偿活动,对应的XML描述如代码如下所示:
在这里插入图片描述

<boundaryEvent id="Event_1" name="捕获取消结束事件" attachedToRef="Task_2">
  <!-- compensateEventDefinition表示一个补偿事件 -->
  <compensateEventDefinition id="EventDef_1" />
</boundaryEvent>
<!-- association关联两个对象,和顺序流类似 -->
<association id="Association_1" sourceRef="Event_1" targetRef="Task_4" associationDirection="One"/>
<!--isForCompensation声明这是一个补偿类型的活动 --> 
<serviceTask id="Task_4" name="退回已扣金额" isForCompensation="true"/>

注意:如果补偿边界事件"附加"的活动是多实例,当抛出补偿事件时,每一个实例都会被触发补偿边界事件。

6.2、中间捕获事件

中间捕获事件是流程中的“拦路虎”,根据事件的不同类型需要使用不同的方式才能继续执行后续的输出流的活动。例如执行完一个任务1天后执行下一个活动,或者一个指定的信号、消息被接收到之后再执行后续输出流的活动。

中间捕获事件在图形表现形式上和边界事件是有区别的:边界事件需要“附加”在一个活动上,而且不需要输入流;而中间捕获事件必须连接一个输入流和一个输出流,所以根据这个特性命名为中间捕获事件。另外,所有的中间捕获事件的图标都是空心。

中间捕获事件的基本XML描述如下所示:

<intermediateCatchEvent id="event">
  <xxxEventDefinition/>
</intermediateCatchEvent>
6.2.1、定时器中间捕获事件

定时器中间捕获事件和定时启动事件、定时器边界事件的功能、
配置参数的方式都类似,都在一个特定的时间或指定间隔多长时间之后被触发,从而执行输出流后面的活动。下面代码列出了定时器中间捕获事件的XML描述:

<intermediateCatchEvent id="timerintermediatecatchevent1" name="TimerCatchEvent">
  <timerEventDefinition>
    <timeDuration>PT5M</timeDuration>
  </timerEventDefinition>
</intermediateCatchEvent>

定时器中间捕获事件的图形化表示和定时器边界事件一样,唯一的区分方式就是在流程中所处的位置,下图展示了定时器中间捕获事件的应用场景。
在这里插入图片描述

定时中间捕获事件的XML的描述方式和定时器边界事件类似,不同的是由intermediateCatchEvent包裹timerEventDefinition。

<startEvent id="startevent1" name="Start"/>
<userTask id="usertask1" name="任务一"/>
<sequenceFlow id="flow1" name="" sourceRef="startevent1" targetRef="usertask1"/>
<intermediateCatchEvent id="timerintermediatecatchevent2" name="TimerCatchEvent">
  <timerEventDefinition>
    <timeDuration>PT5M</timeDuration>
  </timerEventDefinition>
</intermediateCatchEvent>
<sequenceFlow id="flow2" name="五分钟后执行" sourceRef="usertask1" targetRef="timerintermediatecatchevent2"/>
<!-- 后续省略... -->
6.2.2、信号中间捕获事件

信号中间捕获事件用来捕获被当前流程或其他流程抛出的信号事件,捕获的条件就是信号的ID一致。信号中间捕获事件的图形化表示是在中间事件的基础上嵌入一个空心三角符号,如图4-51所示,对应的XML描述如代码清单如下所示:

在这里插入图片描述

<intermediateCatchEvent id="signal">
  <signalEventDefinition signalRef="doXxxSignal"/>
</intermediateCatchEvent>

图4-52展示了一个信号中间捕获事件的应用场景,在“处理任务”的用户任务完成之后,流程处于“等待”状态,一直接收到指定的信号才能继续执行。

在这里插入图片描述

<process id="process1" name="process1">
<startEvent id="st1" name="Start"/>
  <sequenceFlow id="fl1" name="" sourceRef="st1" targetRef="ut1"/>
  <userTask id="ut1" name="处理任务"/>
  <sequenceFlow id="fl2" name="" sourceRef="ut1" targetRef="signalintermediatecatchevent"/>
  <!-- 
    中间捕获事件,并设置信号引用,在第一个“处理任务”的用户任务完成之后通过输出流流转到信号中间捕获事件,
    使流程处于等待状态,在有信号被抛出之后流程重新被激活,继续执行后续活动。
  -->
  <intermediateCatchEvent id="signalintermediatecatchevent" name="SinalCatchEvent">
    <signalEventDefinition signalRef="signal-001"/>
  </intermediateCatchEvent>
  <sequenceFlow id="fl3" name="" sourceRef="signalintermediatecatchevent" targetRef="en1"/>
  <endEvent id="en1" name="End"/>
</process>
<!-- 定义一个id为signal-001的信号 -->
<signal id="signal-001" name="发送通知信号"/>

注意:当抛出的信号事件匹配到多个相同的信号捕获事件时(可以是不同流程),每个匹配到的事件均会被执行,这也被称为“广播式”。

6.2.3、消息中间捕获事件

消息中间捕获事件和信号中间捕获事件类似,不同的是信号事件是“广播式“传播,而消息中间捕获事件是定向一对一的传递,也就是说一次只能把一个消息发给一个指定的流程实例。

消息中间捕获事件的图形化表示是在中间事件的基础上嵌入一个空心的信封图标,如图4-53所示,下面代码列出了消息中间捕获事件的XML描述:
在这里插入图片描述

<intermediateCatchEvent id="messageCatch">
  <messageEventDefinition messageRef="continueXxx"/>
</intermediateCatchEvent>

图4-54展示了一个消息中间捕获事件的应用场景,在“处理任务”的用户任务完成之后,流程处于“等待”状态,一直接收到指定名称的消息才能继续执行。
在这里插入图片描述

<process id="process">
<startEvent id="theStart" name="Start"/>
  <sequenceFlow id="sfl1" name="" sourceRef="theStart" targetRef="task"/>
  <userTask id="task" name="处理任务"/>
  <sequenceFlow id="sfl2" name="" sourceRef="task" targetRef="messageCatch"/>
  <!-- 
    定义了消息中间捕获事件,在第一个用户任务“处理任务”完成之后,通过输出流流转到消息中间捕获事件,
    使流程处于等待状态,当此流程实例收到消息之后,流程重新被激活继续执行后续活动(因为消息事件是一
    对一方式传播)。 
  -->
  <intermediateCatchEvent id="messageCatch">
    <messageEventDefinition messageRef="newInvoice"/>
  </intermediateCatchEvent>
  <sequenceFlow id="sfl3" name="" sourceRef="messageCatch" targetRef="theEnd"/>
  <endEvent id="theEnd" name="End"/>
</process>
<!-- 定义了一个id为newIncoice的消息 -->
<message id="newInvoice" name="newInvoiceMessage"/>

6.3、中间抛出事件

中间抛出事件和中间捕获事件是两个相互依赖的关系,中间捕获事件需要有事件抛出才能被触发,而中间抛出事件需要有对应的捕获事件接收才有意义。

中间抛出事件一般用在一个任务完成后需要发送通知或执行其他系统任务的场景,工作流引擎会对抛出的事件进行传播(不同类型的事件有不同的作用范围)。

中间抛出事件的图形化表示和中间捕获事件正好相反,使用实心的图标而非中间捕获事件的空心图标。

中间抛出事件的基本XML表示如下所示:

<intermediateThrowEvent id="throwEvent">
  <xxxxEventDefinition/>
</intermediateThrowEvent>
6.3.1、空中间抛出事件

空中间抛出事件是BPMN2.0规范中没有任何功能的事件,因此执行到空中间抛出事件时直接跳过。从业务层面对其理解,可以把它作为中间状态、结果的处理器,这就要借助Activiti的扩展功能来实现了,可以为它添加到监听器中执行我们预设的业务功能。

空中间抛出事件的图形化表示和中间事件的基础形状一样,仅仅只有一个双层圆形,如图4-55所示,对应的XML描述如下所示:
在这里插入图片描述

<intermediateThrowEvent id="noneintermediatethrowevent" name="NoneThrowEvent">
  
</intermediateThrowEvent>

这样的事件有何作用呢?BPMN是用语言方式描述业务逻辑,明确地告知用户流程的流向及每一步的处理任务,空中间抛出事件虽然是一个空的任务,但是可以借助Activiti对大多数活动、事件添加的扩展功能使其更有意义,下面代码为空中间抛出事件添加了一个监听器。

<intermediateThrowEvent id="noneintermediatethrowevent" name="NoneThrowEvent">
  <extensionElements>
    <activiti:executionListener class="xxx.xxx.JavaClass"/>
  </extensionElements>
</intermediateThrowEvent>

为空中间抛出事件添加了Activiti的扩展功能activiti:executionListener,让空中间抛出事件更有意义,在设置了监听的class名称。在实际应用过程中,可以在监听器中书写业务逻辑,当然流程变量也是可以获取的。

6.3.2、信号中间抛出事件

顾名思义,信号中间抛出事件可以抛出一个信号,然后交给引擎传播信号事件。

信号中间抛出事件的图形化表示是在中间事件的基础上嵌套一个实心的三角符号,如图4-56所示,对应的XML描述如代下所示:

在这里插入图片描述

<intermediateThrowEvent id="noneintermediatethrowevent" name="SignalThrowEvent">
  <signalEventDefinition signalRef="signal-001"/>
</intermediateThrowEvent>

图4-57展示了信号中间抛出事件的一个应用场景。
在这里插入图片描述

<startEvent id="start1" name="Start"/>
<sequenceFlow id="f1" name="" sourceRef="start1" targetRef="task1"/>
<userTask id="task1" name="任务一"/>
<sequenceFlow id="f2" name="" sourceRef="task1" targetRef="siEvent"/>
<!-- 
  在“任务一”执行完成之后直接抛出信号事件并等待匹配的信号捕获事件执行完成,
  如果在执行过程中有异常抛出,那么事务回滚,否则继续执行信号中间抛出事件的输出流,也就是创建“任务二”。
-->
<intermediateThrowEvent id="siEvent" name="SignalThrowEvent">
  <signalEventDefinition signalRef="signal-001"/>
</intermediateThrowEvent>
<sequenceFlow id="f3" name="" sourceRef="siEvent" targetRef="task2"/>
<userTask id="task2" name="任务二"/>
<sequenceFlow sourceRef="task2" targetRef="end1"/>
<endEvent id="end1" name="End"/>

代码的抛出是“同步”的,如果其中一个失败,那么其他的已经执行过的信号事件全部回滚;同样,也可以设置非同步即“异步”执行,意思就是引擎只会把信号抛出而不关心事务,在Activiti中会先创建和匹配到的信号事件相同数量的作业(Job,定时执行),由引擎负责作业的调度,不会等待其他信号事件的执行结果而继续执行信号中间抛出事件的输出流。

<intermediateThrowEvent id="siEvent" name="SignalThrowEvent">
  <signalEventDefinition signalRef="signal-001" activiti:async="true"/>
</intermediateThrowEvent>

7、监听器

监听器是Activiti在BPMN2.0规范基础上扩展的功能,是业务与流程的“非侵入性粘合剂”。熟悉设计模式的读者应该能很快联想到“观察者模式”的一种实现“监听器模式”,
在Activiti中开发人员可以通过配置监听器的方式监听各种动作,例如流程启动、结束、任务创建、任务完成,甚至是经过某个顺序流时。

监听器分为两类:执行监听器和任务监听器,和其他的Activiti扩展模型一样,监听器需要包含在BPMN2.0规范的<extensionElements>标签中。

7.1、执行监听器

执行监听器允许在执行流程过程中执行Java代码(实现了监听器接口)或表达式。执行监听器可以捕获的事件如下:

  • 流程实例启动、结束
  • 输出流捕获
  • 活动启动、结束
  • 路由开始、结束
  • 中间事件开始、结束
  • 触发开始事件、触发结束事件

多个事件的XML定义方式有一个共同点,都是在对应的监听对象内的extensionElements中添加activiti:executionListener扩展属性。执行监听器使用Activiti的扩展元素<activiti:executionListener>定义,通过event属性指定监听事件的类型(分为三类:start、end、take),并且可以选择监听器执行类型。下表列出了执行监听器的3种监听器执行类型。

  • class:需要实现接口:org.activiti.engine.delegate.ExecutionListener
<activiti:executionListener event="start" class="xxx.xxx.MyListener"/>
  • expression:定义一个表达式,类似EL的语法。这里的pojo是一个Bean的名称(可以用spring代理),还可以在exxpreesion中通过计算一个表达式配置监听器的名称。
<activiti:executionListener expression="${pojo.method(execution.eventName)}" event="end"/>
  • delegateExpression:class属性必须显示指定一个class的全路径,如me.kafeitu.ExecutionListenerXxx。在有些情况下需要根据业务的不同动态指定一个接口实现,delegateExpression允许指定一个实现了监听接口(参考class属性的限制)类的name,具体的类可以在引擎中配置或由spring代理。
<activiti:executionListener event="start" delegateExpression="${executionListenerBean}" />
<!-- 其中${executionListenerBean}为一个Bean对象的名称 -->

监听输出流(包含经过顺序流、条件顺序流)中没有开始、结束事件类型,只有在经过输出流的时候被触发,如下所示。

<userTask id="firstTask" name="Task1"/>
<!-- 定义了take类型执行监听器,当流程经过这个顺序流的时候将被触发 -->
<sequenceFlow sourceRef="firstTask" targetRef="secondTask">
  <extensionElements>
    <activiti:executionListener event="take" class="pers.zhang.TestDemo"/>
  </extensionElements>
</sequenceFlow>
<userTask id="secondTask" name="Task2"/>

此外还可以在监听中使用<activiti:field/>注入字段,在流程运行时监听实现类可以通过接口中的对象获取字段的值,如下所示:

<userTask id="firstTask" name="Task1"/>
<!-- 定义了take类型执行监听器,当流程经过这个顺序流的时候将被触发 -->
<sequenceFlow sourceRef="firstTask" targetRef="secondTask">
  <extensionElements>
    <activiti:executionListener event="take" class="pers.zhang.TestDemo">
      <!-- activiti:field为监听器TestDemo注入了名称为firstVar的变量 -->
      <activiti:field name="firstVar" stringValue="one"/>
      <activiti:field name="secondVar" stringValue="two"/>
      <!-- 通过计算表达式'user'+counter的值为变量thirdVar赋值 -->
      <activiti:field name="thirdVar" expression="${'user' + counter}"/>
    </activiti:executionListener>
  </extensionElements>
</sequenceFlow>
<userTask id="secondTask" name="Task2"/>

7.2、任务监听器

相对于执行监听器的使用范围来说,任务监听器的使用范围要小很多,因为它只能应用于用户任务,用来监听3种事件。

  • create:在任务被创建且所有的任务属性设置完成之后才触发。
  • assignment:在任务被分配给某个办理人之后触发。有一点需要注意,assignment事件总是在create事件触发之前被触发,因为任务的办理人是一个属性,而create事件需要逐一处理任务的办理人、候选人、候选组属性。
  • complete:在配置了监听器的上一个任务完成时触发,也就是在运行时任务数据被删除之前(Activiti的数据分为运行时和历史两种类别,当运行时数据处理完毕后将
    会被清理)触发。

下表列出了任务监听器的3种监听器执行类型:

  • class:需要实现接口:org.activiti.engine.delegate.TaskListener

  • expression:定义一个表达式,类似EL的语法。这里的pojo是一个Bean的名称(可以用spring代理),还可以在exxpreesion中通过计算一个表达式配置监听器的名称。

<activiti:executionListener expression="${pojo.method(execution.eventName)}" event="end"/>
  • delegateExpression:class属性必须显示指定一个class的全路径,如me.kafeitu.ExecutionListenerXxx。在有些情况下需要根据业务的不同动态指定一个接口实现,delegateExpression允许指定一个实现了监听接口(参考class属性的限制)类的name,具体的类可以在引擎中配置或由spring代理。
<activiti:executionListener event="start" delegateExpression="${executionListenerBean}" />
<!-- 其中${executionListenerBean}为一个Bean对象的名称 -->

下面列出了请假流程中的“领导审批”节点完成时触发me.kafeitu.listener.TaskListenerForLeaveOnComplete监听实现类。

<!-- userTask中添加了activiti:taskListener元素来定义任务监听器 -->
<userTask id="leaderAudit" name="销假">
  <extensionElements>
    <!-- 
      complete设置当任务完成时触发,任务监听器和执行监听器一样可通过添加
      <activiti:field/>为任务监听器注人字段 
    -->
    <activiti:taskListener event="complete" class="me.kafeitu.listener.TaskListenerForLeaveOnComplete"/>
  </extensionElements>
</userTask>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是 activiti-bpmn-layout-5.22.0 的 pom.xml 文件内容: ``` <project> <modelVersion>4.0.0</modelVersion> <groupId>org.activiti</groupId> <artifactId>activiti-bpmn-layout</artifactId> <version>5.22.0</version> <packaging>jar</packaging> <name>Activiti BPMN Layout</name> <description>Layout algorithms for BPMN 2.0 diagrams</description> <dependencies> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-bpmn-model</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.4</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>${main.class}</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> </project> ``` 该 pom.xml 文件定义了项目的基本信息,包括: - 项目的 groupId、artifactId 和 version; - 项目的名称和描述; - 项目依赖的其他库(activiti-bpmn-model、commons-lang3 和 slf4j-api); - 项目的构建配置,包括编译器版本、打包方式和插件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值