在令牌进入节点后,这个节点被执行。节点自己负责图执行连续(continuation of the graph execution)。图执行连续完成后使令牌离开这个节点。每个节点类型为图执行连续实现一个不同的行为。不传播执行的节点将成为状态节点。
图 10-1 拍卖流程图
<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章 面向图的程序设计。
· 1、不传播执行。那样的话这个节点相当于等待状态。
· 2、通过节点的离开转换中的一个来传播执行。这就意味着最初到达节点的令牌(token)使用API调用executionContext.leaveNode(String)方法通过其中的一个离开转换。这个节点将扮演一个自动节点,从某种意义上说,它可以执行一些定制的程序逻辑并且而后不用等待自动地继续流程执行。
· 3、创建新的执行路径。一个节点决定创建新的令牌。每个新令牌代表一个新的执行路径并且每个新令牌可以通过节点的离开转换被启动。这种行为相当的例子就是一个分支(fork)节点。
· 4、结束执行路径。节点能够决定执行路径的终点。那意味着令牌被结束并且执行路径被完成。
· 5、通常,节点可以修改整个流程实例的运行时结构。运行时结构是包含令牌树的一个流程实例。节点能够被创建并结束令牌、在图的一个节点上设置令牌以及通过转换启动令牌。
jBPM包含(像任何工作流和BPM引擎一样)一系列预实现的有明确的文档化配置和行为的节点类型。但关于jBPM和面向图的程序设计基础的唯一的事情是我们为开发人员展现了这样的模型。开发人员可以非常容易地写他们自己的节点行为以及在流程中使用它。
传统工作流和BPM系统更加封闭。他们通常提供一套固定的节点集合(叫流程语言)。它们的流程语言是封闭的并且执行模型是隐藏在运行时环境中。工作流模型的研究已经显示出任何的流程语言都不是足够强大的。我们选定了一个简单的模型并允许开发写自己的节点类型。那就是JPDL流程语言最后开放的途径。
At runtime the decision node will FIRST loop over its leaving transitions THAT HAVE a condition specified. It will evaluate those transitions first in the order as specified in the xml. The first transition for which the conditions resolves to 'true' will be taken. If all transitions with a condition resolve to false, the default transition (the first in the XML) is taken.
Another approach is to use an expression that returns the name of the transition to take. With the 'expression' attribute, you can specify an expression on the decision that has to resolve to one of the leaving transitions of the decision node.
当决策被外部方(意思是:不是流程定义的部分)处理时,你应该使用多个转换离开一个状态或等待状态节点。然后离开转换能够被等待状态完成后恢复执行的外部的触发器提供。例如:Token.signal(String transitionName)和TaskInstance.end(String transitionName)。
转换能够可选的来命名。注意jBPM的大部分属性依赖转换的命名的唯一性。如果有多个转换有相同的名称,第一个命名的转换将被处理。万一多个重复转换名称占用一个节点,方法Map getLeavingTransitionsMap()返回的元素要比List getLeavingTransitions()少。
动作(actions)是在流程执行里的事件上执行的java代码片段。图是关于软件需求沟通上一个重要的工具。而且图正好也是将要被开发的软件的一个视图(项目)。它隐藏了许多的技术细节。动作是在图形化表示外面增加技术细节的一个机制。一旦图放在那里,它可以被动作修饰。这就意味着在不改变图的结构的情况下可以让java代码同图关联起来。主要事件类型是进入节点(entering a node)、离开节点(leaving a node)和处理转换(taking a transition)。
注意下动作放在事件和放在节点上的不同。动作放在事件上当事件被触发时执行。事件上的动作没有办法去影响流程的控制流。它同观察者模式(observer pattern)很相似。另一方面,放置在节点上的动作有责任来传播执行。
图 10-2 数据库更新动作
public class RemoveEmployeeUpdate implements ActionHandler { public void execute(ExecutionContext ctx) throws Exception { // 从流程变量上取得被操作的员工 String firedEmployee = (String) ctx.getContextInstance().getVariable("fired employee");
// 得到同jbpm更新使用的一样的同一个数据库连接,我们为我们的数据库更新
// 重用jbpm的事务
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>
|
脚本(script)是一个动作执行的beanshell脚本。更多的关于beanshell的信息,请看beanshell网站。缺省情况下,所有流程变量可以当作脚本变量使用而且不会有脚本变量被写入流程变量中。下面的脚本变量是可用的:
<process-definition>
<event type="node-enter"> <script> System.out.println("this script is entering node "+node); </script> </event> ...
</process-definition>
|
<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>
|
注意也可能在流程执行期间触发你自自己的事件。事件通过图的组成元素(节点、转换流程定义和超级状态是图元素)以及事件类型(java.lang.String)唯一地定义。jBPM为节点、转换和其他的图元素定义一系列的触发事件。在动作中、自定义的节点实现中或甚至流程执行的外面,你可以调用GraphElement.fireEvent(String eventType, ExecutionContext executionContext);。事件类型的名称可以被自由的选择。
<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>
|
<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>
|
注意在一个处理异常的动作里,它可能使用Token.setNode(Node node)方法来放置这个令牌在图的任意一个节点上。
<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流程的流程状态。当执行到达“first interview”时,一个 interview流程的新的执行(=process instance)被创建。如果没有明确版本被指定,当部署hire流程时已知子流程的最新版本将被使用。为了让jBPM实例化一个特定版本,可选的版本属性可以被指定。为了延迟绑定指定的或最新的版本直到真正创建子流程时,可选的绑定属性应该被设置为late。然后hire流程的变量“a”被复制进入interview流程的变量“aa”中。同样的方法,hire流程的变量“b”被复制进入interview流程的变量“bb”中。当interview流程完成时,只有interview流程的变量“aa”被复制回hire流程的变量“a”中。
图 10-3 更新ERP例子的流程片段
public class AmountUpdate implements ActionHandler { public void execute(ExecutionContext ctx) throws Exception {
// 业务逻辑
Float erpAmount = ...从ERP系统中获取数量...;
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");
}
}
}
|
它也可能创建并合并令牌在定制的节点实现里。就如何做这个的例子,到jbpm源代码检出Fork和Join节点实现吧 :-)。
图 10-4 图执行的相关方法
同样的解释在10.10 图执行部分和第4章 面向图的程序设计,jBPM在客户端线程中运行流程而且天生就是同步的。那意味着token.signal()或taskInstance.end()当流程已经进入新的等待状态时将只是返回。
我们这里描述的jPDL属性是来自第15章 异步连续的建模视图。
...
<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();
}
|