alartin 的个人资料Windows Live 共享空间照片日志列表更多 ![]() | 帮助 |
|
|
1月22日 JBPM术语:ProcessInstance和ProcessDefinition概述 ProcessInstance是ProcessDefinition的一个执行实例,想象一下对于订票流程,每个客户的订票动作都会根据订票流程定义而创建一个流程实例,也就是执行实例ProcessInstance.创建执行实例很简单:ProcessInstance(ProcessDefinition def)。当一个ProcessInstance被创建后,负责执行主路径的token也被创建,这个token就是根token(root token),根token此时位于流程定义的开始状态start state. JBPM术语:事件Event概述 Event反映的是流程执行中的各个时刻。在流程执行中JBPM引擎会在计算下一个状态的时候触发各种事件。一个事件通常和流程定义中的一个元素相关联,比如流程定义本身,节点或者转移。大部分的元素能够触发不同类型的事件,比如一个节点可以触发节点进入事件,节点离开事件。事件其实是和动作连接在一起的。每个事件维护一个动作列表。当JBPM引擎触发一个事件的时候,该事件维护的动作列表中的动作将被执行。 事件类型 在JBPM中事件类型是写死在事件类中的,共有16种:
JBPM术语:超级状态Superstates概述 一个超级状态就是一组节点。超级状态可以以回归方式嵌套。超级状态能够给流程定义带来层次效果。例如一个应用可能要把流程中的所有节点按阶段进行分组。动作可以和超级状态的事件相关联。超级状态能够让一个token在给定的时间上位于多个嵌套的节点中。这会带来很多方便,例如你可以方便的查询流程的执行是否在启动阶段。在JBPM中你可以将任意的节点们组成一个超级状态。 超级状态的转移 超级状态的所有转移都通过超级状态中的节点们的token来完成。转移也可以到达超级状态。此时,token被重定向到超级状态中的第一个节点。超级状态外的节点可以拥有指向超级状态内节点的转移,当然同时超级状态内的节点也可以拥有指向超级状态外的节点的转移,甚至指向超级状态本身的转移。超级状态甚至可以有自引用。 超级状态的事件 超级状态拥有两个特定的事件:进入超级状态,离开超级状态。无论超级状态 JBPM: 上下文概述 上下文Context其实就是处理流程变量的地方。流程变量使用键值对来维护流程实例相关的信息。上下文必须要存储到数据库中。 访问变量 我们通过上下文实例(ContextInstance)来和流程变量打交道,上下文实例就是一个中央接口,位于org.jbpm.context.exe包中,从流程实例ProcessInstance中获得上下文实例: ProcessInstance processInstance = ...; ContextInstance contextInstance = (ContextInstance) processInstance.getInstance(ContextInstance.class); 常见的对上下文实例的操作包括:设置变量,获得变量: void ContextInstance.setVariable(String variableName, Object value); void ContextInstance.setVariable(String variableName, Object value, Token token); Object ContextInstance.getVariable(String variableName); Object ContextInstance.getVariable(String variableName, Token token); 变量的名字是String类型的。默认情况下,JBPM支持多种变量值的类型:
Null值也支持,也可持久化。其他类型的也可以存储在流程变量中,只是会抛出异常。当流程变量的值为hibernate存储的类的时候需要配置JBPM. 流程变量的生命周期 流程变量无需在流程文件中声明,在运行时,你可以将任意Java对象设为变量。如果该变量不存在的话,变量会被创建,就象Map一样。 删除变量也很简单: ContextInstance.deleteVariable(String variableName); ContextInstance.deleteVariable(String variableName, Token token); JBPM现在支持自动改变类型。这意味着你可以使用不同类型的值来覆盖一个变量。当然你应该严格控制类型改变的次数,毕竟这需要额外的数据库通讯。 流程变量的持久化 流程变量是流程实例的一部分。将流程实例保存在数据库中,意味着数据库和流程实例同步。流程变量的创建更新删除是通过流程实例的更新来实现的。 流程变量的范围 每个token,也就是每个流程执行的路径都有一套自己的变量。请求变量总是在token上发生的。流程实例拥有一个token树。在请求变量的时候,当你没有指定token参数的话,默认的是root token作为参数。 变量的查询是在给定的token的父token们中递归查找的。类似编程语言中的变量范围。 如果你在一个token上设置一个并不存在的变量,那么这个变量将在root token上创建。这意味这每个变量的默认范围是流程范围。 如果你需要限定变量的范围,则需要显式地指定Token: ContextInstance.createVariable(String name, Object value, Token token); 变量的过载overloading 变量的过载指的是流程执行的每个路径都可以有相同一套名字的变量,也就是说不同的token中变量的名字可以一样,值和类型可以不一样。token之间是独立的。这在流程跨越同一个转移,启动不同的并发路径时很有用。 区别不同执行路径的就靠它们各自的这些变量。 变量的重载overriding JBPM: 任务的管理 Task Management概述 JBPM的核心就是持久化流程。持久化流程中最重要的特性就是管理人们的任务和任务列表。JBPM允许我们设定程序为人工参与进入等待状态。 任务 任务是流程定义的一部分。任务负责定义流程执行中任务实例task instance如何必须被创建和指派。所以任务也就是任务定义。任务可以在任务节点中定义,也可以在流程定义中定义。不过通常的方法是在任务节点中定义一个或者多个任务。这意味着任务节点task node代表着一个用户的人工任务,流程执行必须等待直到参与者actor(也就是用户)完成这项人工任务。当参与者actor完成人工任务后,流程执行将继续。如果一个任务节点中指定了多个任务,默认的行为是必须流程执行必须等待所有任务都完成。 流程定义中也能定义任务。在流程定义中的任务能够通过名字查找,在任务节点中被引用,或者内部动作inside actions中使用。事实上,无论任务在流程定义中定义还是在任务节点中定义,只要任务有名字,都能够在流程定义中查找。需要注意的是:任务的名字在整个流程定义中必须唯一的。 任务可以有优先级。优先级在这个任务的每个任务实例中作为初始优先级。任务实例task instance能够在日后修改这个优先级。 任务实例 task instance 一个任务实例能够被指派给一个参与者ID, actorId(一个字符串)。所有的任务实例都在JBPM的JBPM_TASKINSTANCE表中存储。我们可以通过这张表查询给定actorId的所有任务实例,通过这种方法你可以获得特定用户的任务列表。JBPM任务列表机制能够将JBPM任务和其他任务结合起来,甚至能够结合那些和流程执行不相关的任务。通过这种方式,JBPM的开发者能够容易的将JBPM流程任务和其他系统的任务在一个同一的任务列表仓库同一管理。 任务实例的生命周期 任务实例的生命周期非常直观:创建后,任务实例就可以开始了,然后,可以被结束,这意味着任务实例被标记为完成。注意:为了灵活性,任务的指派并不是任务实例生命周期的一部分。所以任务实例可以被指派了,也可以尚未指派,这丝毫不影响任务实例的生命周期。任务实例通常是在流程执行进入任务节点的时候被创建。创建的方法是TaskMgmtInstance.createTaskInstance()。然后,用户界面组件可以通过TaskMgmtInstance.findTaskInstanceByActorId()方法查询任务列表。用户界面组件可以调用TaskInstance.assign(String), TaskInstance.start(), TaskInstance.end()等方法。 任务实例通过日期属性来维护它的状态:create, start, end. 可以通过TaskInstance相应getter方法访问。目前完成了的任务实例将会通过结束日期被标记为完成,因此对于后续查询任务列表的时候,完成了的任务实例不会被包含在任务列表中,不过它们仍旧在JBPM_TASKINSTANCE表中。 任务实例和图的执行 从某种意义上说,任务实例是参与者actor任务列表上的项目。任务实例可以信号化,信号化的任务实例可以在任务结束的时候向token发出signal,让流程执行继续下去。任务实例也可以拦截化/阻止化,这意味着相关的token在任务未完成前不能离开任务节点。任务实例默认是信号化的。 在一个任务节点中有多个任务实例的情况下,流程的开发者可以指定任务实例的完成将如何影响流程执行的继续。通过任务节点的signal属性的各种值来控制这一点:
任务实例也可以通过运行时计算而创建。在这种情况下,在任务节点的进入节点事件中添加一个ActionHandler,并且将属性create-task设为false(意味着进入任务节点并不创建任务,而是让动作监听进入节点事件,在动作中运行时计算), 下面是个例子: public class CreateTasks implements ActionHandler {
public void execute(ExecutionContext executionContext) throws Exception {
Token token = executionContext.getToken();
TaskMgmtInstance tmi = executionContext.getTaskMgmtInstance();
TaskNode taskNode = (TaskNode) executionContext.getNode();
Task changeNappy = taskNode.getTask("change nappy");
// now, 2 task instances are created for the same task.
tmi.createTaskInstance(changeNappy, token);
tmi.createTaskInstance(changeNappy, token);
}
}我们提到过,任务可以在任务节点中定义,也可以在流程定义中定义。如果在流程定义中定义,我们可以通过TaskMgmtDefinition获取。 TaskMgmtDefinition继承了ProcessDefinition, 提供了任务管理信息。 完成一个任务实例很简单,调用TaskInstance.end()方法就可以了。你也可以在这个方法中指定一个转移transition,这样的话,任务实例完成 后将触发流程执行继续下去,离开任务节点,走向指定的转移。 任务指派 流程定义中包含了任务节点。一个任务节点可以包含零个或多个任务。什么是任务呢?任务其实就是流程定义中静态描述的一部分(是说任务仅仅是个描述而已)。在运行时,任务实例 将根据任务这个描述创建。而一个任务实例可以说就是一个人的任务列表中的一项。任务实例总是要被指派的。 JBPM的任务指派模型分为PULL和PUSH. 任务指派的数据模型 任务实例或泳道实例的参与者ID actorId负责这个任务。而其中的PooledActors, 参与者候选池维护一系列任务的候选者,每个候选者都有可能负责这个任务,当候选者获得这个任务 后,他就为这个任务负责。需要注意的是:参与者ID和参与者候选池是可选的,也可以组合在一起使用。 个人的任务列表 个人任务列表负责维护指派给特定个人的任务,通过任务实例的参与者ID就可以看出。所以要想将任务实例放入个人任务列表中,只需下列方法的一种:
我们可以通过TaskMgmtSession.findTaskInstances(String actorId)来获取特定人的任务列表。 组的任务列表 参与者候选池指的是任务实例的候选者。这意味着这个任务可以提供给多个用户,其中一个候选者可以被设置接受这个任务。候选池中的参与者可以看作是同一个组的,如果这个任务是提供给候选池的,用户必须先将任务拿到个人的任务列表中,才能执行任务。 TODO 背后的动机是我们希望将Identity组件从JBPM的任务指派中剥离。JBPM应该只关注actorId这些String类型的对象,而不应该关注用户,组或者其他识别信息之间的关系。 actorId总是覆盖参与者候选池中的actor(pooled actor)。所以如果任务实例既有actorId,也有参与者候选池的话,那么这个任务实例只出现在个人任务列表中。不过,参与者候选池有重要作用,一个用户可以将任务实例放回到组中重新供参与者候选人获取,这些只需要将任务实例的actorId属性去掉即可。 任务实例变量 任务实例可以拥有自己的一套变量,并且任务实例能够“看见”流程变量。任务实例是在一条执行路径token中创建的。需要注意的是:token和在这个token中创建的任务实例之间也是父子关系,这和token和其子token是父子关系是一样的。任务实例的变量和token相关的流程变量之间的范围规则同样适用。这意味着一个任务实例能够看到自己的一套变量,加上和它相关的token的所有变量。 任务控制器能够用来在任务实例范围和流程范围变量之间传递和提交变量。 任务控制器 任务控制器本质上是流程变量和用户界面程序的桥梁。从这张图片可以看出,任务是以任务实例的方式赋给特定的用户的,任务控制器任务实例负责流程变量和任务变量之间的传递。用户只能通过任务实例中的变量和流程打交道,因此用户界面接口中的输入输出都是流程变量<->任务控制器<->任务实例<->用户界面。因此实际上用户在界面中输入的都是以任务变量的形式保存,当任务结束后通过任务控制器来将任务变量转化为流程变量。 最简单的情况下,流程变量和用户的表单参数是一对一的。任务控制器在任务元素中指定。JBPM提供一个默认的任务控制器,它维护一个变量元素列表。变量元素指示如何将流程变量拷贝到任务变量。 <task name="clean ceiling"> <controller> // name属性指的是流程变量的名字,mapped-name属性指的是任务变量的名字,如果没有设定mapped-name,则任务变量名和流程变量名一致 // 需要注意的是:mapped-name任务变量名就是任务实例表单中字段的标签 // access属性指定变量传递的形式,read指的是任务实例创建时,从流程变量中拷贝到任务变量 // write指的是任务实例完成时,从任务变量中拷回到流程变量中,默认的是read,write
<variable name="a" access="read" mapped-name="x" />
<variable name="b" access="read,write,required" mapped-name="y" />
<variable name="c" access="read,write" />
</controller>
</task>需要注意的是:一个任务节点可以有多个任务,而一个开始状态start-state只能有一个任务。 如果你觉得流程变量和表单参数一对一的关系无法满足需求的话,你也可以实现自己的任务控制器TaskControllerHandler。 public interface TaskControllerHandler extends Serializable {
void initializeTaskVariables(TaskInstance taskInstance, ContextInstance contextInstance, Token token);
void submitTaskVariables(TaskInstance taskInstance, ContextInstance contextInstance, Token token);
}<task name="clean ceiling">
<controller class="com.yourcom.CleanCeilingTaskControllerHandler">
// 此处配置实现的任务控制器
</controller>
</task>12月21日 JBPM:事务的划分JBPM是在客户端线程中运行的流程,因此它自然就是同步的。这意味着token.signal()和taskInstance.end()方法只有在流程执行进入了一个新的等待状态才能返回。 大部分情况下,这是非常直观的方法,因为流程执行能够很容易的和服务器端事务绑定:在同一个事务中流程从一个状态执行到另一个状态。 在流程中需要大量时间计算的场景中,这种方法肯定不是想要的。为了解决这种场景遇到的问题,JBPM引入了一个异步消息系统,允许流程以异步的方式执行。当然,在Java企业环境中,JBPM可以被配置为使用其他的JMS消息代理系统,而不是使用内置的消息系统。 在任何节点中,JPDL支持async属性为true。被配置为异步的节点将不会在客户端线程中执行。相反,一个消息将被异步消息系统发送,然后线程就被返回给客户端(这意味着token.signal()和taskInstance.end()将被返回)。 注意,这时候JBPM客户端代码可以提交事务了。在流程更新的同一个事务中消息将被发送。所以事务的结果就是:token被移到了下一个节点(这时这个节点还没有被执行),并且一个执行节点命令(ExecuteNodeCommand)消息被异步消息系统发送给JBPM的命令执行器Command Executor。 JBPM的命令执行器从消息队列中读取并且执行。在执行节点命令中,流程将被继续执行。每个命令都在一个单独的事务中被执行。 所以为了让异步的流程能够继续,一个JBPM命令执行器必须运行。最简单的方法是在Web应用中配置CommandExecutionServlet。让这个Servlet负责运行JBPM命令执行器。或者你使用其他方法,总之必须保证命令执行器线程必须运行。 作为一个流程的建模者,你不必关心所有的异步消息,关键一点是事务的划分:默认情况下,JBPM在客户端操作事务,计算整个流程直到流程进入一个等待状态。你可以将async属性设为true去在流程中划分事务。 <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();
}在上述例子中,第一个事务结束后,流程执行的根令牌root token将会指向节点one,然后一个执行节点命令消息ExecutionNodeCommandMessage 被发送给命令执行器。在接下来的事务中,命令执行器将会读取消息队列的消息,然后执行节点one。这个动作能够决定是传递流程执行还是进入等待状态。 如果动作决定传递流程执行,那么在流程执行到达节点two的时候,事务就结束了。然后继续...继续...下去。 JBPM:图的执行Graph ExecutionJBPM中,图的执行模型是基于对流程定义的解释和命令模式链的。 对流程定义的解释意味这流程定义必须存储在数据库中(其实不尽然,但必须有某种存储机制)。在流程执行过程中需要流程定义的信息。需要注意的是JBPM使用hibernate的二级缓存来避免在运行时加载流程定义信息。这是因为一般来说流程定义不会轻易改变,所以我们能够将流程定义放入缓存中。 命令模式链意味着图中的每个节点需要负责将流程的执行传递下去。如果某个节点没有传递流程的执行,那么这个节点位于等待状态。 JBPM的设计思想是在流程实例上启动执行,然后这个流程执行将一直继续下去直到进入一个等待状态。 一个令牌token代表一个执行的路径。一个token拥有一个指向流程执行中的一个节点的指针。同样token也可以通过数据库将自己持久化。在等待状态时,token可以被持久化到数据库中。我们看一下JBPM是如何计算token的执行的。当有一个信号singal发送给token的时候,执行开始启动,接着执行通过命令模式链的方式沿着转移transition和节点node传递。在类定义中有相关的方法: 当token在节点中的时候,信号可以发送给token.信号的发送指令着执行的启动。一个信号必须指定当前节点中token的离开转移(节点到转移被称为离开转移,转移到节点被称为到达转移) 。 需要注意的是第一个转移就是默认的转移。 给token发送信号的时候,token会调用在其内维护的当前节点的Node.leave(ExecutionContext,Transition)方法。你可以将ExecutionContext想象成是一个token(会引起误导,不过ExecutionContext中维护着一个token).这个方法(Node.leave())能够触发一个离开节点的事件(node-leave类型),并且调用转移transition的take方法Transition.take(ExecutionContext). 这个方法(Transition.take())能够触发一个transition的事件并且调用这个转移Transition的目的节点的enter方法(Node.enter())(每个Transition都维护一个from节点和一个to节点,from节点被称为源节点,to节点被称为目的节点)。 每种类型的节点都在execute方法中有自己的行为。每个节点都通过Node.leave(ExecuteContext,Transition)来负责传递流程的执行。 简单的说:
需要注意的是整个的下一状态的计算(包括动作的调用)都是在客户端线程中完成的。一个常见的错误理解是所有的计算都必须在客户端线程中完成。对于任何的异步调用,你可以使用异步消息JMS。当消息在流程实例更新的同一个事务中被发送时,所有的同步问题都要小心的处理。一些工作流系统在图中的所有节点间使用的是异步消息。但是在高通量环境中,这种算法能够为业务流程提供更高的灵活性和控制性。 12月20日 JBPM术语:动作Action概述 动作是在流程执行中的事件中的Java代码片段。你可以认为动作是可以在事件上发生的(注意,也可以在节点内发生)。为何要有动作这个概念呢?原因在于图虽然是软件需求间通讯的重要设备,但是图只是软件的一个视图或者投影,它隐藏了太多技术细节(流程图主要给业务分析员看或者编辑的),而动作是给流程图增加技术细节的机制(开发者需要用到它)。我们需要动作来装点图,这样的图才有意义。 因为动作多少和事件有关系,我们看一下事件类型。主要的事件类型包括:进入节点,离开节点,走向转移。 注意:动作可以在事件中发生,也可以在节点中发生!而这两种情况非常不同!
我们看一下事件中动作的例子,假定我们需要在一个指定的转移中更新数据库,更新数据库是技术细节,业务分析员不关心这个。这个例子是如果开除一个员工,我们需要将他的工作证收回,在这个转移中,需要更新人力资源的数据库。 public class RemoveEmployeeUpdate implements ActionHandler { // 实现ActionHandler
public void execute(ExecutionContext ctx) throws Exception {
// 从流程变量中获得开除的员工
String firedEmployee = (String) ctx.getContextInstance().getVariable("fired employee");
// by taking the same database connection as used for the jbpm updates, we
// 重用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>在这个例子中,我们实现了ActionHandler,同时将这个类作为动作放在名为收回工作证的转移中。 JBPM术语:节点Node和节点类型NodeType概述 流程由节点和转移组成。每个节点都有特定的类型。节点的类型决定了当一个流程执行在运行时到达这个节点后将发生什么。JBPM已经具有多个预定义的节点类型。当然你可以定制自己的节点从而实现特殊需求的节点行为。那么节点负责什么呢? 节点主要负责两件事情:
传递流程的执行 每个节点都有以下几种方式传递流程的执行:
节点的类型 JBPM包含了很多预定义和实现的节点类型,这包括:
JBPM术语:图的元素GraphElement概念 GraphElement是JBPM的一个定义,位于org.jbpm.graph.def包中。GrahpElement是个抽象类。 GraphElement有一个名字name, 一个描述description. GraphElement有多个事件event,使用Map来存储。 GraphElement有多个例外处理器exceptionhandler, 使用List来存储。 GraphElement有一个流程定义processDefinition. 有意思的是:processDefinition本身就是GraphElement的子类。 子类
重要的方法
JBPM术语定义:令牌token概念 token是执行execution的一条路径, 它是运行时的概念,维护一个指向流程定义中节点的指针。 我们通常通过ProcessInstance.getRootToken()或ProcessInstance.findToken(String token)来获得token。 每个token可以有一个父token, 这意味着这个token可以由父token产生出来,这主要考虑的是流程可以分叉fork,因此每个分支都要有新的token来代表各自的执行路径。当然每个token都可以有多个子token,使用Map来存储它们。 每个token都可以有多个注释comment,使用List来存储它们。 每个token都有一个流程实例ProcessInstance, 也有一个子流程实例Sub ProcessInstance 每个token都有一个节点Node, 表示当前节点 原理 信号signal用来指引一个令牌token去继续一个图的执行。当token接受到一个无名的信号时,它将离开当前的节点(注意包括节点的子类),走向默认的转移transition, 要是信号有参数指定转移的话,走向该指定的转移。注意在流程实例ProcessInstance中信号signal是委派给根令牌root token的!因此对流程实例的信号实际就是根令牌的信号。 当token进入一个节点后(token维护一个Node),这个节点将被执行。注意节点node自身负责图的继续执行,当这个token离开这个节点就意味着这个节点中的图的继续执行已经完成了。每种节点类型node type都可以实现自己特定的图的继续执行。 如果一个节点node并不传递执行,说明这个节点是一个状态。 重要的方法
token.signal()方法能够激活token本身,然后让token离开当前状态,走向默认的转移transition 带参数的signal()方法让token走向指定的转移transition
token.suspend()方法能够将token暂停挂起
token.resume()方法能够将token重新启动 |
|
|