疯狂Activiti6.0连载(23)BPMN事件分类与事件定义


声明:本文转载自https://my.oschina.net/JavaLaw/blog/1584803,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

本文节选自《疯狂工作流讲义(第2版)》

疯狂Activiti电子书:https://my.oschina.net/JavaLaw/blog/1570397

工作流Activiti教学视频:https://my.oschina.net/JavaLaw/blog/1577577

事件分类与事件定义

        BPMN2.0规定了多种的事件定义,这些事件定义被嵌套到不同的事件中,则可以成为不同类型的事件,这些事件具有不同的特性,并且BPMN2.0规定了这些事件允许出现的位置以及其作用,本小节将对BPMN2.0规范定义的事件进行分类。

按照事件的位置分类

        对事件按照位置进行分类,主要可分为开始事件、中间事件和结束事件,其中中间事件可以分为两类:单独作为流程节点的中间事件和依附在某个流程节点的中间事件,本书中所讲的中间事件是指单独作为流程节点的事件,依附在某个流程节点的中间事件,本书将称为边界事件,那么按照位置进行分类,本书将会有以下4种类型的流程事件:

  • 开始事件:表示流程开始的事件。
  • 结束事件:表示流程结束的事件。
  • 中间事件:出现在流程中,单独作为流程节点的事件。
  • 边界事件:附属于某个流程节点(如子流程、流程任务)的事件。

按照事件的特性分类

        按照事件的特性进行分类,可以将事件分为Catching事件和Throwing事件,Catching事件会一直等待被触发,而Throwing事件会自动触发并反馈结果,全部的开始事件是Catching事件,因为开始事件总会等待被触发,每种开始事件的触发条件不一样而已,例如定时器开始事件,就需要时间符合条件后触发。全部的结束事件是Throwing事件,结束事件会自动执行并返回结果。全部的边界事件是Catching事件,因为这些边界事件总会符合某些特定条件才会触发。部分的中间事件为Catching事件(如Signal Intermediate Catching Event),部分的中间事件为Throwing事件(如Signal Intermediate Throwing Event)。

事件定义概述

        事件主要用于体现Catching事件的触发和Throwing事件的结果,BPMN2.0规范中规定了多种事件定义:CancelEventDefinition、CompensationEventDefinition、ConditionalEventDefinition、ErrorEventDefinition、EscalationEventDefinition、MessageEventDefinition、LinkEventDefinition、SignalEventDefinition、TerminateEventDefinition和TimerEventDefinition。除此之外,还包括无指定事件和复合事件,无指定事件是指在一个事件中没有指定任何事件定义,复合事件是指在一个事件中包含多个事件定义。每个事件定义可以按照规定与事件(开始事件、结束事件和中间事件)结合,成为特定的事件。例如将TimerEventDefinition与开始事件结合,成为定时器开始事件。

定时器事件定义

        定时器事件是一个由定时器触发的事件,定时器事件的定义可以嵌套在开始事件、中间事件或者边界事件中。在流程文件中使用timerEventDefinition元素表示一个定时器事件定义,假设在一个开始事件中定义一个定时器事件定义,配置如下:

        <startEvent id="timerstartevent1" name="Timer start">             <timerEventDefinition></timerEventDefinition>         </startEvent> 

        只定义一个timerEventDefinition元素是不足以描述该定时器事件,需要往timerEventDefinition元素下加入子元素,timerEventDefinition下允许定义的子元素有以下3个:

  • timeDate:指定一个定时器触发的时间。
  • timeDuration:指定定时器激活后多久的时间内该定时器被运行。假设定时器在当前时刻激活,设置该值为PT5M,即会在5分钟后执行。
  • timeCycle:指定定时器的重复间隔,该元素常应用于一些定时任务的执行,包括流程的定时启动、任务提醒等。

        以上3个timerEventDefinition的子元素,在定义时间时,都需要遵守ISO 8601的国际标准,该标准是日期和时间的表示方法,其中timeCycle还支持使用cron表达式来设定定时器的重复间隔。代码清单11-1为timerEventDefinition的三个子元素的配置示例。

        代码清单11-1:

        codes\11\11.1\event-definition\resource\bpmn\timer\TimerDefine1.bpmn,

        codes\11\11.1\event-definition\resource\bpmn\timer\TimerDefine2.bpmn,

        codes\11\11.1\event-definition\resource\bpmn\timer\TimerDefine3.bpmn

            <timerEventDefinition>                 <timeDate>2018-10-10T06:00:00</timeDate>             </timerEventDefinition>             <timerEventDefinition>                 <timeDuration>PT5S</timeDuration>             </timerEventDefinition>             <timerEventDefinition>                 <timeCycle>R2/PT1M</timeCycle>             </timerEventDefinition> 

        代码清单11-1中使用了timerEventDefinition的3个子元素进行配置,该3个配置均使用了ISO 8601格式的日期时间。ISO 8601标准中,如果需要同时表达日期和时间,需要在时间前加入大写字母T,如2018-10-10T06:00:00,表示2018年10月10日的6点0分0秒。如果需要表示一个时间段,可以加入大写字母P,例如P1D表示1天内,如果要表示某个时间段(精确到时间),则需要在时间前加入T,例如PT10M,即表示10分钟内。需要重复的时间,可以加入大写字母R,如本例的“R2/PT1M”表示执行2次,每次持续1分钟。元素timeCycle除了可以使用ISO 8601标准定义时间外,还可以使用cron表达式,cron表达式将在下一小节中讲解。

cron表达式

        一个cron表达式是一串由6到7个域组成并且以空格分隔的字段串,cron原来是UNIX的工具之一,主要用于进行任务调度,cron核心使用的就是cron表达式来处理任务调度,以下为一个简单的cron表达式:10 * * * * ?,表示每分钟的第10秒将会触发。

        一个完整的cron表达式总共有7个域(以空格分隔),从左到右表示秒、分、小时、月份中的日期、月份、星期中的日期和年份,其中年份域为可选项,例如有以下这个cron表达式:1 2 3 4 5 ? 2013,该表达式共有7个域,以空格隔开,该表达式表示2013年5月4日03时2分1秒,在该表达式中,第6个域(星期中的日期)使用了问号,表示并不需要关心该域,由于该表达式指定了第4个域(月份中的日期),因此此处使用问号,可以理解为不关心5月4日是星期几。以下为cron表达式中符号及其作用:

  • *:允许该域使用全部的值。假设在秒域值为10且分钟域为*,那么意味着每一分钟的第十秒将会符合表达式条件。
  • ?:只允许出现在第4个域(月份中的日期)和第6个域(星期中的日期),表示不关心该域的取值,由于两个域取值可能存在冲突,因此为不关心取值的域使用该符号。
  • -:该符号表示范围,假设将第3个域(小时)设置为10-12,则表示10点到12点。
  •  ,:该符号表示一个域内并列的多个值,例如第4个域(月份中的日期)值2,4,8,表示2号、4号和8号会触发。
  • /:使用该符号设置步长,假设将第1个域(秒)设置为5/15,表示从第5秒开始,步长为15,即第5、20、35、50秒时均会触发。
  • L:英文Last的缩写,如果出现在第4个域(月份中的日期),则为每个月的最后一天,如果出现第6个域(星期中的日期),则表示该星期的最后一天(周六),如果该符号出现在某个值后,如第6个域值为6L,则表示该月的最后一个星期5。
  • W:英文weekday的缩写,表示周一到周五(工作日),该符号只能出现在第4个域(月份中的日期)并且只能与其他值组合使用,如15W,则表示该月中与15号最接近的工作日。另外,L和W可以在第4个域中混合使用,表示该月的最后一个工作日。
  • #:该符号只能出现在第6个域(月份中的日期),表示该月的第几天,如果设值为#5,表示该月的第5天,如果设置为4#3,则前面的4表示星期中的日期,即4#3表示该月的第3个星期三(7是周6)。

        各个符号允许出现的域以及每个域的取值范围如下所示:

  • 秒:必选项,取值范围为0到59,允许出现的符号有“, - * /”。
  • 分钟:必选项,取值范围为0到59,允许出现的符号有“, - * /”。
  • 小时:必选项,取值范围为0到23,允许出现的符号有“, - * /”。
  • 月份中的日期:必选项,取值范围为1到31,允许出现的符号有“, - * ? / L W”。
  • 月份:必选项,取值范围为1-12或者月份的英文缩写,允许出现的符号有“, - * /”。
  • 星期中的日期:必选项,取值范围为1-7或者英文缩写,允许出现的符号有“, - * / L #”。
  • 年份:非必选项,取值范围为1970-2099,允许为空值,允许出现的符号有“, - * /”。

        假设现有一个检查工作日志的业务流程,需要在周一到周五下班时启动,那么创建定时器事件定义,并且使用timeCycle元素配合cron表达式进行时间定义:

            <timerEventDefinition>                 <timeCycle>* * 18 ? * 1,2,3,4,5</timeCycle>             </timerEventDefinition> 

        以上的cron表达式为“* * 18 ? 1,2,3,4,5”,表示周一到周五的18点将会触发。如果使用timeCycle元素定义周期性的任务,笔者觉得使用cron表达最为合适,即使cron表达式在刚接触时有点难理解,但是它强大的时间定义能力已经让众多的任务调度框架都对其提供支持,例如Quartz框架。

错误事件定义

        错误事件会被定义的错误信息所触发,BPMN中的错误事件主要用于处理流程中出现的业务异常。需要注意的是,流程中的业务异常与Java中的Exception是不同的概念,在设计业务时,如果满足一定的条件,那么就会触发错误事件。例如有一个检查服务器进程的业务流程,每隔6小时检查服务器的某个进程是否存在,如果该服务进程不存在,则触发原来定义好的错误事件,进入特定的处理流程。在BPMN2.0规范中,使用errorEventDefinition元素定义一个错误事件,配置如下所示:

<errorEventDefinition errorRef="errorRef"></errorEventDefinition>

        定义错误事件的errorEventDefinition元素下只有一个errorRef属性,errorRef引用定义的error元素id,一个errorEventDefinition可以不提供errorRef属性,表示定义了一没有实现的事件。代码清单11-2为error以及errorEventDefinition的配置。

        代码清单11-2:codes\11\11.2\event-definition\resource\bpmn\ErrorDefine.bpmn

    <error id="myError" errorCode="123"></error>     <process id="testProcess" name="testProcess">         <endEvent id="myErrorEndEvent">             <errorEventDefinition errorRef="myError" />         </endEvent>     </process> 

        在BPMN2.0规范中,规定了一个error元素需要包含以下属性:

  • id:该元素的唯一标识。
  • name:元素的名称,BPMN2.0规范中明确流程XML文件中需要支持该属性,Activiti并没有对其提供实现(允许在XML中配置,但是不会读取该属性)。
  • errorCdoe:错误事件编码,当处理业务时抛出相应的异常代码,流程引擎会根据该错误代码自动匹配到该error元素,定义error元素必须设定errorCode属性。
  • structureRef:结构引用属性,根据BPMN2.0规范,该属性用于引用公用的错误定义(引用itemDefinition),Activiti并不会读取该属性(配置了该属性,也不会产生效果)。

        错误事件定义可以被嵌套在开始事件(startEvent)、边界事件(boundaryEvent)和结束事件(endEvent)中成为错误开始事件、错误边界事件和错误结束事件。

信号事件定义

        在讲解第9章流程控制时,就已经使用过信号事件定义,信号事件是一种引用了信号定义的事件,可以使用一个信号向全部的流程发送广播(前提是流程定义使用了同样名称的信号)。定义一个信号事件,需要使用signalEventDefinition元素,与错误事件一样,当使用了signalEventDefinition元素定义一件信号事件时,需要使用signalRef属性来引用一个信号元素(signal),signalRef的值需要为signal的id,信号事件的XML配置如以下代码所示:

        代码清单11-3:codes\11\11.2\event-definition\resource\bpmn\SignalDefine.bpmn

    <signal id="signalA" name="signalA"></signal>     <process id="testProcess" name="testProcess">         ...             <signalEventDefinition signalRef="signalA"></signalEventDefinition>         ...     </process> 

        在BPMN2.0规范中,一个信号元素(signal)允许有以下3个属性:

  • id:该元素的唯一标识,必须提供。
  • name:signal元素的名称,必须提供,否则Activiti在解析流程文件时将会抛出异常,信息为“signal with id XXX has no name”异常。另外,在一个流程定义中,不允许同时出现多个name相同的signal元素,否则将抛出异常,信息为“duplicate signal name XXX”。
  • structureRef:该属性与错误事件的structureRef属性一样,BPMN2.0规范中规定了该属性引用公用的配置,Activiti并没有读取和使用该属性。

        信号事件可以嵌套在边界事件、中间Catching事件和中间Throwing事件中成为信号边界事件、信号中间Catching事件和信号中间Throwing事件,其中信号边界事件和信号中间Catching事件是Catching事件,即这些事件会一直等待信号,接收到信号后事件才会被触发,如果执行流到达这些Catching事件,Activiti会在ACT_RU_EVENT_SUBSCR表中加入相应的事件描述数据。

消息事件定义

        消息事件是一种引用了消息定义的事件,与信号不同的是,消息只能指向一个接收人,而不能像信号一样进行广播。使用messageEventDefinition元素定义一个消息,使用messageRef属性引用一个消息(引用消息元素的id)。在BPMN2.0规范中,messageEventDefinition下还有一个operationRef的子元素,用于在可执行的流程中定义消息事件的具体操作,但是Activiti人实现中并没有使用该operationRef元素。代码清单11-4为消息事件的XML配置。

        代码清单11-4:codes\11\11.2\event-definition\resource\bpmn\MessageDefine.bpmn

    <message id="myMsg" name="myMsg"></message>      <process id="medProcess" name="medProcess">         ...             <messageEventDefinition messageRef="myMsg"></messageEventDefinition>         ...     </process> 

        定义一个消息元素使用message元素,该元素包含以下属性:

  • id:该元素的唯一标识。
  • name:消息的名称,使用RuntimeService的messageEventReceived方法时传入该参数,可选项。
  • itemRef:用于指定该消息引用的itemDefinition元素。

        消息事件可以嵌套在开始事件和中间Catching事件中成为消息开始事件和消息中间Catching事件。其中消息开始事件可以通过RuntimeService的startProcessByKey方法启动流程,如果执行流遇到消息中间Catching事件时,会停留在该流程节点前,一直等待消息的来临,一旦接收到消息,流程才会继续向前执行。

取消事件定义

        在BPMN2.0规范中,取消事件使用在事务子流程(Transaction Sub-Process)模型中,取消事件定义可以使用在边界事件和结束事件中,成为取消边界事件(Cancel Boundary Event)和取消结束事件(Cancel End Event)。以下代码使用cancelEventDefinition元素定义一个取消事件:

<cancelEventDefinition></cancelEventDefinition>

        取消边界事件是Catching事件,会等待被触发,而取消结束事件则为Throwing事件。关于取消边界事件和取消结束事件的作用以及其应用,请参看11.4.3章节。

补偿事件定义

        补偿机制主要用于对已经成功完成的流程作回退处理,因为这些流程的结果有可能不是所期望的,并且希望能将其回退。如果当前的流程活动是激活状态的,那么不能使用补偿机制,但可以考虑使用取消机制,相反,取消有可能会导致补偿的触发,例如在子流程中。

        补偿事件主要用于触发或者处理补偿机制,BPMN2.0规定了补偿事件定义可以嵌套在开始事件、中间Catching事件、中间Throwing事件和结束事件。如果将补偿事件定义使用在中间Catching事件时,不允许作为单独的流程节点,因此如果嵌套在中间Catching事件时,将成为补偿边界事件。使用compensateEventDefinition元素定义一个补偿事件:

<compensateEventDefinition><compensateEventDefinition/>

        该元素除了id属性外,还有waitForCompletion属性,根据BPMN2.0规范,该属性决定抛出的事件是否等待补偿完成,当前版本的Activiti不支持该属性。另外,compensateEventDefinition元素还有一个activityRef属性,如果在中间补偿事件的定义中设置该属性,则补偿的触发就会有针对性,该补偿会只针对指定的已经完成的流程活动,如果不指定该属性,那么补偿将会产生广播的效果,即会触发全部的(符合条件的)补偿事件。

        Activiti目前只对补偿边界事件和补偿中间Throwing事件作了实现,相应请见边界事件和中间Throwing事件章节。

其他事件定义

        除了以上的事件定义外,BPMN2.0还有如下事件定义:条件事件定义(ConditionalEventDefinition)、升级事件定义(EscalationEventDefinition)、连接事件定义(LinkEventDefinition)和终止事件定义(TerminateEventDefinition),目前版本除了终止事件定义外,其余事件还没有提供实现。以下代码片断定义了一个终止事件定义:

<terminateEventDefinition activiti:terminateAll="true"></terminateEventDefinition>

        关于终止结束事件,将在后面章节中讲述。到此,关于Activiti的事件定义已经全部介绍完,从下面开始,将会介绍这些事件定义嵌套在不同事件中的作用及使用。

本文节选自《疯狂工作流讲义(第2版)》

疯狂Activiti电子书:https://my.oschina.net/JavaLaw/blog/1570397

工作流Activiti教学视频:https://my.oschina.net/JavaLaw/blog/1577577

本书代码目录:https://gitee.com/yangenxiong/CrazyActiviti

本文发表于2017年12月05日 10:33
(c)注:本文转载自https://my.oschina.net/JavaLaw/blog/1584803,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 2425 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1