Activiti工作流学习-----基于5.19.0版本(7)

时间:2023-01-14 20:09:26

八、BPMN 2.0流程图详解

BPMN 2.0的标准的出现是好事,用户不在被某个工作流开发商绑架或者在工作流中开发妥协,Activiti作为BPMN标准的一套解决方案,使得用户在选择工作流框架时可以平滑的迁移过渡。也有负面的不好的消息,就是BPMN标准是大量开会讨论和开发商妥协的结果(一般这是在做梦),所以用户在阅读BPMN规范会感觉到它太笨重了,Activiti开发工作流将用户体验放到第一位置,开发出了工作流设计插件。工作流官方推荐使用工作流设计插件。

8.1 事件(Event)

每个流程设计都有start event和end event,而在整个流程中发生的事件都是有event来表示。事件在设计面板中用圆圈表示,在BPMN 2.0中主要有两种事件:

  • Catching:当流程执行到事件的时候, 它会等待被触发。而触发条件需要用户配置在这个圆圈图标的属性里面,和下面第二种圆圈图标外形上的区别是:Catching图标里面是空的,就是空圈。
  • Throwing:当流程执行到事件的时候,它会立即触发,同样的触发器也需要配置在图标属性里面,和Catching图标不同是圆圈图标里面有东西是,黑色的。

总的来说,事件定义决定了事件的语义。如果没有事件定义,这个事件就不做什么特别的事情。 没有设置事件定义的开始事件不会在启动流程时做任何事情。如果给开始事件添加了一个事件定义 (比如定时器事件定义)我们就声明了开始流程的事件 "类型 " (这时定时器事件监听器会在某个时间被触发)。

8.1.1 定时器事件

  定时器事件是根据指定的时间触发的事件。可以用于开始事件(start event), 中间事件(intermediate event)和边界事件(boundary event)。定时器事件必须含有下面一种属性的配置。

timeDate:指定ISO 8601格式的日期定时器激活。(至于ISO 8601日期格式可以详见百度:http://baike.baidu.com/view/931641.htm)

<timerEventDefinition>
<timeDate>2016-08-23T18:13:00</timeDate>
</timerEventDefinition>

timeDuration:定义定时器经过多少时间后激活。时间段也是取得ISO 8601格式,比如在一年三个月五天六小时七分三十秒内,可以写成P1Y3M5DT6H7M30S。

<timerEventDefinition>
<timeDuration>P10D</timeDuration>
</timerEventDefinition>

timeCycle:定义定时器重复间隔,在某些场景使用,比如周期性的启动流程,任务超时发送提醒。timeCycle的设置目前有两种方式:ISO 8601和Cron表达式(quartz任务调度框架提供的解决方案),activiti默认是使用ISO 8601。例如现在重复三次,每次间隔10小时:

 <timerEventDefinition>
<timeCycle activiti:endDate="2016-08-22T16:42:11+00:00">R3/PT10H</timeCycle>
</timerEventDefinition>
<timerEventDefinition>
<timeCycle>R3/PT10H/${EndDate}</timeCycle>
</timerEventDefinition>

其中endDate是可选的配置,上面使用了两张方式加上了endDate, 定时器将会在指定的时间停止工作。

此外如果你使用Cron 表达式,可以这样写:

0 0/5 * * * ?

注意: 第一个数字表示秒,而不是像通常Unix cron中那样表示分钟。重复的时间周期能更好的处理相对时间,它可以计算一些特定的时间点 (比如用户任务的开始时间),而cron表达式可以处理绝对时间, 这对定时启动事件特别有用。

你可以使用表达式进行配置,在里面动态设置值,不过该值需要为ISO 8601或者(cron表达式)格式,

<boundaryEvent id="escalationTimer" cancelActivity="true" attachedToRef="firstLineSupport">
<timerEventDefinition>
<timeDuration>${duration}</timeDuration>
</timerEventDefinition>
</boundaryEvent>

定义器的执行有先决条件:async executor和job被启用定时器才会激活(例如在activiti.cfg.xml中配置了jobExecutorActivate或者asyncExecutorActivate为true)。

8.1.2 错误事件定时器

BPMN的错误是关于业务上面的异常处理,它和java代码上的异常是不同的,两者完全不同,比如这样配置一个错误事件:

 <endEvent id="myErrorEndEvent">
<errorEventDefinition errorRef="myError" />
</endEvent>

8.1.3 信号事件

信号事件会引用一个命名的信号,所谓的信号作用在整个流程引擎全局范围内,会发送给所有活跃的处理器。信号事件在BPMN文件中是定义在signalEventDefinition中,其中的signalRef属性可以引用前面声明的signal,而signal在definitions的根节点中作为子元素,下面就是一个例子

 <definitions... >
<!-- 声明signal -->
<signal id="alertSignal" name="alert" /> <process id="catchSignal">
<intermediateThrowEvent id="throwSignalEvent" name="Alert">
<!-- signal event definition -->
<signalEventDefinition signalRef="alertSignal" />
</intermediateThrowEvent>
...
<intermediateCatchEvent id="catchSignalEvent" name="On Alert">
<!-- signal event definition -->
<signalEventDefinition signalRef="alertSignal" />
</intermediateCatchEvent>
...
</process>
</definitions>

Throwing信号事件:在BPMN中配置或者用代码实现都可以发出信号,而使用代码可以这样子:

 RuntimeService.signalEventReceived(String signalName);
RuntimeService.signalEventReceived(String signalName, String executionId);

这两个方法不同之处在于第一个方法发出全局的信号,第二个方法会指定execution发出信号。

Catching信号事件:被中间事件和边界事件捕获的事件。

前面第二个方法的executionId或者查询当前活跃的信号事件方法如下:

 List<Execution> executions = runtimeService.createExecutionQuery()
.signalEventSubscriptionName("alert")
.list();

信号的作用范围:

默认的信号作用域是整个流程引擎,也就是说你可以throw一个信号在多个流程实例之间并发生作用。有时候我们需要作用范围仅仅是在发生事件的流程实例里,限制信号的作用范围,可以这样配置,不过它并不是BPMN2.0规范中的,是activiti独有的,其中activiti:scope的默认值是global。

 <signal id="alertSignal" name="alert" activiti:scope="processInstance"/>

信号事件案例:这里我使用了Activiti Explorer在线流程图设计器设计了两张图,展示了信号交互。

第一张流程是从保险规则变动开始的,然后相关人员审批,如果同意后会发出保险条件发生改变的信号。

Activiti工作流学习-----基于5.19.0版本(7)

第二张流程中将在红框标识的地方会捕获(Catching)这个事件,使得保险合同在这时重新计算。

Activiti工作流学习-----基于5.19.0版本(7)

信号是通过广播传递给所有活跃的事件,但有时候我们并不是想要这种结果,譬如下图:

Activiti工作流学习-----基于5.19.0版本(7)

上面流程图的意思是执行“do something”任务时出现的错误,会被边界错误事件捕获, 然后使用信号传播给并发路径上的分支,进而中断"do something inparallel"任务, 但是,根据信号的广播含义,它也会传播给所有其他订阅了信号事件的流程实例,这就是我们不想要的。这时我们需要调用前面介绍触发信号的API的第二个方法进行手动关联。

8.1.4 消息事件

消息事件会引用已命名的消息。和信号不同的是,消息具有名称和内容,并且消息始终指定了单个的接收者。 

消息事件定义在BPMN文件的messageEventDefinition元素中,其中messageRef属性值来自于message,至于message是配置在definitions的根元素里面。下面是一个例子:

<definitions id="definitions"
xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="Examples"
xmlns:tns="Examples"> <message id="newInvoice" name="newInvoiceMessage" />
<message id="payment" name="paymentMessage" /> <process id="invoiceProcess"> <startEvent id="messageStart" >
<messageEventDefinition messageRef="newInvoice" />
</startEvent>
...
<intermediateCatchEvent id="paymentEvt" >
<messageEventDefinition messageRef="payment" />
</intermediateCatchEvent>
...
</process> </definitions>

抛出消息事件:Activiti作为嵌入式的引擎,它不会关注怎么接收消息,接收消息取决于你的环境和特定的平台,比如你可以连接到JMS消息队列或者执行WebService或REST请求,这是需要你的应用层架构中进行实现,Activiti只是其中一部分。

在你的应用里面收到消息,你需要处理它,如果是启动流程实例的消息,可以参考下面的API:

 ProcessInstance startProcessInstanceByMessage(String messageName);
ProcessInstance startProcessInstanceByMessage(String messageName, Map<String, Object> processVariables);
ProcessInstance startProcessInstanceByMessage(String messageName, String businessKey, Map<String, Object> processVariables);

这些API允许使用引用的消息进行启动流程实例。如果流程实例需要接收这些消息,首先你需要关联指定流程实例和消息,然后触发处于等待的流程,使用RunTimeService可以触发基于消息的流程。

 void messageEventReceived(String messageName, String executionId);
void messageEventReceived(String messageName, String executionId, HashMap<String, Object> processVariables);

查询订阅消息事件的流程定义:

对于start event的消息,消息事件关联到指定的流程定义,消息的订阅可以使用ProcessDefinitionQuery查询。

 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.messageEventSubscription("newCallCenterBooking")
.singleResult();

对于明确的消息是对应一个流程的,所以查询结果一般是0个或者1个,如果是流程定义更新,那么方法返回最新的流程定义。

如果是中间消息事件,订阅的消息关联到特定的流程,我们可以使用ExecutionQuery进行查询:

 Execution execution = runtimeService.createExecutionQuery()
.messageEventSubscriptionName("paymentReceived")
.variableValueEquals("orderId", message.getOrderId())
.singleResult();

下面的实例通过两个不同的消息进行启动流程实例:

Activiti工作流学习-----基于5.19.0版本(7)

在某些需要多个start event启动流程实例需要统一的处理方式的时候是有用处的。