流程定义主要基于定向图表示了一个商业流程的规范.图是有节点和转换组成.图中的每个节点都有一个特定的类型. 节点类型定义了运行时间的行为.流程定义有且只有一个开始状态. 令牌是执行的一个路线. 令牌是运行时间概念用来维护图中指想节点的指针. 流程实例是一个流程定义执行的实例.当一个流程实例被建立后,一个令牌也为主要执行路线建立了. 这个令牌称为这个流程实例的根令牌,她的位置处于流程定义的开始状态. 信号指示令牌继续图执行. 当接受到无名的信号,令牌将, 令牌将用缺省的离开转换离开节点. 当转换名字在信号中已经指定,令牌将使用指定的转换离开节点. 信号发给一个被委托给根令牌的流程实例. 在令牌进入节点后,节点被执行.节点自己有责任连续图执行.图执行的连续是通过让令牌离开节点来完成的.每个节点类型为了连续图执行实现了不同的行为. 一个节点如果不能传播执行将被认为是一个状态.. 动作是在流程执行中在事件上执行的片段java代码. 在社区中有关软件需求方面图是个重要指示.但是图只是一个要生产的软件的视图(投射). 隐藏了需要技术细节. 动作是一种在图形表示之外增加更多技术细节的机制. 一旦图放在某个地方,它可有动作来修饰. 主要事件类型是:(进入节点) entering a node,(离开节点) leaving a node 和(执行转换)taking a transition. 基本的流程定义组成是图和节点. 在文件 processdefinition.xml定义 . 每个节点有一个类型state, decision, fork, join,每个节点有一套离开转换. 可以给离开节点的转换一个名字来区别.比如:下面的图表显示了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> 流程图由节点和转换组成.更多关于图的信息和执行模型,参看 第四章, 面向图的程序设计. 每个接点有一个指定的类型. 节点类型定义当执行到达这个节点的时候将发生什么. jBPM 有一套你可以使用的预实现的节点类型.另外,你可以编写自己的定制代码实现你自己的指定的节点行为. 每个节点有2个主要责任: 首先,它可以执行传统java代码. 典型的传统java代码同节点功能相关. 比如.建立一个新的任务实例, 发送一个通知, 更新数据库,.其次,节点服务传播流程执行.基本上来说,每个节点有下列可选的传播流程执行:
jBPM 包含--作为任何工作流和BPM引擎-- 一组预先实现的节点类型可有指明配置文件和行为. 但是关于jBPM独特的事情是 面向图的程序设计基础 对开发者开放的模型. 开发者可以很轻松的写他们自己的节点行为并在流程中使用. 这就使传统的工作流和BPM系统更加接近了. 它们一般提供固定的节点类型(叫做流程语言).它们的流程语言是接近的并且执行模式在运行时间是隐藏的. 研究 workflow patterns 表明了任何流程处理语言都是不足够强大有力的. 我们必须决定简单模型并且允许开发者编写他们自己的节点类型. 这使的 JPDL process 流程语言是可扩展设计的. 下面,我们讨论最重要的JPDL节点类型. 任务型接点代表一个或多个可以被人执行的任务. 当执行到达一个任务节点,任务实例将在工作流参与者任务清单中被建立. 这之后,节点将作为一个等待节点. 因此当用户执行他们的任务,任务完成将触发执行继续开始. 换句话说,令牌将调用一个新的信号. 状态是一个单纯(bare-bones)等待状态. 任务节点的不同是没有任务实例在任何任务清单中建立. 这在流程等待外部系统的时候很有用. 比如.以上的节点入口(通过一个在node-enter事件的动作),一个消息将被送到外部系统. 在这之后, the process will go into a wait state. When the external system send a response message, this can lead to a token.signal(), which triggers resuming of the process execution. 实际上有两个方法模拟决策. 明显的区别在于基于"谁"来做决策. 或者有流程来做决策 (读: 在流程定义中指明. 或者用外部的实体提供决策结果. 当有流程来做一个决策的,就要使用一个决策节点了. 有两个基本方式来指明决策的标准.最简单的方式是给转换增加一个条件元素. 条件是返回结果是boolean的beanshell脚本表达.在运行时间决策代码会被离开转换反复调用(按照XML指定的顺序),来评估每个条件. 第一个转换在条件是'true'的时候发生. 可选的, 用一个DecisionHandler的实现. 在java class中决策被计算并且被选择的离开转换由DecisionHandler 实现的决策方法(decide-method)返回. 当决策是由外部部分决定(含义:不是流程定义的一部分), 你可以用多个转换离开状态或等待状态节点. 然后离开转换可以提供外部的触发在等待状态完成后继续执行.比如 Token.signal(String transitionName) and TaskInstance.end(String transitionName). 默认联合(join)假设所有来自同一个父母的子令牌联合.当在上使用fork(分支)这个情形就出现了并且所有令牌分支建立,并且到达同一个联合(join).当全部令牌都进入联合的时候,联合就结束了. 然后联合将检查父母-子女. 当所有兄弟令牌到达联合(join),父母令牌将传播(唯一的)离开转换. 当还有兄弟令牌活动时,联合的行为将作为等待状态. 类型节点是你想写在节点中写你自己的代码. 节点类型节点期望一个子元素动作.动作(action)在执行到达这个节点时候被执行. 你在actionhandler所写的代码可以做任何事情除了它必须负责 传播执行. 如果你想使用来实现一些对业务分析员来说非常重要的功能逻辑,可以使用节点..当使用节点时,节点在流程图形表示里是可见的.为了对照, 动作(actions) --覆盖着文本-- 允许你加入在图形化流程里看不到的对业务分析员不重要的代码 . 动作是一段java代码,在流程执行有事件时执行. 图在软件需求社区是重要的指示.但是图只是将要生产的软件的一个视图(投射).它隐藏了技术细节. 动作是在图形化表示之外加入技术细节的机制.当一个图被放置,它可以用动作来装饰. 这就是说java 代码可以在不修改图结构的情况下和图关联起来. 主要的事件类型是进入节点entering a node,离开节点 leaving a node 和执行转换taking a transition. 注意事件中的动作和节点中的动作的不同. 事件中的动作当事件发生的时候才执行.事件中的action没有方法影响流程的控制流.可以参考以下观察者的设计模式. 另一面,节点里的action有 递执行的责任. 我们看看事件中的动作. 假定我们想在给定的转换上做数据库更新.数据库更新技术上是极其重要的,但对业务分析员来说是不重要的. public class RemoveEmployeeUpdate implements ActionHandler { public void execute(ExecutionContext ctx) throws Exception { // get the fired employee from the process variables. 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("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中指明配置的更多信息, 参看 13.2.3章节, “委托的配置” 可以给动作起个名字. 命名的动作可以在其他位置通过名字来引用. 命名的动作也可以在流程定义中作为子元素. 这个特点在你想限制重复的动作配置(比如 当动作的配置非常复杂时)的时候就非常有用了.另外一个use case是执行或调度安排一个运行时间的动作. 事件指明流程执行的时刻. jBPM 引擎在图执行时会产生事件. 这发生在当jbpm 计算下一个状态(请看: 生成信号). 事件总是同一个流程定义中的元素相关,比如流程定义,节点或转换.绝大多数流程元素能产生不同类型的事件. 举例的节点可产生 node-enter(节点进入)事件和 和一个节点离开 node-leave事件 .事件是同动作挂钩的. 每个时间有一个动作清单.当jBPM引擎产生一个事件,动作清单就会被执行. 超状态根据流程定义生成一个父母-子女关系. 节点和转换包含在一个以超状态为父母的超状态里. 最顶级的元素以流程定义作为父母. 流程定义没有父母.当事件被生成, 事件将被向上传送到父母层.这允许在一个流程中一个中心可以捕捉所有的转换事件和同这些事件关联的动作. 脚本是动作执行的beanshell脚本. 更多有关beanshell参看 the beanshell website . 默认的所有流程变量作为脚本变量script-variables和非脚本变量no script-variables 被写到流程变量process variables. 所有可用的脚本变量:
<process-definition> <event type="node-enter"> <script> System.out.println("this script is entering node "+node); </script> </event> ... </process-definition> 定制载入和储存到脚本的默认行为, variable 元素可以被用做为脚本子元素.在这个case, 脚本表达式也被放入脚本子元素: expression. <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可用. 当脚本完成后, 脚本变量的值被储存进流程变量 XXX. 如果变量的 access属性包含 'read ', 流程变量可以在评估之前载入作为脚本变量. 如果 access属性包含 'write ', 在评估之后脚本变量被存储为流程变量. 属性 mapped-name可以使流程变量在脚本中用另外一个名字被访问 . 这在你的流程变量包含空格或其他非法的文字字符. 超状态是一组节点.超状态可以递归嵌套. 超状态在流程定义中可用做一些层次. 比如,一个应用程序在流程中同步一致聚合所有节点.动作可同超状态事件关联. 一个结果就是令牌可以在给在给定时间的多个嵌套的节点里.这便于检查是否流程执行,比如说在start-up阶段. 在jBPM模型, 在超状态你可以自由的聚合任何套的节点. 所有转换 leaving a superstate 可以由包含在super state 的节点的令牌产生. 转换能到达超状态. 在这种情况, 令牌将重定向到在超状态的第一个节点. 从超状态外面的节点直接转换到超状态内的节点. 当然, 另一个方向循环,从超状态内的节点可以转换到超状态外的节点或者到超状态自身.超状态可以有自引用. 有两个只有超状态才有的事件: superstate-enter 和 superstate-leave .这些事件产生在进入转换节点或离开的时候 . 当令牌在超状态内开始转换的时候,这些事件不会产生. 注意我们为状态和超状态生成了独立的事件类型. 这让区分的超状态事件和在超状态内传送的节点事件非常容易. 在节点的范围内,节点名必须是唯一的. 节点的范围是它自己的节点集合node-collection. 节点定义和超状态都是节点集合. 为了引用超状态的一个节点, 你必须指明相对性用slash (/)分离名字. slash(/) 分离节点名字. 用'..'引用上级. 下一个例子展示如何引用一个相邻的超状态: <process-definition> ... <state name="preparation"> <transition to="phase one/invite murphy"/> </state> <super-state name="phase one"> </super-state> ... </process-definition> 下一个例子展示如何到超状态层级 <process-definition> ... <super-state name="phase one"> <state name="preparation"> <transition to="../phase two/invite murphy"/> </node> </super-state> <super-state name="phase two"> </super-state> ... </process-definition> jBPM异常处理机制只是应用了java 异常. 图执行它自己不能导致任何问题. 只有它执行委托class能导致异常. 在process-definitions, node s和 transition s, 可指明一个 exception-handler s清单. 每个 exception-handler有一清单动作 .当异常在委托类中发生时候, 当流程元素的父母层搜索匹配到最接近的 exception-handler . 则这个 exception-handler 的动作被执行. 注意:jBPM 的异常机制不是完全同java 异常处理相似.在Java,异常可以影响控制流. 而在jBPM, 控制流不能被jBPM 异常处理机制改变的.异常是要么捕捉要么不捕捉. 不捕捉异常 are被扔给client端(比如 client 端调用 token.signal() )或者异常被 jBPM exception-handler捕捉 .捕捉异常,图执行继续执行好象没有异常发生. 注意:在动作action里处理异常, 通过调用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' 流程包含 process-state, 跨越了子流程'interview'. 当执行到达'first interview',一个新的执行(= 流程实例)来自最后版本的'interview' 流程被建立. 然后变量'a' 从hire流程被复制到变量interview 流程里的'aa'. 同样的方式, hire 变量 'b'也被复制到interview变量'bb'. 当interview流程完成, 只有interview流程里的变量 'aa' 被复制回了hire流程的变量a. 一般来说, 当子流程开始时, 所有 variables 有 read 访问属性的从超级流程被读出来并添入新建立的子流程,在离开start start产生信号之前. 当子流程实例完成是,所有 variable s有 write 属性的将从子流程复制会超级流程 . 变量的 mapped-name 元素允许你指明子流程中使用的变量名字. 在jBPM, 写你自己定制的节点. 为了建立定制节点,必须写一个ActionHandler的实现 . 实现可以执行任何业务逻辑,当也有责任来传播图的执行.让我们看一个执行更新ERP-system的例子.我们从ERP-system读一个数量,增加数量然后保存进流程变量并且把结果保存会ERP-system. 基于数量的大小, 我们来通过'small amounts'或 'large amounts' 转换来离开节点. public class AmountUpdate implements ActionHandler { public void execute(ExecutionContext ctx) throws Exception { // business logic 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...; // graph execution propagation if (result > 5000) { ctx.leaveNode(ctx, "big amounts"); } else { ctx.leaveNode(ctx, "small amounts"); } } } 另外一种方法也是可能的在定制的节点实现中建立一个join令牌 . 举例说明怎样来做这个, 可以检查Fork 和 Join 节点实现的jbpm源代码 :-). jBPM图执行模型是基于解释流程定义和 命令链设计模式(chain of command pattern). 解释流程定义意思是把流程定义数据保存在数据库中.在运行时间流程定义信息在流程执行期间被使用 所关注的是:我们用 hibernate's 二级缓存避免在运行时见才载入有关定义信息 由于流程定义不会改变 (参看流程版本) hibernate 缓存流程定义在内存中. 命令链设计模式意思是图中每个节点有责任传播节点执行.如果节点不传播执行,它就作为一个等待状态. 这个想法是在流程实例开始执行知道它进入等待状态. 令牌表示一个执行路线. 令牌有一个指针指向流程图中的节点.在等待状态期间,令牌能被永久化到数据库里. 现在来仔细看看计算令牌执行的算法.当信号送到令牌时执行开始. 执行通过命令链设计模式被传送到转换和节点. 这是类图中相关的方法. 当令牌在节点里的时候,信号能被送到令牌. 发送一个信号就是一个开始执行的指令.一个信号因此必须指明一个令牌当前节点的离开转换(leaving transition).第一个转换是默认的.当一个信号到令牌,令牌获得它的当前节点然后调用 Node.leave(ExecutionContext,Transition) 方法. 考虑 ExecutionContext作为一个令牌 因为ExecutionContext的主要对象是一个令牌. The Node.leave(ExecutionContext,Transition)方法产生一个 node-leave 事件并且调用 Transition.take(ExecutionContext) . 这个方法产生转换( transition)事件并且在转换目标节点上调用 Node.enter(ExecutionContext) . 这个方法将产生 node-enter事件并且调用 Node.execute(ExecutionContext) .每个类型的节点在excute方法里有它自己的 行为.每个节点的传播图执行的责任是通过再次调用 Node.leave(ExecutionContext,Transition) 来实现. 总结:
注意完成下一个状态的计算,包括调用动作(action)是在在client线程完成的.一个常见的误解是所有的计算 *必须*在client线程里完成.作为任何异步调用 你可以用异步消息(JMS)来完成这个.当消息在同一个事务里被送出的时候同时流程实例更新,必须小心所有的同步问题.一些工作流系统在图中的所有节点之间用异步消息 .但是在高流量环境里,这个算法给业务流程性能调整更多控制和灵活性 . |
|