6.4 节点的类型和扩展
我们可以通过定义自己的Node节点对象,来补充jbpm自定的节点对象。只需要extends Node,并重写读写xml的read和write方法,重写负责执行的execute方法,在org/jbpm/graph/node/node.types.xml中配置即可,当然,你可以写的更加复杂,更加业务化的节点。
7 jBpm的过程调度机制
7.1 吸纳自Petri Net思想
jBpm的过程调度机制是吸纳了Petri Net的一些思想。
jBpm采用Token来表示当前实例运行的位置,也利用token在流程各个点之间的转移来表示流程的推进,如下图所示:
当jbpm试图去启动一个流程的时候,首先是构造一个流程实例,并为此流程实例创建一个Root Token,并把这个Root Token放置在Start Node上。
以下截取部分代码实现,仅供参考。手头有jbpm3相应开发环境的朋友,可以打开ProcessInstance和Token这两个类。(注:以下所有参考代码,为了突出主题,都已经将实际代码中的event,log等处理删除)
|
public ProcessInstance( ProcessDefinition processDefinition ) { public Token(ProcessInstance processInstance) { |
jbpm是允许在start-state执行Task的,也允许在start-state创建工人任务。不过此处我们不予讨论。
7.2 Token的推进
当Token已经在Start-State节点了,我们可以开始往前推进,来促使流程实例往前运行。对于外部操作来说,触发流程实例往下运行的操作有两个:
(1) 强制执行ProcessInstance的signal操作
(2) 执行TaskInstance的end操作。
但是,这两个操作,都是通过“当前token的signal操作”来内部实现的,如下图所示:
让我们来看看Token类中signal方法的部分代码实现,仅供参考:
|
public void signal() { void signal(Transition transition, ExecutionContext executionContext) { |
接下来,请注意node.leave()这个操作。这是一个很有意思的语义转换:我们是采用token的signal操作来表示往下一个节点推进,但是实际确实执行的node.leave ()操作。
如果这地方让你自己来实现,代码会不会就是这样子呢?不妨此处想一想。
| //假设代码,仅供思考 void signal(Transition transition, ExecutionContext executionContext) { transition.take(executionContext); } |
前面说过,jbpm的调度机制吸纳的Petri Net的思想。在Petri Net中,并没有transition中驻留token这个语义,token只驻留在库所(Place)中。所以,jbpm此处的设计思路,是于此有一定关系的。所以只是把一个ExecutionContext对象放在了transition中,而不是一个token对象。
让我们来看看node对象的leave方法:
| public void leave(ExecutionContext executionContext, Transition transition) { Token token = executionContext.getToken(); token.setNode(this); executionContext.setTransition(transition); executionContext.setTransitionSource(this); transition.take(executionContext); } |
我们直接跟踪进Transition的take操作:
| public void take(ExecutionContext executionContext) { executionContext.getToken().setNode(null); // pass the token to the destinationNode node to.enter(executionContext); } |
经过这么多的中间步骤,我们终于把ExecutionContext对象从一个node转移到下一个node了。让我们来看看Node对象的enter操作:
| public void enter(ExecutionContext executionContext) { Token token = executionContext.getToken(); token.setNode(this); // remove the transition references from the runtime context executionContext.setTransition(null); executionContext.setTransitionSource(null); // execute the node if (isAsync) { } else { execute(executionContext); } } |
至此,jBpm成功的从一个节点转移到下一个节点了。—— 这就是jbpm的调度机制。
7.3 非常简单的调度机制
怎么样,是不是非常的简单?
让我们把整个过程,用一张更清晰的“思维图”来展示一下:

8 jBpm的过程执行机制
8.1 执行机制
前面我们的“过程调度机制”是为了让流程可以正确的从“一个节点转移到下一个节点”,而本节所要讲解的jbpm“执行机制”,则是为提供一个运行机制,来保证“节点的正确执行”。
首先我们需要明确如下的概念:
(1) 节点有很多中,每种节点的执行方式肯定是不一样的
(2) 节点有自己的生命周期,不同的生命周期阶段,所处的状态不同。
在WfMC的《工作流参考模型》文档中,为活动实例归纳了几个可参考的生命周期。(仅供参考,实际很多工作流引擎的节点的生命周期要比这复杂)
但是,jbpm并没有突出“节点生命周期”这个理念,仅仅只是在“Event”中体现出出来。在我看来,可能的原因有两个:
(1) jBpm没有NodeInstance这个概念。利用Token和TaskInstance,jBpm足以持久化足够的信息,能够让流程实例迅速定位到当前运行的状态。
(2) jBpm的Event已经很丰富,并且这个Event是围绕“Token的转移”而设置的,并不是围绕Node的生命周期设置的。
(3) 通常我们需要在Active和Completed的生命周期内所要操作的分支与聚合,在jBpm模型中分别由Fork、Join之类的节点替代。所以jBpm过分关注Node生命周期的管理意义不是非常大。
作为个人,我并不行赏jBpm这样抛弃“节点生命周期管理”的实现方式,更行赏OBE(最早的基于XPDL模型的java工作流引擎之一)的生命周期约束和管理。但是,也不得不承认,jBpm规避了“繁琐的状态维护”,反而让处理变得“简易”,也更容易被大家所理解和接受,而这也正是OBE逐渐消失的一个原因:过于复杂和臃肿。
让我们在前面那张jBpm的“调度机制思维图”上,再稍稍补充一点(为了突出显示,与上图有所改动)。
这张图应该可以很好的诠释出
, jBpm是如何执行各种节点的,这也是得益于OO的“多态与继承”特性。
8.2 分支处理
jBpm的执行机制非常简单,但还是需要稍微补充一下有关“分支”方面的处理。
jBpm采用sub token的机制来解决分支方面的处理:当遇到有分支的时候,会为每个分支节点创建一个child token。在聚合节点(Join或Merge),则依赖其同步或异步的聚合方式,来分别处理。
比如我们参看Fork节点的执行代码(为了突出重点,省略部分代码):
| public void execute(ExecutionContext executionContext) { Token token = executionContext.getToken(); Iterator iter = transitionNames.iterator(); while (iter.hasNext()) { String transitionName = (String) iter.next(); forkedTokens.add(createForkedToken(token, transitionName)); } iter = forkedTokens.iterator(); while( iter.hasNext() ) { //省略部分代码 ExecutionContext childExecutionContext = new ExecutionContext(childToken); leave(childExecutionContext, leavingTransitionName); } } protected ForkedToken createForkedToken(Token parent, String transitionName) { Token childToken = new Token(parent, getTokenName(parent, transitionName)); forkedToken = new ForkedToken(childToken, transitionName); return forkedToken; } |
至于Merge节点,我想此处不用在累赘的展示,有兴趣的,可以参看Merge类的execute方法,即可。
9 jBpm内核结构与实例对象
Jbpm引擎内核的结构非常“精简”。除了我们上面所说的那些定义对象(各种Node节点和Transtion),还有几个与“运行实例”相关的对象。如下图所示,jbpm引擎内核对象主要是在org.jbpm.graph.def和org.jbpm.graph.exe包。
(1) 我们需要描述一个流程实例,所以需要一个ProcessInstance对象。
(2) 每个流程实例,都会维护一套属于其自己的“执行环境”,也就是ExecutionContext对象。注意,这里是一套,而不是一个。
10 后记
上半年写了些bpm和SOA的文章,也被csdn的好友拉着忽悠了不少这方面的概念,弄的好像我开始搞这方面的工作似的。其实不然,本质工作与这有“天壤之别”,完全是非常底层的java技术应用。而workflow,也有两三年没有从事这方面的开发了,所以写此篇文章,着实费了点功夫。
想痛痛快快写篇有关“引擎内核”的文章,这个想法由来以及了,却担心自己不足以诠释清楚,反而容易误导他人,遂中途多次放弃。
正如前面所说的那样,引擎内核的实现,并没有一套“固定的模式”或者“固定的实现体系”,会因为很多因素而造成实现不同。如果想把“引擎内核”的实现真正诠释清楚,必须把这些相关因素都诠释明朗——但这依然是一个浩大的工程。
前些日子,受朋友所托,为他们的公司学员讲了几节工作流的课程,期间尝试jBpm来诠释了一下引擎的实现思路,发现效果不错。——受此引发,遂萌发了以jBpm为实例,来简单诠释“流程引擎内核”想法。
耗时一周的业余时间,虽然还很难诠释自己的全部想法,但“点出几个要点”,还是应该有了。
验证码:
注册会员 会员登陆