活动可以实现循环,基于转移或活动组合。 循环可以包含等待状态。
为了支持多次自动循环执行,流程虚拟机 把执行的传播从尾部递归转换成while循环。
7.2. 子流程
TODO: 子流程
7.3. 默认执行行为
当一个Activity被用作活动行为, 它可以使用下面的方法从外部控制流程:
◆waitForSignal()
◆take(Transition)
◆end(*)
◆execute(Activity)
◆createExecution(*)
当Activity实现用做活动行为, 没有调用任何下面的流程传播方法,然后 在活动执行时,执行会使用默认执行行为。
默认执行行为定义在下面:
◆如果当前活动有一个默认向外转移,选择它。
◆如果当前活动有一个父活动,回退到父活动。
◆否则,结束这个执行。
流程语言可以重写默认执行行为, 通过重写ExecutionImpl中的 proceed方法。
7.4. 功能活动
活动也可以用作事件监听器,被称作功能活动。 自动活动的例子是发送邮件,执行数据库更新, 生成pdf,计算平均数,等等。 所有这些都是自动活动,没有改变执行流向。 这里是这些活动如何实现:
- public class FunctionalActivity implements Activity, EventListener {
- public void execute(ActivityExecution execution) {
- perform(execution);
- }
- public void notify(EventListenerExecution execution) {
- perform(execution);
- }
- void perform(OpenExecution execution) {
- ...do functional work...
- }
- }
perform方法获得一个OpenExecution, 这是ActivityExecution和 EventListenerExecution的超类。 OpenExecution没有提供任何特定目的的方法, 但是依旧是当前状态,流程定义可以通过变量检验, 这包含了环境信息 对应流程执行。
这些方法其实都不能调用执行传播方法。 所以在perform方法完成后,执行会 执行默认的方式。
7.5. 执行和线程
这一章解释流程虚拟机如何通过客户端的线程, 把一个执行从一个等待状态带到另一个。
当一个客户调用一个执行的一个方法(比如signal方法)。 默认,流程虚拟机会使用线程执行流程 直到它到达一个等待状态。一旦下一个等待状态到达, 这个方法会返回,客户端的线程就会返回。 这是流程虚拟机操作的默认方式。 两个更多的异步执行可以补充默认行为: 异步继续 和异步命令服务。
下一个流程会展示基本理论。 它有三个等待状态和四个自动活动。
图 7.1. 有很多顺序自动活动的流程。
这里是如何构建流程:
- ClientProcessDefinition processDefinition = ProcessFactory.build("automatic")
- .activity("wait 1").initial().behaviour(new WaitState())
- .transition().to("automatic 1")
- .activity("automatic 1").behaviour(new Display("one"))
- .transition().to("wait 2")
- .activity("wait 2").behaviour(new WaitState())
- .transition().to("automatic 2")
- .activity("automatic 2").behaviour(new Display("two"))
- .transition().to("automatic 3")
- .activity("automatic 3").behaviour(new Display("three"))
- .transition().to("automatic 4")
- .activity("automatic 4").behaviour(new Display("four"))
- .transition().to("wait 3")
- .activity("wait 3").behaviour(new WaitState())
- .done();
让我们和你一起顺着流程的执行一起走。
- ClientExecution execution = processDefinition.startProcessInstance();
启动一个新执行意味着初始活动被执行。 所以如果一个自动活动是初始活动,这意味着***个未命名的向外转移会被立刻选择。 这些都发生在startProcessInstance调用的内部。
然而在这种情况下,初始活动是一个等待状态。 所以startProcessInstance方法会立刻返回, 执行会定位到初始活动'wait 1'。
一个新执行会被定为到'wait 1'。
图 7.2. 一个新执行会被定为到wait 1。
然后一个外部触发器会执行signal方法。
- execution.signal();
像上面解释的介绍WaitState, signal会导致选择默认的转移。 转移会把执行移动到automatic 1活动,并执行它。 automatic 1中的Display活动的execute方法, 向控制台打印一行,它不会 调用execution.waitForSignal()。 因此,执行会通过选择automatic 1外部的默认转移进行执行。 在这种状态,signal方法一直阻塞着。另一个需要考虑的方式是执行方法, 像signal会使用客户端的线程 来拦截流程定义,直到到达一个等待状态。
然后执行到达wait 2, 执行WaitState活动。那个方法会调用 execution.waitForSignal(),这会导致signal方法返回。 线程会返回到调用signal方法 的客户端。
所以,当signal方法返回时,执行定义到wait 2。
一个signal会把执行从'initial'带到'wait 2'。
图 7.3. 一个signal会把执行从initial带到wait 2。
然后执行会等待一个外部触发器, 像是一个对象(更准确的是一个对象图)在内存中, 直到下一个外部触发器执行signal方法。
- execution.signal();
第二个调用的signal会直接让执行进入wait 3, 在它返回之前。
第二个signal让执行进入'wait 3'。
图 7.4. 第二个signal让执行进入wait 3。
使用这个范例的好处是相同的流程定义可以在 客户执行模式中执行 (在内存内不使用持久化),就像在持久化执行模式, 依赖应用和环境。
当在持久化模式下执行一个流程,你如何绑定 流程执行到数据库的事务上。
持久化模式下的事务超时
图 7.5. 持久化模式下的事务超时
在大多情况下,计算工作是流程需要完成的一部分, 在外部触发器(红色部分)之后的部分,其实很少。 一般来说,处理流程执行和处理UI传递过来的请求 的事务不会超过一秒。 而业务流程中的等待状态可能超过几小时,几天甚至几年。 当等待状态启动后,线索就变得很清晰, 在等待状态启动之前,只有计算工作的完成包含在事务中。
考虑一下这种方式: "当到达审批时,所有的自动流程需要做的是什么, 在流程系统需要等待另一个外部触发器之前?"。 除非pdf需要被创建,或大邮件需要被发送, 大部分时候,它消耗的时间都是可以忽略的。 这就是为什么在默认的持久化执行模式下, 流程工作在客户端线程下执行。
这个原因也保证着流程同步路径的情况。 当一个执行的单独路径切分成流程同步路径, 流程花在计算上的时间是可忽略的。 所以为什么分支或切分活动实现是有意义的, 目标持久化模式产生的同步路径在同一个线程中按顺序执行。 基本上它们都只是在同一个事务中的计算工作。 因为分支或切分知道每个执行的同步路径会返回,所以这只能被完成, 当出现一个等待状态的时候。
因为这里有一个困难的概念需要掌握,我会再次使用其他词语来解释它。 从头再看一次在持久化执行模式下被流程执行创建出来的它。 如果在一个事务中,一个执行被给与一个外部触发器, 那导致执行切分成多个执行的同步路径。 然后执行在计算上的部分也可以忽略。 生成SQL的部分也可以忽略。 因为所有在同步分支上完成的功能,必须在同一个事务中完成, 这里一般没有指针在分支或切分实现, 在多个线程中产生执行的同步路径。
为了创建可执行流程,开发者需要确切知道什么是自动活动, 什么是等待状态,哪些线程会被分配给流程执行。 对于画业务流程的业务分析人员,事件就很简单了。 对于他们画的活动,他们通常只要知道这是一个人或是一个系统响应。 但是他们通常不知道如何转换线程和事务。
所以对于开发者,***个任务是分析什么是流程控制的线程中需要执行的, 什么是外部的。 查找外部触发器是寻找一个流程中的等待状态的很好的开始, 就像动词和名词可以在构建UML类图中的元素的规则。
【编辑推荐】