另一实例:
(实例)
http://linliangyi2007.javaeye.com/blog/176340(简介)
jBPM相关概念
http://blog.csdn.net/fndcz/archive/2007/08/05/1726848.aspx
(0)
jbpm下载:
http://sourceforge.net/project/downloading.php?group_id=70542&filename=jbpm-4.0.0.Beta2.zip&a=31905032
现使用:jbpm-jpdl-suite-3.2.3.zip
(1)
eclipse安装jbpm3.2:
jBPM3.2.3的流程设计器插件在Eclipse下的安装方法
1. 在Eclipse的安装目录下建名为links的目录。
2. 在links目录中新建名为jbpm-designer3.link的文本文件。
3. jbpm-designer3.link文件内容为jbpm的designer目录的路径,如:path=C:/Program Files/jbpm-jpdl-3.2.3/designer
4. 重启Eclipse,单击菜单项【Window】,选择【Preferences】,弹出【Preference】窗体。
5. 在左侧看到【JBoss jBPM】节点,即为安装成功。
为了方便创建jBPM项目可进行如下操作:
1. 单击【JBoss jBPM】节点下的【Runtime Locations】,单击【Add...】按钮,弹出【Add Location】窗体。
2. Name项填写名称(如:jBPM3.2.3)和Location项选择jbpm的安装目录(如:C:\Program Files\jbpm-jpdl-3.2.3)即可。
eclipse中创建一个jbpm项目:
1.
右键--new--other--jBoss jbpm--process project。
这个时候你会看见他弹出一个对话框,输入你的工程名字,然后点击next,这个时候你会发现他已经把jbpm加载进去了.
记住要选中 Generate simple ......。
2.
工程建立完了,我们开始建立我们的流程定义文件。在工程里面你会发现src/main/jpdl这个source folder,然后你会看见他里面已经有了一个流程定义文件了。
若要自定义,右键src/main/jpdl,然后new--other--jBoss jbpm--process definition。这个时候他就会弹出一个对话框,起一个你要写的流程定义文件的名字输入进去,可以了。这个时候你打开你建立的那个文件夹,里面就有processdefinition.xml文件。
以下参考: http://linliangyi2007.javaeye.com/blog/176345
(2)
jpdl:
1.
任务结点<task-node>
任务结点是一个需要人工参与的结点类型。当流程进入结点时,会生成相应的任务实例(TaskInstatnce),并执行,即:通过委派接口 AssignmentHandler或jBPM表达式将任务委派给一个或多个特定的角色或参与者。然后结点自身进入等待状态,直到用户执行signal等命令,流程会离开当前的结点,即执行<transition>,然后进入下一结点,且执行相应的<task>,然后再等待。
2.
判定结点<decision>
判定结点的设计目标是根据上下文环境和程序逻辑,判定流程转向。相当于java中的swith case判断。一个decision能够具有许多离开的transition。
通过指定一个实现DecisionHandlder接口的Java委派类或jBPM表达式,来返回转向(transition)的字符窜类型的名称(可以是中文哦),来达到决定流程方向的功能。
3.
普通结点<node>
普通结点也可以定义相应的处理任务,通过定义相应的ActioinHandler类。同任务结点不同的是,普通结点定义的任务是由流程自动执行的,无须人工干预。
4.
不理解部分:
三种结点都可定义结点事件(event):
node-enter,该事件在流程进入结点时触发
node-leave,该事件在流程离开节点是触发
可以在事件上挂接ActioinHandler接口的实现类来完成一些特定的功能。
三种节点都可以定义异步处理方式(async属性):
异步处理意味着每个结点的事务处理是通过消息机制分离的,不再同一线程中统一调用执行。而是由消息监听线程从消息队列中取得消息体来运行相应得程序。
此外我们定义了结点间的转向(transition),用来记录和处理状态的变迁。每个转向中,可以委派一个或多个的ActioinHandler接口实现类,负责处理节点变迁时的上下文状态变更及回调用户定义的处理程序。
(3)
流程的程序接口说明
1.
动作处理接口(ActioinHandler)
接口方法:void execute( ExecutionContext executionContext ) throws Exception
该接口是jPDL中最常用的一个回调接口。从它的接口方法可以发现,它仅仅暴露了流程执行上下文变量ExecutionContext。用户程序通过ExecutionContext来了解流程的执行状态,并通过改变ExecutionContext中的属性值来影响流程的执行。
ActioinHandler接口可以在所有能包含事件(event)、动作(action)元素的地方被回调。
如,
可以用于<task-node>,<node>.
这里要提到一个很重要的区别,就是作用于Node上的ActoinHandler和作用于Transition上的ActoinHandler是有不同的。区别在于,Node上的ActoinHandler在结束业务逻辑处理后,必须调用executionContext.leaveNode();或 executionContext.leaveNode(transition)来保证流程向下执行;而作用于Transition上的则不需要。
2.
判定处理接口(DecisionHandlder)
接口方法:String decide(ExecutionContext executionContext) throws Exception
判定接口仅适用于判定节点(decision)中。从它的接口方法可以看出,方法要返回一个字符串型的结果,这个结果必须和判定节点拥有的转向(transition)集合中的一条转向名称相匹配。
在DecisionHandlder的接口方法中一样能访问到ExecutionContext变量,这为判定提供了执行上下文的根据。当然,如果有必要,用户也可以在该接口中改变ExecutionContext中的变量值。
3.
委派处理接口(AssignmentHandler)
接口方法:void assign(Assignable assignable, ExecutionContext executionContext) throws Exception;
委派处理接口被用户任务元素(task)的委派(assignment)子元素中,它的职责很明确,就是将任务分配给指定的人员或角色。
在AssignmentHandler接口的方法中,Assignable变量通常指任务实例(TaskInstance)。通过将 ExecutionContext和TaskInstance两个变量都暴露给接口方法,用户就可以根据流程上下文情况,来决定要将指定的任务分配个谁。
(4)
我们将给大家剖析两个流程测试类。一个是简单的基于内存模型的流程测试FirstFlowProcessTest;一个是更贴近实用的,基于MySQL数据库操作的标准测试案例。
(4.1)
测试案例类:FirstFlowProcessTest.java
该案例是在没有数据库支持的情况下,对报销流程进行运行测试,测试逻辑如下:
1.
加载流程定义
ProcessDefinition.parseXmlResource("firstflow/processdefinition.xml")
在没有数据库存储的情况下,流程定义通过ProcessDefinition类直接从processdefinition.xml文件中解析加载。
2.
实例化流程对象
1. //生成实例
2. pi = pdf.createProcessInstance();
3. assertNotNull("processInstance should not be null", pi);
4. //设置流程发起人
5. pi.getContextInstance().createVariable("initiator", user);
6. //触发流程转向
7. pi.signal();
代码说明:
在获得流程定义的实例后,可以用它生成流程实例,使用如下的语句:
pi = pdf.createProcessInstance(); --即进入流程的<start-state>且进入等待.
流程实例拥有自己的ContextInstance环境变量对象。它实际上是一个HashMap,以key-value方式记录了流程的上下文变量值,代码中的pi.getContextInstance().createVariable("initiator", user);就是向环境变量中添加一个key为initiator的对象。
每个流程实例都拥有自己Token令牌对象,主流程有自己的RootToken,子流程也拥有自己的子Token。父流程的Token和子流程的Token相互关联,形成Token树。Token对象表示流程运行的当前位置(运行到哪个节点了)。通过对Token对象的signal()方法调用,可以使流程向下运行。代码中的pi.signal();实际上是间接调用了pi.getRootToken().signal();它使得新建的流程继续向下个节点(即借款申请单填写)进发。
3.
员工发起借款申请
1. /**
2. * 填写提交申请单
3. * @param money
4. */
5. protected void submitApplication(int money){
6. System.out.println("==FirstFlowProcessTest.submitApplication()==");
7.
8. TaskInstance ti = (TaskInstance)pi.getTaskMgmtInstance().getTaskInstances().iterator() .next()
9. System.out.println("ti.actor = " + ti.getActorId());
10. ContextInstance ci = ti.getContextInstance();
11. ci.setVariable("money",new Integer(money));
12. ti.end();
代码说明:
在借款流程发起后,流程进入了申请单填写阶段。这个阶段是个人工的任务,需要用户的介入。因此,对于要借款的用户而言,首先是获取填写申请单的任务实例:
TaskInstance ti = (TaskInstance)pi.getTaskMgmtInstance().getTaskInstances().iterator() .next()
在这个测试类中,由于没有数据库。对流程实例的引用是依靠了类的全局标量pi。这里通过pi获取全部的任务列表。实际上有且仅有一个任务,就是我们刚刚发起的申请单填写任务。
接下来,我们获取流程的上下文变量,将申请借款的数额记录在上下文变量中ContextInstance ci = ti.getContextInstance();
ci.setVariable("money",new Integer(money));
最后,我们要结束当前任务,告诉流程继续下行,调用ti.end();这个方法的本质依然是调用了token.signal(),它选择了一个默认的transition进行转向。这里要说明的是signal方法有多态的实现signal(Transition transition),是可以指定具体转向参数的。
4.
部门领导审批申请
1. /**
2. * 部门经理审批
3. * @param pass
4. */
5. @SuppressWarnings("unchecked")
6. protected void approveByManager(boolean pass){
7. System.out.println("==FirstFlowProcessTest.approveByManager()==");
8. Iterator<TaskInstance> it = pi.getTaskMgmtInstance().getTaskInstances().iterator();
9. for( ;it.hasNext(); ){
10. TaskInstance ti = it.next();
11. if(ti.getActorId().equals("DepartmentManager")){
12. List<Transition> transitions = ti.getToken().getNode().getLeavingTransitions();
13. for(Transition t : transitions){
14. System.out.println("----Transition" + t.getName());
15. }
16. assertEquals("DepartmentManager",ti.getActorId());
17. if(pass){
18. ti.end("部门经理审批通过");
19. }else{
20. ti.end("部门经理驳回");
21. }
22. return;
23. }
24. }
25. }
代码说明:
这里,流程进入了部门经理审批阶段。由于没有数据库支持,我们只能采取遍历任务列表,并比对委派者ID的方式来确定委派给部门经理的任务实例。(在后面的基于数据库的标准案例中,我们会看到如果根据用户的ID来获取分配给指定用户的任务)
ti.getActorId().equals("DepartmentManager") // 比对任务的委派人。
ti.getToken().getNode().getLeavingTransitions();//获取任务在当前节点上的所有转向。
这里我们要特别指出的是ti.end("部门经理审批通过")和ti.end("部门经理驳回")这实际上调用token.signal(transition);来完成任务的转向,从而使流程继续。
对于有多个转向的,可以用如下方式调用。
ti.end("部门经理审批通过"); --》即ti.end(transition name);
(4.2)
对于基于关系型数据库的jBPM系统,
1.
配置jbpm中的hibernate配置文件及在mysql中创建相应的mysql库。
2.
导入jbpm的mysql基本表;
3.
须先将流程定义文档发布到DB中。否则出错。如下:
public void testDeployProcessDefinition() throws FileNotFoundException {
// 从 jbpm.cfg.xml 取得 jbpm 的配置
JbpmConfiguration config = JbpmConfiguration.getInstance();
// 创建一个 jbpm 容器
JbpmContext jbpmContext = config.createJbpmContext();
// 由 processdefinition.xml 生成相对应的流程定义类 ProcessDefinition
InputStream is = new FileInputStream("src/main/jpdl/firstflow/processdefinition.xml");
ProcessDefinition processDefinition = ProcessDefinition.parseXmlInputStream(is);
// 利用容器的方法将流程定义数据部署到数据库上
jbpmContext.deployProcessDefinition(processDefinition);
// 关闭 jbpmContext
jbpmContext.close();
}
4.
导入成功后,会在jbpm的基本表中,看到相应的记录,如:jbpm_processdefinition,jbpm_action,jbpm_note等。
(4.3)
该案例模拟了标准运行环境中,基于关系型数据库的jBPM系统是如何执行流程的。
测试案例类:FirstFlowProcessDBTest.java
相对于简单流程测试案例,标准流程的业务是相同的。
它们的不同点在于:
简单流程通过XML载入流程定义,并使用类的全局变量来保存流程实例的引用;
而标准流程则是通过数据库载入流程定义,并使用数据库的会话来维护流程的运行。
1.
从数据库载入流程定义
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
从jBPM配置环境中获取jBPM上下文实例,jbpmContext是对jbpm持久层操作API及上下文环境的封装,它根据jBPM的配置文件生成。
GraphSession graphSession = jbpmContext.getGraphSession();
生成对流程图操作的持久层会话
ProcessDefinition pdf = graphSession.findLatestProcessDefinition("simple");
从数据库获取命名为“simple”的流程定义。
2.
新建流程实例,并存入数据库持久化
ProcessInstance pi = pdf.createProcessInstance();
根据流程定义生成实例。
3.
其中的
jbpmContext.save(pi);
保存实例到数据库,持久化实例。
4.
从数据库获取属于指定操作者的任务集
# JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
# try{
# List<TaskInstance> taskInstances = jbpmContext.getTaskList("DepartmentManager");
# for(TaskInstance ti : taskInstances){
# System.out.println("ti.name = " + ti.getName());
# System.out.println("ti.actor = " + ti.getActorId());
# if(pass){
# ti.end("部门经理审批通过");
# }else{
# ti.end("部门经理驳回");
# }
# }
# }finally{
# jbpmContext.close();
# }
通过JbpmContext对象,通过指定某个操作者,从数据库获取指定操作者的任务集合,即可获取同一流程实例的前一阶段信息,维护流程的运行。(不同于上面介绍的第一种:简单流程测试案例)
List<TaskInstance> taskInstances = jbpmContext.getTaskList("DepartmentManager");
总的操作过程是:
即在创建流程实例后,设置分配的操作者,保存实例;-->以后,都是通过上面的操作者获取任务实例,进行处理.不断循环。
注意,在每次的JbpmContext对象使用完毕后,一定要执行jbpmContext.close(),其本质是要释放数据库的操作连接。