NOTE:
- 令牌(token)
- 转换(transition)
- 节点(node)
- 信号(signal)
- 动作(action)
一个流程定义代表了一个业务流程的正式详细说明书,并且它以一个定向图(directed graph)为基础。这个流程图由许多节点和转换组成。在这个流程图里的每个节点都有着各自特殊的类型,节点的类型决定了节点在运行时的行为。一个流程定义只有一个开始状态(start state).
一个令牌代表了一条执行的路径。它包含了这条执行路径的当前的执行状态。令牌是一个运行时概念,它维护一个到图象中节点的指针.
一个流程实例是一个流程定义的执行实例。一个流程定义可以对应多个流程实例。当一个流程实例被创建的时候,一个主执行路径的令牌同时被创建,这个令牌叫做流程实例的根令牌,它指向流程定义的开始状态(processDefinition.getStartState()==token.getNode ()).
一个信号指示令牌继续流程的执行。当接收到一个未命名的信号时,令牌将沿缺省的转换离开当前节点。如果在信号中指定一个转换名,令牌将沿指定的转换离开当前的节点。发给流程实例的信号其实都是委派给了根令牌。
在令牌进入一个节点后,节点被执行.节点本身负责图象执行的延续。图执行的连续是通过让令牌离开节点来完成的.每个节点类型为了连续图执行实现了不同的行为. 一个节点如果不能传播执行将被认为是一个状态.
动作是在流程执行中在事件上执行的片段java代码. 在社区中有关软件需求方面图是个重要指示.但是图只是一个要生产的软件的视图(投射). 隐藏了需要技术细节. 动作是一种在图形表示之外增加更多技术细节的机制. 一旦图放在某个地方,它可有动作来修饰. 主要事件类型是:进入节点,离开节点和执行转换。
jbpm提供了灵活的动作,当流程执行,令牌进入节点和转换时,会触发相应的一些事件。在这些事件上附上我们自己写的操作,就会带动操作的执行。操作里是我们自己的相关java操作代码,非常方便。注意的是事件是内置的,无法扩展。另外,操作也可以直接挂在节点上,而不依赖于事件的触发,这个很重要!
流程定义的基础是一个由节点和转换组成的图象。这些信息表示在一个叫作processdefinition.xml的xml文件中. 每个节点都有一个类型,象state, decision, fork, join,... 每一个节点有一组离开转换(leaving transitions).可以给离开节点的转换一个名字来区别.例如:下图展示一个jBAY拍卖流程的流程图.
以下是jBAY拍卖流程的流程图的XML表示:
<process-definition> <start-state> <transition to="auction" /> </start-state> <state name="auction"> <transition name="auction ends" to="salefork" /> <transition name="cancel" to="end" /> </state> <fork name="salefork"> <transition name="shipping" to="send item" /> <transition name="billing" to="receive money" /> </fork> <state name="send item"> <transition to="receive item" /> </state> <state name="receive item"> <transition to="salejoin" /> </state> <state name="receive money"> <transition to="send money" /> </state> <state name="send money"> <transition to="salejoin" /> </state> <join name="salejoin"> <transition to="end" /> </join> <end-state name="end" /> </process-definition>
一个流程图由许多节点和转换组成。更多关于图的信息和执行模型,参看第4章, 面向图形编程.
每一个节点有一个指定的类型.节点类型决定在运行时当一个执行到达节点时将发生什么。jBPM有一组预实现的节点类型供你使用. 做为选择,你可写代码来实现你自已特定的节点行为。
每个节点有两个主要的职责: 首先, 它可运行简明的java代码。典型的传统java代码同节点的功能相关。比如.建立一个新的任务实例, 发送一个通知, 更新数据库,... 其次, 一个节点负责传播(propagating)流程执行。基本上来说,每个节点有下列的传播流程执行的选项:
- 1. 不传播执行. 在此情况下节点的行为是作为一个等待状态.
- 2. 通过执行节点的一个离开转换来传播执行.其意思是最初到达节点的令牌是通过的一个被称作executionContext.leaveNode(String)的API来传送一个离开转换的. 现在节点作为可执行一些定制程序逻辑及不用等待连续流程执行的自动节点.
- 3. 创建新的执行路径. 节点可以决定建立新的令牌.每个新令牌表示一个执行的新路线并且每个新令牌可以通过节点的离开转换被调用.一个很好的例子就是fork(分支)节点模式的行为.
- 4. 执行的结束路径. 节点可以决定结束执行路线.那意昧着令牌结束了和执行路径完成了
- 5. 更加一般的,节点可以修改流程实例的全部的运行时间结构 运行时间结构是包含令牌树的流程实例.每个令牌表示一个执行路线. 节点可以建立和结束令牌,把每个令牌放在图的节点里并且通过转换来调用.
jBPM包含 --作为一个工作流和BPM引擎-- 一套预实现的可有指定的文档的配置和行为的节点类型.但是和jBPM和面向图形编程基础有关的独特的事情是我们对开发者开放的模型. 开发者可以很轻松的写他们自己的节点行为并在流程中使用.
这就使传统的工作流和BPM系统更加接近了. 它们一般提供固定的节点类型(叫做流程语言).它们的流程语言是接近的并且执行模型在运行时环境中是隐藏的. 工作流模式 的研究表明了任何流程处理语言都是不足够强大有力的. 我们决定简单模型并且允许开发者编写他们自己的节点类型. 这使的 JPDL process 流程语言是可扩展设计的.
下一步,我们将谈讨JPDL的最重要的节点类型.
一个任务节点描绘一个或多个可由人执行的任务。所以当流程执行到任务节点时,将会在工作流参与者的任务列表中创建任务实例。在此之后,任务节点就处于等待状态。所以当用户执行他们的任务,任务的完成将触发执行的再续,换句话讲,那将会在token上引导一个新的signal被调用。
state 是一个纯粹的wait state(等待状态)。它和task-node的区别就是它不会在任何任务列表中创建任务实例。它对于流程需等待外部系统时非常有用,例如,当进入这个节点时(通过一个node-enter event事件上的action),发送一条消息到外部的系统,然后流程就处于等待状态。当外部系统完成一些操作后发送一条响应消息,这可引导一个 token.signal(),此触发器恢复流程执行。
实际上有两种方法来模拟一个decision。两者之间的不同之处是基于谁作出决策(*who* is making the decision). 决策将由流程作出(read: specified in the process definition). 或将由一个外部实体提供决策的结果。
当决策由流程来作出时,一个decision节点将被使用. 有两个基本方法来指定决策标准(decision criteria). 最简单的是在转换之上增加条件元素,条件是beanshell脚本写的将返回一个boolean 的表达式. 当运行的时候,decision节点将会在它的离开转换中轮循(按照在xml中定义的顺序),并对每个条件求值。最先返回'true'的条件的那个离开转换将会被执行(taken). 作为选择,可指定DecisionHandler的一个实现,它有一个decide()方法,该方法返回一个 String(离开转换的名字)。那么,决定在java类中计算中并且由DecisionHandler实现的决定方法返回被选择的离开转换。
当决策由外部团体来选定时(meaning: 不是流程定义的一部分),你应该使用多个转换离开一个状态或等待节点。那么这个可在外部的触发器中提供的离开转换将在等待状态完成后恢复执行。例如, Token.signal(String transitionName) 和TaskInstance.end(String transitionName).
*当需要在流程中根据不同条件来判断执行不同路径时,就可以用decision节点。
一个分叉节点把一条执行路径分离成多条同时进行(并发)的执行路径.缺省的分叉行为是为每条离开分叉节点的转换产生一个子令牌,在要到达分支的令牌之间建立一个父-子关系。
specifies configurable fork behaviour.
if this fork behaviour is not sufficient for your needs, consider writing your own custom TokenHandler.
this forkhandler can be configured in 3 ways :
- without configuration : in that case the fork will launch one new sub-token over each of the leaving tranisions of the fork node.
- a script : can be used to calculate a collection of transition names at runtime. if a script is configured, the script must have exactly one variable with 'write' access. that variable should be assigned a java.util.Collection in the script expression.
默认情况下,join节点会认为所有到达该节点的token都有着相同的父token。当使用上面讲到的fork并且所有由一个fork创建的tokens 到达相同的join时会产生此情况.join 节点会结束每一个到达该节点的token. 然后join将检查进入join的token的父子关系。当所有的同属(sibling)token都到达该节点后,父令牌将传播(唯一的)离开转换。当仍然有子token处于活动状态时,join 节点是wait state(等待状态)。
节点类型服务在你想写你自己的代码在节点中。这个节点类型节点期待一个子元素行为。这个行为被执行当所有执行到达这个节点。在actionHandler中你写的代码可以做你想做的任何事情,但它也负责传播执行.
如果你将使用JavaAPI来实现一些对逻辑分析是重要的功能逻辑,可以使用这个节点. 通过使用一个node,node在流程的图形表现中是可视的。为对比,actions-convered next--将允许在流程图形表现中增加不可见的代码,万一对你逻辑分析是不重要的,在此情况下逻辑对于业务分析是不重要的。
node节点就是让你挂自己的action用的(注意:不是event触发!!),当流程到达该节点时,action会被执行。你的action要实现ActionHandler接口。同样,在你的action里要控制流程!
转换有一个源节点和一个目标节点. 源节点用属性from来表示并且目标节点用属性to来表示.
节点可有一个名字. 注意大多数jBPM功能信赖于转换的名字的唯一性. 如果有多于一个的转换有相同的名字,将取第一个拥有指定名字的转换. 在多个转换名出现在一个节点上的情况下, 方法 Map getLeavingTransitionsMap()将返回比List getLeavingTransitions()少的元素.
默认的转换是位于列表中的第一个转换.
Actions 是运行在流程执行中事件上的java代码片段. 图在软件需求社区是重要的指示.但是图只是将要生产的软件的一个视图(投射).它隐藏了许多技术细节. 动作是在图形化表示之外加入技术细节的机制.当一个图被放置,它可以用动作来装饰. 这就是说java 代码可以在不修改图结构的情况下和图关联起来. 主要的事件类型是进入节点,离开节点和获取转换(taking a transition).
注意位于一个事件中的动作与位于一个节点中的动作的不同. 位于一个事件中的动作在事件触发(event fires)时执行. 事件上的动作没有办法影响流程的控制流程。这类似于观察者模式(observer)。另一方面,一个节点上的动作负责传播执行(propagating the execution)
让我们来看一下一个在事件上的动作的例子.假设我们想在一个给定的转换上做一个数据库更新. 数据库更新在技术上重要但对于数据分析并不重要.
public class RemoveEmployeeUpdate implements ActionHandler { public void execute(ExecutionContext ctx) throws Exception { // 从流程变量中取得被解雇的员工. String firedEmployee = (String) ctx.getContextInstance().getVariable("fired employee"); // by taking the same database connection as used for the jbpm updates, we // reuse the jbpm transaction for our database update. Connection connection = ctx.getProcessInstance().getJbpmSession().getSession().getConnection(); Statement statement = connection.createStatement(); statement.execute("DELETE FROM EMPLOYEE WHERE ..."); statement.execute(); statement.close(); } }
<process-definition name="yearly evaluation"> ... <state name="fire employee"> <transition to="collect badge"> <action class="com.nomercy.hr.RemoveEmployeeUpdate" /> </transition> </state> <state name="collect badge"> ... </process-definition>
有关添加配置到你自定义的作动中和如何在processdefinition.xml中指定配置的更多信息, 请见 Section 16.2.3, “配置 of delegations”
Actions可指定一个名字. 命名的actions可被在需指定action的地方引用. 命名的actions也可作为子元素放在流程定义中.
如果你想限制action配置的重复,会对此功能感兴趣. (例如. 当动作有复杂的配置时). 另外一个用处是执行或调度安排一个运行时间的动作。
事件指明在流程的执行中的时刻.jBPM 引擎在图形执行过程中会触发事件. 这发生在当jbpm 计算下一个状态(请看: 生成信号). 事件总是同一个流程定义中的元素相关,比如流程定义,节点或转换.绝大多数流程元素能触发不同类型的事件. 举例的节点可产生一个node-enter(节点进入)事件和一个node-leave(节点离开)事件 .事件是同动作挂钩的. 每个事件有一个动作清单.当jBPM引擎触发一个事件,动作清单就会被执行.
超状态根据流程定义生成一个父母-子女关系. 包含在一个超状态中的节点和转换以超状态作为父母。 最顶级的元素以流程定义作为父母. 流程定义没有父母.当事件被激发, 事件将被向上传播到父母层.这允许在一个流程中一个中心可以捕捉所有的转换事件和同这些事件关联的动作.
脚本是一个执行BeanShell脚本的动作。缺省地,所有的流程变量可作为脚本变量和非脚本变量被写到流程变量中使用。下列脚本变量也是合法的:更多的有关beanshell的信息, 参见 the beanshell website。
- executionContext
- token
- node
- task
- taskInstance
<process-definition> <event type="node-enter"> <script> System.out.println("this script is entering node "+node); </script> </event> ... </process-definition>
为定制加载和储存变量进script的缺省行为,变量元素可以被用来作为script的子元素。那样,脚本表达式也不得不被放入一个脚本的子元素。
<process-definition> <event type="process-end"> <script> <expression> a = b + c; </expression> <variable name='XXX' access='write' mapped-name='a' /> <variable name='YYY' access='read' mapped-name='b' /> <variable name='ZZZ' access='read' mapped-name='c' /> </script> </event> ... </process-definition>
在脚本开始之前,这个流程变量YYY和ZZZ将分别作为脚本变量b和c使其合法。所有脚本完成之后,脚本变量值a被存储进流程变量XXX.
如果变量的access属性包含"read",这个流程变量在脚本赋值之前将被加载作为一个脚本变量。如果access变量属性包含 "write",这个流程变量在脚本赋值以后将被存储作为一个脚本变量。属性mapped-name可以使流程变量在脚本中以另外一个名字使用,当你的流程变量包含空格或其他非法脚本字符时这很有用。
注意在流程执行的过程中将触发你自己的定制的事件.事件是由图元素(nodes, 转换transitions,流程定义 process definitions和超状态superstates 是图元素)和一个事件类型的组合来唯一定义.jBPM定义了一套被触发给nodes, transitions和其它图形元素的事件。但作为用户,你可以自由的触发你的事件。 在动作里, 你自己的定制代码实现里,或者流程实例的执行的外部,你可以调用 GraphElement.fireEvent(String eventType, ExecutionContext executionContext);事件类型的名字可自由选择 .
超状态是节点的一个组合.超状态可以递归嵌套. 超状态在流程定义中可用做一些层次. 比如,一个应用程序在流程中同步一致聚合所有节点.动作可同超状态事件关联. 一个结果就是令牌可以在给在给定时间的多个嵌套的节点里.这便于检查流程是否执行,比如说在start-up阶段.在jBPM模型中,你可以将任何节点集组合在超状态中.
所有离开一个超状态的转换可以由包含在超状态的节点的令牌激发. 转换也能到达超状态. 在这种情况, 令牌将重定向到在超状态的第一个节点. 从超状态外面的节点可有直接到超状态内的节点的转换. 当然, 另一个方向循环,从超状态内的节点可有转换到超状态外的节点或者到超状态自身.超状态可以有自引用.
注意我们为状态和超状态创建了独立的事件类型. 这让区分的超状态事件和在超状态内传送的节点事件非常容易.
在节点的范围内,节点名必须是唯一的. 节点的范围是它自己的节点集合node-collection. 节点定义和超状态都是节点集合. 为了引用超状态的一个节点, 你必须指明相对的,用斜线(/)分离的名字。斜线(/) 分离节点名字。用'..'引用上级. 下一个例子展示如何引用一个在超状态中的节点:
<process-definition> ... <state name="preparation"> <transition to="phase one/invite murphy"/> </state> <super-state name="phase one"> <state name="invite murphy"/> </super-state> ... </process-definition>
下例将显示如何上升超状态层次(how to go up the superstate hierarchy).
<process-definition> ... <super-state name="phase one"> <state name="preparation"> <transition to="../phase two/invite murphy"/> </state> </super-state> <super-state name="phase two"> <state name="invite murphy"/> </super-state> ... </process-definition>
jBPM异常处理机制只是适用于java 异常. 图形执行它自己不能导致任何问题. 只有它的委托类的执行才能导致异常.
在process-definitions, node和 transitions上, 可指明一个 exception-handlers列表. 每个exception-handler有一动作列.当在委托类中发生异常时候, 流程元素的父母层搜索匹配到最接近的exception-handler.在发现时,这个 exception-handler 的动作被执行.
注意:jBPM 的异常机制不是完全同java 异常处理相似.在Java中,异常可以影响控制流。而在jBPM, 控制流不能被jBPM 异常处理机制改变的.异常是要么捕捉要么不捕捉. 不捕捉异常被扔给client端(比如 client 端调用 token.signal() )或者异常被 jBPM exception-handler捕捉.对于捕捉异常,图形执行继续就好象没有异常发生.
注意:在处理异常的动作中, 通过调用Token.setNode(Node node)把令牌放入图中任意节点是可能的.
在jBPM中流程组成的支持依赖于process-state .流程状态是同另外流程定义有联系的状态 .当图执行到达流程状态,sub-process的流程实例被建立并且他是同到达的流程状态的执行路线相关联. 超级流程的执行路线将会等待直到子流程实例结束. 当子流程实例结束是,超级流程的执行路线将离开流程状态继续在超级流程里图执行.
<process-definition name="hire"> <start-state> <transition to="initial interview" /> </start-state> <process-state name="initial interview"> <sub-process name="interview" /> <variable name="a" access="read,write" mapped-name="aa" /> <variable name="b" access="read" mapped-name="bb" /> <transition to="..." /> </process-state> ... </process-definition>
这个'hire' 流程包含一个跨越了子流程'interview'的 process-state。当执行到达'first interview',一个来自最新版本的'interview' 流程的新的执行(= 流程实例)被建立. 然后变量'a' 从hire流程被复制到变量interview 流程里的'aa'. 同样的方式, hire 变量 'b'也被复制到interview变量'bb'. 当interview流程完成, 只有interview流程里的变量 'aa' 被复制回了hire流程的变量a.
一般来说, 当子流程开始时, 所有有 read 访问属性的变量从超级流程被读出来并在向start发送离开信号之前添入新建立的子流程。 当子流程实例完成时,所有具有 write 属性的变量将从子流程复制会超级流程. 变量元素的 mapped-name属性允许你指明将在子流程中使用的变量名字.
在 jBPM中, 写自已的定制节点十分简单. 为了创建定制节点,必须写 ActionHandler的一个实现.实现可执行任何业务逻辑,但也负责传送(propagate)图象运行。让我们来看一下一个将更新ERP系统的样例. 我们将从ERP系统中读取一个数量, 添加一个存储在流程变量中的数量并存储结果到ERP系统中。基于数量的大小, 我们来通过'small amounts'或 'large amounts' 转换来离开节点。
public class AmountUpdate implements ActionHandler { public void execute(ExecutionContext ctx) throws Exception { // 业务逻辑 Float erpAmount = ...get amount from erp-system...; Float processAmount = (Float) ctx.getContextInstance().getVariable("amount"); float result = erpAmount.floatValue() + processAmount.floatValue(); ...update erp-system with the result...; // 图形执行传播 if (result > 5000) { ctx.leaveNode(ctx, "big amounts"); } else { ctx.leaveNode(ctx, "small amounts"); } } }
也可在自定义的节点实现上创建和结合令牌. For an example on how to do this, check out the Fork and Join node implementation in the jbpm source code :-).
jBPM的图像执行模型是基于流程定义的解释(interpretation)和命令链模式(the chain of command pattern)之上的。
流程定义的解释意味着流程定义数据被存放在数据库中.在运行时流程定义信息在流程执行期间被使用. 所关注的是:我们用 hibernate's 二级缓存避免在运行时见才载入有关定义信息 由于流程定义不会改变 (参看流程版本) hibernate 在内存中缓存流程定义.
命令链模式意味着在图形中的每个节点负责传送流程执行. 如果节点不传送执行,它处理于等待状态。
其想法是在流程实例上启动执行并且一直运行到它进入一个等待状态。
一个令牌代表一个执行路径.令牌有一个指向流程图中节点的指针. 在等待状态过程中, 令牌可持久在数据库中. 现在我们看一下计算令牌执行的算法. 当一个信号被发送到一个令牌时执行开始。 执行通过命令链设计模式被传送到转换和节点. 这是类图中相关的方法.
当令牌在节点里的时候,信号能被送到令牌. 发送一个信号就是一个开始执行的指令.一个信号因此必须指明一个令牌当前节点的离开转换。第一个转换是默认的.当一个信号到令牌,令牌获得它的当前节点然后调用 Node.leave(ExecutionContext,Transition) 方法。考虑 ExecutionContext作为一个令牌 因为ExecutionContext的主要对象是一个令牌. Node.leave(ExecutionContext,Transition)方法产生一个 node-leave 事件并且调用 Transition.take(ExecutionContext) . 这个方法激发转换事件并且在转换目标节点上调用 Node.enter(ExecutionContext) . 这个方法将激发 node-enter事件并且调用 Node.execute(ExecutionContext) .每个类型的节点在excute方法里有它自己的行为.每个节点的传播图执行的责任是通过再次调用 Node.leave(ExecutionContext,Transition) 来实现.总结:
- Token.signal(Transition)
- --> Node.leave(ExecutionContext,Transition)
- --> Transition.take(ExecutionContext)
- --> Node.enter(ExecutionContext)
- --> Node.execute(ExecutionContext)
注意完成下一个状态的计算,包括调用动作(action)是在在client线程完成的.一个常见的误解是所有的计算 *必须*在client线程里完成。作为任何异步调用,你可以用异步消息(JMS)来完成这个.当消息在同一个事务里被送出的时候同时流程实例更新,必须小心所有的同步问题.一些工作流系统在图中的所有节点之间用异步消息 .但是在高流量环境里,这个算法给业务流程性能调整更多控制和灵活性 .
象“9.10图执行”TODO和“第4章 面向图的编程”TODO中解释的那样,Jbpm在客户端线程中运行流程,并且自然使用同步。这个意思是说,token.signal()或者taskInstance.end()只有当流程进入一个新的等待状态时才会返回。
我们在此所描述的功能来自的建模视图是第13章, Asynchronous continuations.
因为流程执行可以很容易同服务器一方的事务绑定,所以在很多情况下这是一个非常直观的方法:流程从转换上的一个状态移至下一。
Of course, in a java enterprise environment, jBPM can be configured to use a JMS message broker instead of the built in messaging system.
在流程中的某个计算会花费很长时间的情形下,这个行为是不受欢迎的。为了应对这种情况,Jbpm包含一个允许以一种异步方式来继续流程的异步消息系统。当然,在java企业应用环境,Jbpm可以被配置为使用JMS消息代理,用来代替内置的消息系统。
在任何节点,JPDL支持属性async=“true”,异步节点不会在客户端线程被执行,而是通过异步消息系统发送一个消息,并且线程被返回给客户端(意味着是token.signal()或taskInstance.end()返回)。
注意,现在Jbpm客户端代码可以提交事务。消息的发送应该与流程的更新在同一事务中完成。因此事务的最后结果是令牌被移动到下一节点(尚未被执行),并且一个org.jbpm.command.ExecuteNodeCommand消息在异步消息系统上被发送到Jbpm的命令执行器(Command Executor)。
Jbpm的命令执行器从队列中读取并执行命令,在org.jbpm.command.ExecuteNodeCommand下,流程通过执行节点被继续,每个命令在一个独立的事务中被执行。
因此,为了异步流程可以继续,必须需要运行一个Jbpm命令执行器。一个简单的方法是在你的Web应用中配置CommandExecutionServlet。作为选择,你应确保命令执行器线程可以运行于任何其他方式。
作为一个流程建模者,你不应该被这些异步消息所干预,主要关注点是事务划分:默认情况下Jbpm在客户端事务中运转,进行全部计算,直到进入一个等待状态。使用async=“true”在流程中划分事务。
让我们看一个例子:
... <start-state> <transition to="one" /> </start-state> <node async="true" name="one"> <action class="com...MyAutomaticAction" /> <transition to="two" /> </node> <node async="true" name="two"> <action class="com...MyAutomaticAction" /> <transition to="three" /> </node> <node async="true" name="three"> <action class="com...MyAutomaticAction" /> <transition to="end" /> </node> <end-state name="end" /> ...
客户端代码同流程执行交互(开始和恢复)是同正常的(同步)流程是一样的:
...start a transaction... JbpmContext jbpmContext = jbpmConfiguration.createContext(); try { ProcessInstance processInstance = jbpmContext.newProcessInstance("my async process"); processInstance.signal(); jbpmContext.save(processInstance); } finally { jbpmContext.close(); }
在第一个转换之后,流程实例的根标志令牌将指向节点one并且ExecuteNodeCommandmessage将发送给命令的执行者。
在并发的事务中,命令执行器从队列中读取消息,并且执行一个节点,动作可以决定传播执行或进入一个等待状态。如果动作决定传播执行,则当执行到达节点two时事务会被结束,以此类推…?