用户名:
密  码:
验证码:
 
JAVA J2EE J2ME J2SE JSP C/C++ C语言 C++ VC MFC Web前台 Html css JavaScript 软件测试 软件测试入门 LoadRunner Windows Win2008 Win2003 WinXP
.NET ASP.NET VB.NET MVC Linux/Unix Linux Unix Shell Web开发 PHP ASP Ajax IIS Apache 编程语言 C VB Delphi 汇编 数据库 MSSQL Mysql Oracle

JBPM工作流引擎内核设计思想及构架

www.diybl.com 时间:2008-11-25 作者:佚名 编辑:辉辉 点击:  [评论]

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 ) {
this.processDefinition = processDefinition;
this.rootToken = new Token(this);

public Token(ProcessInstance processInstance) {
this.processInstance = processInstance;
this.node = processInstance.getProcessDefinition().getStartState();

jbpm是允许在start-state执行Task的,也允许在start-state创建工人任务。不过此处我们不予讨论。

7.2 Token的推进

 当Token已经在Start-State节点了,我们可以开始往前推进,来促使流程实例往前运行。对于外部操作来说,触发流程实例往下运行的操作有两个:
 (1) 强制执行ProcessInstance的signal操作
 (2) 执行TaskInstance的end操作。
 但是,这两个操作,都是通过“当前token的signal操作”来内部实现的,如下图所示:

 
 Token的Signal操作表示:实例需要离开当前token所在的节点,转移到下一个节点上。因为Node与Node之间是“Transition”这个桥梁,所以,在转移过程中,会首先把Token放入相关连的Transtion对象中,再由Transition对象把Token交给下一个节点。

让我们来看看Token类中signal方法的部分代码实现,仅供参考:

public void signal() {
//注意ExecutionContext对象
signal(node.getDefaultLeavingTransition(), new ExecutionContext(this));
}

void signal(Transition transition, ExecutionContext executionContext) {
// start calculating the next state
node.leave(executionContext, transition);
}

接下来,请注意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为实例,来简单诠释“流程引擎内核”想法。

 耗时一周的业余时间,虽然还很难诠释自己的全部想法,但“点出几个要点”,还是应该有了。

 

1 2
如果图片或页面不能正常显示请点击这里 站内搜索:
推荐文章
文章评论
请您留言
昵称:  
验证码:
注册会员
会员登陆
频道地图