jBPM 3 流程模型

本文深入介绍了jBPM 3的流程模型,包括基于图的程序设计概念,如Node、Transition和Execution(Token)。文章详细阐述了Action、流程建模中的Task Instance、变量以及Assignment和Swimlane等概念,展示了如何通过这些元素构建和控制业务流程。此外,还探讨了决策节点、Fork/Join和自定义Node的使用,为理解jBPM的流程执行机制提供了清晰的指导。

以前写的。

jBPM 流程模型简介

面向图的程序设计

jPDL ( jBPM Process Definition Language )是建立在 JBoss jBPM 框架之上的一个流程语言。它的核心功能被封装成了一个简单的 java 库,用来管理流程定义和流程实例执行的运行时环境,可以很方便的嵌入到我们的应用中。

面向图的程序设计( Graph Oriented Programming , GOP )是 jPDL 的基础。 GOP 是一种用 OO 语言来实现基于图的语言( Graph Based Language )的技术。这种基于图的语言有两种基本特性:对等待状态的支持( Support for wait states )和图形化的表现形式( Graphical representation )。

图的结构: Node 和 Transition 的组合

流程图是一个有向图,由两个基本元素: Node (节点)和 Transition (转移,有向弧)组成。

Transition 具有方向,它从一个Node(from)指向另一个Node(to)。每个 Node 有一个 execute 方法, Node 的子类可以 override 这个 execute 方法来实现自己特定的行为。

Execution(Token)

Execution (也就是 Token )代表了流程当前的状态,它持有一个当前 node 的引用。

Transition 可以通过 take 方法将一个 execution 从源 node 传递到目的 node 。当一个 execution 到达一个 node 时, node 执行 execute 方法,同时负责这个 execution 的传播。也就是说, node 能够通过它的一个 leaving transition 将 execution 传递到下一个 node 。

如果一个 node 的 execute 方法没有传递 execution ,那么它就处于一个等待状态( wait state )。这时可以给 execution 发一个事件( Event ),来触发 execution 的移动。这样 execution 就会继续向下传递,直到它到达了下一个等待状态。

Action

Action 提供了一种不引入新的节点而执行程序逻辑的方法。 Action 也有一个 execute 方法,并能同事件关联起来。

当 execution 被 node execute 时,会触发一些事件,引起 action 被执行。基本的事件例如 node-leave 和 node-enter 等。每个事件可以关联多个 action ,所有关联的 action 在事件被触发时执行。

Process Language 示例

上述内容已经支持了前面提到的两个基本特性:等待状态和图形化的表示。

Process Language 实际上就是由一些种类的节点实现构成。每个节点类型具体的行为由对 execute 方法的覆写来实现。

我们来看一个简单的示例 Process Language (与 jPDL 无关)。它有四个类型的 Node :开始状态 Start State ,结束状态 End State ,任务节点 Task Node 以及决策判定节点 Decision 。

现在用这几种节点来画一个示例的流程:报销流程,流程图如下。

给流程创建一个新的 execution 时,我们把 execution 放在开始节点上。这时必须给 execution 一个事件,才能启动 execution 。否则 execution 会一直停留在开始节点。

给 execution 一个 event 之后, execution 找到节点的默认的 leaving transition ,然后调用 transition 的 take 方法,把自己作为参数。这样就通过 transition 把 execution 向下传递到了 Decision 节点,并调用 Decision 节点的 execute 方法。

这里 Decision 节点的 execute 方法实现会计算报销的金额,并判断金额是否超过 1000 元。假设超过 1000 元,这时 Decision 节点会给 execution 发一个“ Yes ”事件,触发 execution 选择“ Yes ” transition 进行传递,到达“领导审批”这个 Task Node 节点。

“领导审批”这个节点的 execute 方法会给领导的待处理任务列表中添加一个任务。此时 execution 会停留在这个节点等待领导进行审批。从开始节点触发 execution 的事件的调用直到此时才会返回。

我们可以给流程图添加 action 。 Action 不会直观的反映在流程图上,而是当相应关联的事件发生时被触发。比如给领导审批节点添加一个 action ,这个 action 关联到 Node-enter 事件上,这样在 execution 进入领导审批节点时,就会触发这个 action 。

 

jBPM 还提供了简单的示例代码来帮助理解面向图的设计原则。

下载链接为 http://docs.jboss.com/jbpm/gop/jbpm.gop.zip 需要在 Eclipse 中导入这个项目。

这里只有四个类

Execution.java

Node.java

Transition.java

Action.java

阅读这几个类的源码和相应的测试用例确实能加深我们的理解。甚至作为娱乐,我们可以从这几个类开始,扩展出自己的节点类型 J

jBPM 流程建模

流程定义和流程实例

首先需要明确流程定义和流程实例的概念和区别。

流程定义( Process Definition )是一个基于有向图的业务流程的定义。流程图由 node (节点)和 transition (转移)构成。节点有自己的类型,定义了运行时的行为。每个流程图有一个唯一的开始状态( start state )。

流程实例( Process Instance )是流程定义的一次执行( execution )。一个流程定义可以有很多个流程实例。用面向对象语言的概念来类比的话,可以把流程定义看作一个类,而流程实例就是根据这个类生成的对象。

Node

流程图由 Node 和 Transition 组成。 Node 的类型决定了在运行时 token 到达该节点时的动作。

Node 有两个基本的职责:首先 Node 在 execute 时可以执行一些业务逻辑,例如创建任务,发送通知消息等等;其次 Node 也负责流程 execution 的传递。对于 execution 的传递,有如下几种可能:

l 不传递 。这种情况 node 将作为一个等待状态 wait state 。

l 通过该节点其中的一个 transition 来传递 execution 。这样这个节点就将自动传递 execution 而无需等待,同时它会执行一些相应的业务逻辑的操作。

l 创建新的 execution 路径 。一个节点可以创建新的 token ,每个新的 token 都代表一个新的 execution 路径,它们可以从这个节点启动,沿着节点的 leaving transition 传递。

l 结束 execution 路径 。节点也能够结束一个 execution 路径。此时 token 在这个节点结束。

l 更一般地,节点可以改变整个流程实例的运行时结构 。我们可以把流程实例在运行时的结构看成一棵 token 的树。每个 token 代表一个 execution 的路径,节点能够创建和结束 token ,把 token 放到图中的一个节点上,并通过 transition 传递 token 。

jBPM 有一些预定义的 Node 类型,如图。

下面对这几种比较重要的 node 类型分别进行介绍:

Task-node

最常用的 Node 类型是 task-node 。 task 表示由人来完成的任务。当 execution 到达 task node 时,会创建 task 的实例,并将 task instance 加入到某些参与者的待处理任务列表里。这时 task node 将变成一个等待状态,直到参与者完成了他们的任务,才会触发 execution 继续向下进行,也就是给 token 发一个 signal 。

State

State 则代表一个纯粹的等待状态。它与 task node 的区别在于 state 节点不会去创建 task instance 。一般 State 主要用于与外部系统交互时等待外部系统的响应消息。当 execution 到达 state 节点时,向外部系统发起一个请求,同时流程进入等待状态。而在响应消息到达时会给 token 发 signal ,触发流程继续向下进行。在我们的平台里没有用到 state 节点类型。

Decision

Decision 也就是判定决策节点,实际上有两种方式来对其进行建模。如果决策是在流程内部进行的,也就是在流程定义中指定的,那么需要用一个 decision 节点。在 execution 到达 decision 节点后,有几种判定方式可以决定 execution 朝哪一个 leaving transition 进行。例如可以分别对每个 transition 进行计算,并选择第一个返回 true 的 transition 。如果都返回 false ,则选择默认的 transition 。也可以直接通过一个表达式来计算得出要选择的 transition 的名称。

而如果决策不是在流程内部进行的,而是由一个流程外部的实体来决定时,我们就可以直接使用一个带有多个 leaving transition 的 wait state 节点。例如使用一个 Task Node ,并给这个节点多条 transition ,根据 task 的执行结果来决定流程沿着哪条 transition 进行。在我们的平台里基本上都采用了这种方式来进行 decision 决策判定。

Fork

Fork 节点会把一条 execution 的路径分成多个并行的 execution 路径。 Fork 和 decision 的区别在于 execution 经过 decision 节点后只是沿其中一个 transition 传递。而 fork 节点则把一个 execution 分成了多个。它是通过给 token 生成多个子 token 来实现的。 Token 在经过 fork 节点时,会自动生成多个子 token 向下进行,而父 token 则停留在 fork 节点。 Fork 节点默认的实现是按照流程图上的 transition 来生成子 token ,一个 transition 对应一个 token 。另外 fork 也提供了动态生成 token 的方式,这时需要写一个 beanshell script ,然后在运行时 jBPM 能够动态计算这个 script ,根据结果来决定生成多少个 token 。

在我们的平台中,并没有直接使用 fork 节点。我们目前是将动态生成子 token 的逻辑放到了一个 action 里,这个 action 可以放到一个 node 节点中,或者只是隐式的与一个事件相关联而不直接的表现在流程图里。

Join

Join 节点与 fork 相对应。在 fork 节点中会生成一个父 token 的多个子 token ,而当子 token 到达 join 节点时会被 join 节点结束。每次 token 到达 Join 节点时, jBPM 都会检查是否这个 token 的所有兄弟 token 都已到达。如果父 token 的所有子 token 都到达了这个 join 节点并被结束掉后,父 token 将沿着 join 节点的 leaving transition 进行传递。而如果还有子 token 没到达 join 节点, join 节点将作为一个等待状态。

Node

当我们想实现一些特殊的功能,想在节点中加入自己的代码时,我们可以使用最简单的 Node 类型。一般说来,我们会在 node 中添加自己的 action ,当 execution 到达 node 时会触发这个 action ,来实现我们期望的行为。

Node 在流程图上会显式的表现出来,与之相反的, action 在图上则是看不见的。所以如果这个功能对于业务分析而言不是太重要的话,我们往往直接使用 action ,而不去显式的在流程图中添加一个 node 节点。

Task Instance

Task 是由人去完成的任务。当任务分派给用户后,流程会进入等待状态。

Task 定义了如何创建 Task instance 并指派给用户。 Task 通常定义在 task-node 节点里。在流程定义中, Task name 必须唯一。

Task instance 生命周期

Task instance 通常在流程 execution 进入 task-node 时自动创建。也可以在运行时手动创建。这种情况通常出现在需要给一个 task 生成多个 task instance 时。如果要不自动创建 task instance ,需要将 task node 中的 create-tasks 设为 false 。此时可以给 node-enter 事件添加 action ,在 action 中创建 task instance 。

Task instance 在创建后会被分派给一个或多个用户。但任务分派跟 task instance 的生命周期没有直接的关系。分派给用户后就能够通过 TaskMgmtSession.findTaskInstancesByActorId 方法得到这个 task instance 。

Task Instance 和 execution

当 task instance 结束时,它会给 token 发一个 signal 消息,触发 token 继续向下进行。而如果这个 task node 生成了多个 task instance ,我们能够指定 task instance 结束时流程 execution 如何继续进行。 Task node 的 signal 属性有如下几种选择:

l last :是默认选项。等待最后一个已创建的 task instance 结束之后, execution 向下执行。如果还没有创建 task instance ,则 execution 直接向下执行。

l last-wait :也等待最后一个 task instance 结束,但如果还没有创建 task instance ,则 execution 必须等待创建了 task instance 并全部结束之后才能继续执行。

l first :有一个 task instance 结束之后 execution 就向下继续。如果还没有创建, execution 也向下继续执行。

l first-wait :必须等待 task instance 创建之后,并且有一个 task instance 结束,流程才继续向下执行。

l unsynchronized :不管 task instance 是否创建,也不等待 task instance 结束,流程直接向下执行。

l never :也不管 task instance 是否创建或结束, execution 一直等待,不向下执行。这时需要手动的给 token 发一个 signal 请求,才能触发流程的继续执行。

Assignment 和 Swimlane

jBPM 可以把创建好的 task instance 分派给一个或多个用户。 jBPM 能够将任务添加到一个用户的任务列表中,也可以把任务分配到一个参与者池( pool of actors )中,在这种情况下,每个池中的参与者都作为这个任务的一个候选人,都能够处理这个任务。

jBPM 用 actorId 来代表一个任务的一个 actor (参与者)。 ActorId 是一个 string ,在我们的程序中将用户 Id 作为 actorId 保存。另外在我们的程序中为了统一,都采用了 pooled actor 来进行任务的分派。而在 pooled actor 中的一个 actor 处理完任务后,会把这个 actorId 写入到 task instance 中。

Task instance 的分派通过 AssignmentHandler 接口进行。 AssignmentHandler 接口有一个 assign 方法:

public interface AssignmentHandler extends Serializable {

void assign( Assignable assignable, ExecutionContext executionContext )

}

Assignable 是一个 task instance 或是 swimlane instance 。在 assign 方法中需要给 Assignable 接口分配参与者,即调用 assignable.setActorId 或是 assignable.setPooledActors 方法。

Swimlane 可以看成流程中的一个角色的概念。它主要是为了保证多个任务能够被相同的 actor 处理。假设多个 task 指定了相同的 swimlane ,这样在第一个 task instance 被创建之后,会通过 AssignmentHandler 给这个 swimlane instance 分派了用户。之后所有设置了这个 swimlane 的其它 task instance 都会分配给相同的用户进行处理。

Token

Token 实际上可以说是 jBPM 整个流程控制中最核心的部分。

jBPM 用 Token 来表示流程实例当前运行的位置,同时利用 token 在流程各个节点之间的流转来表示流程的状态推进。 Token 总是指向流程图中的当前节点。

在启动一个工作流时,会根据流程定义创建一个流程实例。同时会为这个流程实例生成一个 token ,称作 Root token ,并将这个 Root token 指向流程图上的 start state 节点。此时 Root token 会一直停留在 Start-state 节点上,直到它收到一个 signal 消息,才会触发 token 的移动,让流程实例向下运行下去。

流程的推进最终是通过 token.signal() 方法进行的。但外部程序触发流程实例向下进行一般最常用的是调用 task instance 的 end 方法。或者直接调用 processInstance 的 signal 方法。而这两个方法最终仍然是调用当前 token 的 signal 方法。

token.signal() 方法会触发一系列的流程转移动作,如下图:

首先 token 会调用当前节点的 leave 方法。在 Node.leave 方法中, node 会调用 transition 的 take 方法,并把当前 execution 的 context 当作参数传入。 Transition 在 take 方法中会把 executionContext 对象传到 transition 的 to 节点,调用 Node.enter 方法。然后在 node.enter 方法中会再调用 node 的 execute 方法,执行相应的业务逻辑操作。在这个转移的过程中触发了多个事件,包括 node-leave , transition-take , node-enter 等。在这一系列操作结束之后, token 终于从一个节点转移到了下一个节点。这就是 jBPM 流程推进的方式。

对于分支流程, jBPM 采用了 parent-child token 的机制。正如前面介绍 fork 节点时所述,在遇到需要分支的地方,会为每一个分支生成一个子 token 。然后分别调用每个子 token 的 signal 方法,让子 token 向各个分支推进,而父 token 则停留在分支节点。

在分支流程汇聚的地方一般采用 join 节点进行聚合,等待所有生成的子 token 都到达 join 节点后,父 token 沿着 join 节点的离开 transition 继续向下推进。

Variable

jBPM 在流程传递的过程中可以保存一些变量( variable )到流程 context 中。这些变量是一些“键 - 值”对,能够被持久化到数据库中。各个节点之间可以通过 context 中传递的变量共享数据。

变量的作用范围分为两种,一种是 token 范围的变量,也叫流程变量,另一种是 task instance 内部的变量。

每个 token 都有一个自己的变量 map 。 Process Instance 有一个 token 树。所以从一个 token 取变量的值的时候,如果这个 token 没有保存该变量,则会递归的这个 token 的父节点进行查找,一直到 root token 。如果取变量时不指定具体的 token ,则默认的会去 root token 中进行查找。相应的,在创建变量时也必须指定 token 。如果不指定 token ,则默认会把变量创建到 root token 中。

如果父 token 和子 token 中存在重名的变量,则子 token 的变量会覆盖掉父 token 的变量。只有当直接指定从父 token 中取变量时,才能得到父 token 的变量。这与 OO 语言中父类与子类的关系很相似。

Task instance 内部也可以持有自己的变量。从 Task instance 取变量时, jBPM 会先在 task instance 自己的变量中查找,如果自己没有保存这个变量,它会再去相关的 token 中进行查找。这样, task instance 和 token 之间变量的关系就如同父子 token 之间变量的关系一样。

在 task instance 结束时, task instance 会把自己的变量提交到 token 的变量中。如果 token 中存在同名变量, task instance 的变量会覆盖掉 token 中的变量。

 

参考文献:

jBPM 3.2.3 UserGuide

揭秘jbpm流程引擎内核设计思想及构架

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值