jbpm实战讲解


jbpm实战讲解 作者: AngelAndAngel http://duyunfei.iteye.com 里面有许多内容是根据jbpm工作流应用开发指南提炼出来的,原书感觉实用性不 是太强,而且有的操作说明不太利于理解,所以此电子书是用自己的语言组织起来 的。 http://www.iteye.com - 做最棒的软件开发交流社区 第 1 / 53 页 本书由ITeye提供的电子书DIY功能自动生成于 2011-07-16 目 录 1. jbpm 1.1 jbpm学习笔记(一)ant构建以及数据库环境的配置 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.2 jbpm学习笔记(二)MyEclipse整合Jbpm配置,生成简单流程定义文件 . . . . . . . . . . . . . . . . . . . . . .6 1.3 jbpm学习笔记(三)部署jbpm流程定义(ant,api) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.4 jbpm学习笔记(四) 模拟借款申请以及各级审批 熟悉各种服务API调用 . . . . . . . . . . . . . . . . . . . . .12 1.5 jbpm学习笔记(五) jbpm不同服务的详细介绍以及使用 走完流程整个生命周期 . . . . . . . . . . . . . .23 1.6 jbpm学习笔记(六) jbpm的核心 jpdl流程定义语言详解(一) . . . . . . . . . . . . . . . . . . . . . . . . . . .31 1.7 jbpm学习笔记(七) fork-join活动详解以及示例,对处理流程的一点想法 . . . . . . . . . . . . . . . . . . .39 1.8 jbpm学习笔记(八) task活动讲解之任务分配,候选,自定义任务分配处理器 . . . . . . . . . . . . . . .44 1.9 jbpm学习笔记(九) task活动之泳道的概念 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .52 http://duyunfei.iteye.com 第 2 / 53 页 1.1 jbpm学习笔记(一)ant构建以及数据库环境的配置 发表时间: 2011-01-03 关键字: JBPM, Ant, 配置管理, 数据结构, JBoss 做jbpm一段时间,但是一直没来得及记下点什么,今天开始写点jbpm4.x的笔记。 首先介绍一下jbpm的一些理论知识。(参考jbpm4工作流应用开发指南) 企业信息化是每个现代企业构建强有力管理或生产后盾的一个基本途径,普通的流程已经不能满足复杂的企 业应用,工作流逐渐流行起来,特别是在ERP(企业资源计划),CRM(客户关系管理),EAI(企业应用集成)等企业 应用中,发挥了不可估量的作用,当然,互联网领域也有着很多类似的应用。 传统的工作流解决方案已经阻碍开发者的脚步,那么工作流引擎成了统一步伐的发令枪。 JBoss的jbpm可以说是现在最为流行的一种java工作流业务流程管理框架,是一个可扩展,灵活的能够实现 工作流/业务流程管理的企业级开发框架,提供了流程定义,流程部署,流程执行,流程管理等功能。 前段时间一个前同事说学习jbpm时候吃了很多亏,倒腾了几天还在报错,我现在就从最开始怎样配置,怎样 部署,一步步的记录jbpm的开发过程,也算是我再熟悉一下了(现在脑子想的事儿多,一时半会容易出现 blank,好记性不如烂键盘!)。 一,这个东东虽然是jboss的,但是也支持tomcat的。你可以下载tomcat6或者jboss5,我建议先下载 jbpm4.4,然后解压,解压后的文件夹就是你的jbpm工作目录,我就用${jbpm.home}来表示,然后下载一个 jboss-5.1.0.GA.zip,并且把这个压缩文件,直接拷贝到${jbpm.home}/install/downloads下面去(其实tomcat 是一样的道理)。你可以不拷贝进去,但是你不拷贝的话,等你ant的时候,它会自动的在网上下载这个版本的 jboss,万能了ant啊(后面会提到)。 二,假如你不了解ant的话,我建议你看看我的另外一篇博文http://duyunfei.iteye.com/blog/857675,假 如你没时间看,请继续。 ant说简单点,是一个构建部署程序的一把利剑,由于是基于java的开源产品,所以具有良好的移植性和易用 性。首先,你下载一个最新ant版本,然后下载后解压,设置环境变量 ANT_HOME=E:\ant1.8.2,Path后面加 上;E:\ant1.8.2\bin,启动cmd然后ant,出现build.xml does not exist! 成功。 此时你打开命令控制台,即cmd,进入目录${jbpm.home}/install,运行脚本:ant demo.setup.jboss, ok,别看这两步,它帮你完成了这些操作: 1,把jboss安装到${jbpm.home}的jboss-5.1.0.GA下。 2,把jbpm安装到jboss中。 3,安装HSQLDB,并在后台启动。 4,创建数据库表结构。 5,在后台启动jboss,你可以用http://localhost://8080访问,会出现jboss的首页。 6,根据示例(来自examples目录)创建一个examples.bar 业务流程归档,并把它发布到jbpm数据库中。 7,从${jbpm.home}/install/src/demo/example.identities.sql初始化用户和组。 有的书中说还把eclipse安装到${jbpm.home}/eclipse中,启动eclipse,安装jbpmweb控制台,安装 signavio web设计器,但是我这人 http://duyunfei.iteye.com 1.1 jbpm学习笔记(一)ant构建以及数据库环境的配置 第 3 / 53 页 貌似没这些。 此时你可以使用GPD(图形化流程设计器)这个机遇eclipse的客户端软件区进行流程建模,如何安装待会儿再 讲,或者你可以通过 Signavio web设计器进行流程建http://localhost:8080/jbpmeditor/p/explorer,这时候,也可以使用 jbpm控制台 http://localhost:8080/jbpm-console/,用下列用户之一进行登录(用户名/密码): alex/password,mike/password,peter/password,mary/password ant可以为我们办到很多事情,比如下载安装eclipse,jboss,tomcat等,但是惟独没有帮我们下载并安装 数据库软件,如果要成功运行jbpm,你必须要修改一些配置文件,例如数据库对应的配置文件,在目录 ${jbpm.home}/install/jdbc中,这个目录列出了jbpm官方支持数据库类型的相应配置,根据你对数据库的选 择配置相应的properties文件。例如mysql.properties的内容如下 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/jbpmdb jdbc.username=jbpm jdbc.password=jbpm oracle.properties的内容如下 jdbc.driver=oracle.jdbc.driver.OracleDriver jdbc.url=jdbc:oracle:thin:@localhost:1521:jbpmdb jdbc.username=jbpm jdbc.password=jbpm 在install的build.xml文件中database可以自定义,默认是HSQLDB,可选值是mysql,oracle,postgresql, 在ant的时候jboss.version可以指定为5.0.0GA或者5.0.0GA,jbpm4.3的默认是前者,jbpm4.4默认是后者,我 们按照高版本来。如果要自定义这些参数值,可以在运行ant安装脚本时使用 -D指令,如: ant -Ddatabase=postgresql demo.set.jboss,假如你刚才已经执行过了,现在务必把jboss的服务关了, 而且得把mysql服务打开,并确保mysql.properties中的链接语句是正确的,才能重新执行成功。 执行成功后,你可以在mysql数据库中看到jbpmdb下面建立了许多表。在这儿不列出来了。 在这里提到一个重要的概念,执行ant的时候,会把jbpm安装到Jboss应用服务器中,这回把jbpm安装成一 个jboss的服务,因此这台jboss上所有应用程序都可以使用这个安装的jbpm流程引擎。 在上面我们提到 Signavio 这个东西,它是基于web的流程设计器,输入 http://localhost:8080/jbpmeditor/p/explorer,可以进入管理页面。 配置它比较简单,大多数参数在web.xml中修改即可,你可以在jbpmeditor.war/WEB-INF/目录中找到, fileSystemRootDirectory很重要, 这个参数的值必须为一个物理上存在的本地目录,它指定了流程定义文件(*.jpdl.xml文件)存储的位置,比如我 的这个参数显示的是 http://duyunfei.iteye.com 1.1 jbpm学习笔记(一)ant构建以及数据库环境的配置 第 4 / 53 页 Filesystem directory that is used to store models fileSystemRootDirectory F:/jbpm-4.4/signavio-repository 以上是一些最初级的配置,由于篇幅较长,下篇继续讲解,并且演示出一个简单的例子。 http://duyunfei.iteye.com 1.1 jbpm学习笔记(一)ant构建以及数据库环境的配置 第 5 / 53 页 1.2 jbpm学习笔记(二)MyEclipse整合Jbpm配置,生成简单流程定义文件 发表时间: 2011-01-04 关键字: JBPM, Myeclipse, Eclipse, Ant, JBoss 使用jbpm时,有一个东东叫做图形化流程设计器,即Graph Process Designer(GPD),是用户能够通过图形 拖拽,属性设置等可视化的方式进行业务流程设计,建立并展现业务流程模型。 这个模型在jbpm4中一般为.jpdl.xml文件,遵循jpdl规范,此文件即“流程定义”文件,在运行时由工作流引擎 解释执行,生成“流程实例”。 下面我们看看怎样用eclipse集成GPD,由于jbpm4 gpd集成eclipse有版本的限制,即eclipse3.5.x,所以我 一咬牙就下了个myeclipse8.5,对应的eclipse版本为Eclipse 3.5.2,最开始以 为这个版本肯定很慢,等装了之后,发觉部署和启动,都比6.0快,还不错。 myeclipse8.5的软件更新和前几个版本不太相同,首先Help-->Myeclipse Configuration Center,进去之 后,点击SoftWare,然后Add from Archive File,选择 ${jbpm.home}/install/src/gpd/jbpm-gpd-site.zip文 件,取个名字如 jbpmgpd,展开节点,点击右键,Add to Profile(不要带source的),最后点击Apply change,此时打开 window- perferences里面会有一个jbpmgpd的栏目,重启OK。(这几步骤容易出错,小心行事,我试了一下,最好先把 jboss配置好,在点击apply change的时候看看个数是否正确)。 成功后,会在window-->Preferences中看到 JBoss jBPM这个栏目。然后选择Runtime Locations来配置运 行环境,点击add,输入名称如jbpm44,然后选择jbpm安装目录,ok。 接下来为你的工作空间定义一个jbpm用户库(User Libraries),它可以被用来饮用jBPM的所有依赖库文件。 如果你新建一个jBPM工程,只需将这个jBPM用户库添加到build path下即可。 1,选择 window-->Preferences 2,选择java -Build Path-User Libraries选项,单击New,输入名称 jBPM Libraries. 3,单击add jars,找到jbpm安装目录下的lib目录。 4,选择lib目录下的所有jar文件,并单击open按钮。 5,选中刚才新建的jBPM Libraries,重新单击add jars,在jbpm的安装目录下选择jbpm.jar文件 6,单击open 7,在jbpm.jar下选中Sourceattachment。 8,单击edit,在 Source Attachement Configuration对话框中,单击External Folder按钮。 9,找到jBPM安装目录下的src目录。 10,单击choose按钮,为jbpm。jar关联源代码。 11,单击两次ok按钮关闭所有对话框,搞定。 添加jpdl4 schema效验,就想刚才说的jbdl是jbpm独有的流程定义语言,它以xml文件的形式描述业务流 程。由于jbpm官方提供的图形化流程设计器功能不全面,因此很多情况下我们需要直 接编辑jpdl的xml源代码,所以,最好为jpdl xml指定Schema,这样,可以通过快捷键"Alt+/"快速呼出语法题 是,并帮你校验jpdl的语法错误。 在Eclipse中配置此Schema的过程是: http://duyunfei.iteye.com 1.2 jbpm学习笔记(二)MyEclipse整合Jbpm配置,生成简单流程定义文件 第 6 / 53 页 1,选择window-preferences,选择xml-->xml CataLog. 2,单击add,单击File System,然后选择${jbpm.home}/src/jpdl.xsd文件,ok,配置完成。 上面的整完之后呢,我们现在亲自动手,弄个程序跑跑,瞧好了。 在Jbpm4的软件包中,含有丰富的范例流程和测试代码,下面就将这些范例导入你的Eclipse种,成为一个 examples工程,供学习和研究,步骤如下 file-->import,然后选择${jbpm.home}下的examples,ok完成。 配置了jbpm4用户依赖库后,范例中所有的单元测试类(都继承了JbpmTestCase)都可以作为Junit test运行 了,在各个测试类上选择 run as-->JUnit Test命令即可。 运行几个看看吧。 当然,万能的ant也可以来帮你发布程序。首先,选择window-->show view-->other-->Ant-->Ant命令, 打开ant试图;然后,将范例工程中的ant构建文件build.xml,从包视图拖拽到ant 视图,即可使用其中的ant构建任务(target),来发布范例流程到目标服务器上,关于部署流程的细节,以后再 讲。 现在,我们将设计一个最简单的流程定义 "HelloAfei"。 如下: 新建jbpm4 Process Definition,然后可以通过如下方式拖拽: [img]http://dl.iteye.com/upload/picture/pic/79019/2d6bb66a-3541- 35c3-b1ed-cfb47c690048.png[/img] 点击source查看源码,很简单的。 http://duyunfei.iteye.com 1.2 jbpm学习笔记(二)MyEclipse整合Jbpm配置,生成简单流程定义文件 第 7 / 53 页 1.3 jbpm学习笔记(三)部署jbpm流程定义(ant,api) 发表时间: 2011-01-08 关键字: JBPM, Ant, 单元测试, junit, 工作 在第一篇中我讲过怎样用ant命令安装部署jbpm,ant确实很强大,减轻了我们很多工作,假如你已经成功安 装了jbpm(用${jbpm.home}表示安装的根目录),那么请看根目录下的\examples\target中有个 examples.jar,这个文件在jbpm中表示一个“业务流程归档”,它存在的意义是什么呢。 当我们的业务流程设计开发完毕后,会有很多的相关文件散着在,比如什么,定义流程的jpdl文件,流程图 片文件,人机交互的表单页面,java类文件,等等,我们假如一个个滴手动往服务器上部署,可以,但是会累死 你,所以,jbpm4支持将流程定义以及其相关资源打包成一个jar(java归档)格式的文件,部署到服务器上。 那么jbpm4工作流引擎就提供了一个基于ant任务的api来部署业务流程归档叫做JbpmDeployTask,它不仅可 以部署单个业务流程归档,也可以部署一组业务流程归档到服务器上。它通过读取jbpm.cfg.xml中的JDBC数据 连接信息直接将业务流程归档部署到数据库中。在使用之前,确保你的数据库服务器正在运行。 由于在之前我们已经ant过了,所以examples.jar就直接生成了,当然数据库也已经部署了。 这里我们看一下jbpm是怎样处理这个业务流程归档的,首先它扫描业务流程归档中所有以.jpdl.xml结尾的文 件,并解析,然后用来发起流程实例,业务流程归档中所有其他资源也会在部署过程中被持久化到数据库中。 所有这些资源被统一编号保存在数据库表jbpm4_lob中,可以查看一下。我们可以通过jbpm4提供的 RepositoryService.getResourceAsStream API随时访问这些资源。 在这里,先写点api的知识,你可以通过api来进行部署工作,如下 package testmain; import org.jbpm.test.JbpmTestCase; /** * * @author 阿飞哥 JbpmTestCase继承了Junit的TestCase类,是jbpm4对Junit框架 * 的扩展 */ public class Test extends JbpmTestCase { http://duyunfei.iteye.com 1.3 jbpm学习笔记(三)部署jbpm流程定义(ant,api) 第 8 / 53 页 // 这个成员域为单元测试保存流程定义的部署ID String deploymentId; // 一般在单元测试的初始化方法(setUp)中,执行流程定义的部署工作 // 这是jBPM单元测试的约定,在后面的单元测试代码中,都将默认执行此约定 @Override protected void setUp() throws Exception{ super.setUp(); /* * repository * 1.存放处, 储藏室 * 2.仓库;宝库 * 3.学识渊博的人;智囊;知识宝典 */ //使用RepositoryService提供的API方法从classpath中部署流 //程定义 deploymentId=repositoryService.createDeployment ().addResourceFromClasspath ("my_process_define.jpdl.xml").deploy(); //当然,在这里可以多次调用addResourceFromClasspath方法, //将流程定义的其他资源都部署到数据库中 } //一般在单元测试的结束方法(tearDown)中,执行删除流程定义部署的工作 @Override protected void tearDown() throws Exception{ //调用下面方法,将物理清楚deploymentId对应的流程定义及其所有相关资 //源,并关联清楚基于此流程定义的流程实例, //活动实例,任务,历史流程实例等所有运行时以及历史的流程实体记录 repositoryService.deleteDeploymentCascade(deploymentId); super.tearDown(); http://duyunfei.iteye.com 1.3 jbpm学习笔记(三)部署jbpm流程定义(ant,api) 第 9 / 53 页 } } JbpmTestCase作为jbpm4框架的单元测试超类,为我们做了如下6个流程引擎服务的初始化工作 Protected static ProcessEngine processEngine=null; //资源库服务 Protected static RepositoryService repositoryService; //执行服务 Protected static ExecutionService executionService; //管理服务 Protected static ManagementService managementService; //任务服务 Protected static TaskService taskService; //历史服务 Protected static HistoryService historyService; //身份认证服务 Protected static IdentiryService identiryService; JbpmTestCase在其initialize方法中式这样初始化上面的各种引擎服务的: If(processEngine=null){ //根据默认配置,生成工作流引擎对象 processEngine=Configuration.getProcessEngine(); //利用工作流引擎对象,获取6个流程引擎服务 repositoryService=processEngine.get(Repository.class); .…… …… http://duyunfei.iteye.com 1.3 jbpm学习笔记(三)部署jbpm流程定义(ant,api) 第 10 / 53 页 } 这篇主要讲了通过ant和调用jbpmapi方法发布到jbpm的数据库持久化环境中,使得流程定义得到保存,为 后续的流程实例化运行提供模板基础。 下一篇将介绍一个在网上看到的例子 我觉得挺不错的,讲解一下api的一些使用,以便于完整的讲解各种API服 务。 http://duyunfei.iteye.com 1.3 jbpm学习笔记(三)部署jbpm流程定义(ant,api) 第 11 / 53 页 1.4 jbpm学习笔记(四) 模拟借款申请以及各级审批 熟悉各种服务API调用 发表时间: 2011-01-09 关键字: JBPM, 活动, 单元测试, XML, junit 首先,我们来思考一个这样的逻辑,如下图, [img] http://dl.iteye.com/upload/picture/ pic/79595/dd99a470-0acd-3bd4-b51c-ba9458c97792.png [/img] 用文字描述是这样的:公司员工在申请借款时,首先填写借款申请,然后部门经理审批,不通过,则取消,若 通过,并且金额《5000时,由财务拨款,完成申请,假如金额》=5000,则由总经理审批,通过,则由财务拨 款,完成申请,不通过,取消。 通过这个流程图,我们设计jbpm流程定义文件(jbdl),实际上 上面的图就是GPD工具(图形化流程设计器)生成 的,源码如下: Loan.jpdl.xml: http://duyunfei.iteye.com 1.4 jbpm学习笔记(四) 模拟借款申请以及各级审批 熟悉各种服务API调用 第 12 / 53 页 (注:最开始我的name全部写的中文,发觉在运行的时候会报一个错,改成字母就好了) 有的人比较模糊task和state的区别:task有分配,state没有。 在编码过程中,我们经常会用到jbpm API服务帮我们“走”流程,上一篇中说过,jbpm中JbpmTestCase是作 为jbpm4框架的单元测试超类出现的,而且帮我们初始化了如下6个流程引擎服务: //资源库服务 Protected static RepositoryService repositoryService; //执行服务 Protected static ExecutionService executionService; //管理服务 Protected static ManagementService managementService; //任务服务 http://duyunfei.iteye.com 1.4 jbpm学习笔记(四) 模拟借款申请以及各级审批 熟悉各种服务API调用 第 13 / 53 页 Protected static TaskService taskService; //历史服务 Protected static HistoryService historyService; //身份认证服务 Protected static IdentiryService identiryService; 这些比较常用 package org.test; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jbpm.api.ProcessInstance; import org.jbpm.api.history.HistoryProcessInstance; import org.jbpm.api.task.Task; import org.jbpm.test.JbpmTestCase; /** * 模拟借款 * * @author 阿飞哥 * */ public class LoanTest extends JbpmTestCase { String deploymentId; @Override protected void setUp() throws Exception { super.setUp(); deploymentId = repositoryService.createDeployment() http://duyunfei.iteye.com 1.4 jbpm学习笔记(四) 模拟借款申请以及各级审批 熟悉各种服务API调用 第 14 / 53 页 .addResourceFromClasspath("org/test/loan.jpdl.xml").deploy(); } @Override protected void tearDown() throws Exception { repositoryService.deleteDeploymentCascade(deploymentId); super.tearDown(); } /************ 测试例子 ***********/ /** * 张山 申请4000元,部门经理驳回 */ /*public void test1(){ String curUser = "张山"; //创建流程实例 ProcessInstance pi = startLoanProcess(curUser); String piId = pi.getId(); fillOutApplication(piId,curUser,4000); curUser = "manager"; //部门经理不同意 managerCheck(piId,curUser,false,"最近资金紧张); pi = executionService.findProcessInstanceById(piId); assertNull(pi);// 结束 HistoryProcessInstance hpi = getHistoryProcessInstanceById(piId); // 流程结束状态是cancel , 取消 assertEquals(hpi.getState(), "cancel"); }*/ /** * 李四 申请4000元,部门经理同意 */ public void test2(){ String curUser="李四"; //创建流程实例 http://duyunfei.iteye.com 1.4 jbpm学习笔记(四) 模拟借款申请以及各级审批 熟悉各种服务API调用 第 15 / 53 页 ProcessInstance pi= startLoanProcess(curUser); String piId=pi.getId(); fillOutApplication(piId,curUser,4000); curUser="manager"; managerCheck(piId,curUser,true,"同意");//部门经理同意 pi=executionService.findProcessInstanceById(piId); assertNotNull(pi);//流程未结束 /** * pi.findActiveExecutionIn("financeGet")的意思是 在给出的活动名中查询处于活动的可执行活动,假如没有则返回 null */ assertNotNull(pi.findActiveExecutionIn("financeGet"));//财务拨款处于活动状态 /* * Set activityNames=pi.findActiveActivityNames(); * for(Iterator it=activityNames.iterator();it.hasNext();){ System.out.println(it.next()); } *可以看看执行活动名 */ curUser="finance"; earmark(piId,curUser,"我拨款了 哈哈"); pi=executionService.findProcessInstanceById(piId); assertNull(pi);//流程结束 HistoryProcessInstance hpi=getHistoryProcessInstanceById(piId); //流程结束状态是end,正常结束 assertEquals(hpi.getState(),"ended"); } http://duyunfei.iteye.com 1.4 jbpm学习笔记(四) 模拟借款申请以及各级审批 熟悉各种服务API调用 第 16 / 53 页 /** * wang wu申请6000元,部门经理同意,总经理驳回 */ public void test3(){ String curUser="wang wu"; //创建流程实例 ProcessInstance pi= startLoanProcess(curUser); String piId=pi.getId(); fillOutApplication(piId,curUser,6000); curUser="manager"; managerCheck(piId,curUser,true,"同意");//部门经理同意 pi=executionService.findProcessInstanceById(piId); assertNotNull(pi); //既然部门经理同意,而且money>5000,那么必须得有总经理审批 assertNotNull(pi.findActiveExecutionIn("ceoCheck")); curUser="ceo"; ceoCheck(piId,curUser,false,"不同意");//总经理驳回 pi=executionService.findProcessInstanceById(piId); assertNull(pi);//流程结束 HistoryProcessInstance hpi=getHistoryProcessInstanceById(piId); //流程结束状态是cancel,取消 assertEquals(hpi.getState(),"cancel"); } /** * afei 申请6000,部门经理同意,总经理同意 */ public void test4(){ String curUser="afei"; //创建流程实例 http://duyunfei.iteye.com 1.4 jbpm学习笔记(四) 模拟借款申请以及各级审批 熟悉各种服务API调用 第 17 / 53 页 ProcessInstance pi= startLoanProcess(curUser); String piId=pi.getId(); fillOutApplication(piId,curUser,6000); curUser="manager"; //部门经理同意 managerCheck(piId, curUser, true,"manager ok"); pi=executionService.findProcessInstanceById(piId); assertNotNull(pi); assertNotNull(pi.findActiveExecutionIn("ceoCheck"));//总经理审批处于活动状态 curUser="ceo"; ceoCheck(piId, curUser, true, "ceo ok");//总经理同意 pi=executionService.findProcessInstanceById(piId); assertNotNull(pi); assertNotNull(pi.findActiveExecutionIn("financeGet"));//财务拨款处于活动状态 curUser="finance"; earmark(piId, curUser, "拨款"); pi=executionService.findProcessInstanceById(piId); assertNull(pi);//流程结束 HistoryProcessInstance hpi=getHistoryProcessInstanceById(piId); //流程结束状态是ended,正常结束 assertEquals(hpi.getState(),"ended"); } /*** 其他方法 ***/ /** * 用户发起借款申请 */ public ProcessInstance startLoanProcess(String user) { Map valMap = new HashMap(); valMap.put("proposer", user); //创建流程实例 http://duyunfei.iteye.com 1.4 jbpm学习笔记(四) 模拟借款申请以及各级审批 熟悉各种服务API调用 第 18 / 53 页 return executionService.startProcessInstanceByKey("loan", valMap); } /** * 填写借款说明 * * @param piId * @param user * @param money * @throws Exception */ public void fillOutApplication(String piId, String user, double money){ List tasks = taskService.findPersonalTasks(user);//获取任务列表 Task filloutTask = null; for (Task task : tasks) { if (task.getExecutionId().equals(piId)) { filloutTask = task;//匹配任务 break; } } System.out.println("piId: "+piId); assertNotNull(filloutTask);//任务不为空 Map valMap = new HashMap(); valMap.put("money", money);//申请金额 //设置变量参数 executionService.setVariables(piId, valMap); /* * 删除这个任务,标记相关任务为完成, * 假如任务在流程执行的上下文中被创建, * 这个操作可能导致一个流程实例被触发 * (这是我自己翻译的) * */ taskService.completeTask(filloutTask.getId()); } http://duyunfei.iteye.com 1.4 jbpm学习笔记(四) 模拟借款申请以及各级审批 熟悉各种服务API调用 第 19 / 53 页 /** * 部门经理审批 * * @param piId * @param user * @param isPass * @param comment * @throws Exception */ public void managerCheck(String piId, String user, boolean isPass, String comment) { List tasks = taskService.findPersonalTasks(user); Task checkTask = null; for (Task task : tasks) { if (task.getExecutionId().equals(piId)) { checkTask = task; break; } } assertNotNull(checkTask); taskService.addTaskComment(checkTask.getId(), comment); taskService.completeTask(checkTask.getId(), isPass ? "managerPass" : "managerNotPass"); } /** * 总经理审批 * * @param piId * @param user * @param isPass * @param comment * @throws Exception */ public void ceoCheck(String piId, String user, boolean isPass, http://duyunfei.iteye.com 1.4 jbpm学习笔记(四) 模拟借款申请以及各级审批 熟悉各种服务API调用 第 20 / 53 页 String comment){ List tasks = taskService.findPersonalTasks(user); Task checkTask = null; for (Task task : tasks) { if (task.getExecutionId().equals(piId)) { checkTask = task; break; } } assertNotNull(checkTask); taskService.addTaskComment(checkTask.getId(), comment); taskService.completeTask(checkTask.getId(), isPass ? "ceoPass" : "ceoNotPass"); } /** * 财务拨款 * * @param piId * @param user * @param comment */ public void earmark(String piId, String user, String comment) { List tasks = taskService.findPersonalTasks(user); Task earTask = null; for (Task task : tasks) { if (task.getExecutionId().equals(piId)) { earTask = task; break; } } assertNotNull(earTask); taskService.addTaskComment(earTask.getId(), comment); String proposer = (String) executionService.getVariable(piId, "proposer"); // 通知借款申请人 http://duyunfei.iteye.com 1.4 jbpm学习笔记(四) 模拟借款申请以及各级审批 熟悉各种服务API调用 第 21 / 53 页 System.out.println("hello:" + proposer + ",财务已经给你拨款了,请查收"); taskService.completeTask(earTask.getId()); } /** * 得到流程实例历史 * @param piId * @return */ public HistoryProcessInstance getHistoryProcessInstanceById(String piId) { return historyService.createHistoryProcessInstanceQuery() .processInstanceId(piId).uniqueResult(); } } 再次说明,此测试类继承JbpmTestCase,很多service服务都已经初始化好了,直接run junit就ok了,它会把 所有test开头的类当做测试用例执行下去,假如测试出有误,那么会给你报出来。这个例子主要是稍微熟悉一下 service api,主要是对各种情况的判断或流转有一个清晰地认识。 http://duyunfei.iteye.com 1.4 jbpm学习笔记(四) 模拟借款申请以及各级审批 熟悉各种服务API调用 第 22 / 53 页 1.5 jbpm学习笔记(五) jbpm不同服务的详细介绍以及使用 走完流程整个生命周 期 发表时间: 2011-01-10 关键字: JBPM, 活动, XML, 搜索引擎, 单元测试 上一篇的示例中我们尝试用了jbpm Service API,现在我们仍然详细介绍API。 一, 流程引擎对象,ProcessEngine是jbpm4所有Service API之源 在jbpm中各种服务相互依存,但所有的service API都从ProcessEngine中获得,它是由 Configuration类构建的,即工作流引擎根据配置产生。 ProcessEngine是线程安全的,因此它可以保存在静态变量中,甚至JNDI命名服务 中或者其他重要位置。在应用中,所有线程和请求都可以使用同一个ProcessEngine对象, 以下代码告诉您如何获得ProcessEngine: ProcessEngine processEngine=Configuration.getProcessEngine(); 上面代码中的Configuration使用了classpath根目录下的默认配置文件jbpm.cfg.xml创 建一个ProcessEngine。如果你要自定义位置,那么可以这样做: ProcessEngine processEngine=new Configuration().setResource(“myjbpm.xml”) .buildProcessEngine(); 然后,那6个service直接可以用processEngine.getXXX()得到。下面把这6个service描述一下: RepositoryService—流程之源服务的借口。提供对流程定义的部署,查询,删除等操作。 ExecutionService—流程执行服务的借口。提供启动流程实例,“执行”推进,设置流程变量等操作 http://duyunfei.iteye.com1.5 jbpm学习笔记(五) jbpm不同服务的详细介绍以及使用 走完流程整个生命周期 第 23 / 53 页 ManagementService—流程管理控制服务的接口,提供异步工作(Job)相关的执行和查询操作。 TaskService—人工任务服务的接口。提供对任务(Task)的创建,提交,查询,保存,删除等操作。 HistoryService—流程历史服务的接口。提供对流程历史库(即已完成的流程实例归 档)中历史流程实例,历史活动实例等记录的查询操作。还提供诸如某个流程定义中所有活动 的平均持续时间,某个流程定义中某转移的经过次数等数据分析服务。 IdentityService—身份认证服务的接口。提供对流程用户,用户组以及组成员关系的相关服务 二, 发起一个流程实例有这样几种方式 1, 常规方法: ProcessInstance processInstance=executionService.startProcessInstanceByKey(“order”); 2, 指定业务键发起流程实例,假如要对特定的业务实例来说,我们需要将每个流程 实例和一个独特的业务实例关联起来,比如一个保险流程的实例必然需要和一个保险单号关 联,一个订单流程的实例必然需要和一个订单号关联,以便业务上的查询或索引,那么我们就 可以通过为新启动的流程实例分配一个业务键来做到。一个业务键必须在此流程定义所有版本 的流程实例范围内是唯一的。比如以下: ProcessInstance processInstance=executionService.startProcessInstanceByKey(“order”,”afei520”); 在上面种,afei520就是业务键,和特定的业务有关系。 注:最好指定一个业务键发起流程实例,一般情况下,在业务应用中,找到这样的业务键并不 http://duyunfei.iteye.com1.5 jbpm学习笔记(五) jbpm不同服务的详细介绍以及使用 走完流程整个生命周期 第 24 / 53 页 困难,提供一个业务键的好处是可以根据业务来执行流程实例搜索,这是一个最佳实践。 3, 指定变量发起流程实例 如果新的流程实例需要一些输入参数启动,可以将这个参 数放在流程变量里,然后在发起流程时传入流程变量对象。代码如下: Map valMap = new HashMap(); valMap.put("proposer", user); //创建流程实例 return executionService.startProcessInstanceByKey("loan",valMap); 三, 任务服务API,这里的任务是指jbpm task活动产生的人机交互业务。假如要展 示一个人的任务列表: List tasks=taskService.findPersonalTasks(“afei”); 一般来说,任务会有一个表单,显示在一些用户界面中如jsp。这个表单需要读写与任务相关的流程数据, 一般是通过任务变量的方式。如下代码: String taskId=taskId; Set varis=taskService.getVariableNames()taskId; //读取任务变量 Set variableNames=taskService.getVariableNames(checkTask.getId()); for(Iterator it=variableNames.iterator();it.hasNext();){ System.out.println("task vari:"+it.next()); } Map variables=taskService.getVariables(checkTask.getId(),variableNames); http://duyunfei.iteye.com1.5 jbpm学习笔记(五) jbpm不同服务的详细介绍以及使用 走完流程整个生命周期 第 25 / 53 页 System.out.println("variables:"+variables); 或自行创建variables=new HashMap(); //设置”键值”形式的任务变量 Variables.put(“username”,”afei”); Variables.put(“age”,111111); //将变量存入任务 taskService.setVariables(taskId,variables); 四,  对于流程,任务,历史的查询是很重要的,从jbpm4开始,流程查询系统由一 组新的API来支持,这组API可以覆盖到大多数您能想到的查询。 比如流程实例查询: List results=executionService. createProcessInstanceQuery(). processDefinitionId(“my_process_definition”).//流程定义Id notSuspended().//未挂起 Page(0,50).list();//分页 比如任务的查询: List tasks=taskService.createTaskQuery(). processInstanceId(piId).//流程实例 assignee(“Alex”).分配给Alex的任务 page(100,200).//分页 orderDesc(TaskQuery.PROPERTY_DUEDATE). //根据日期逆向排序 List(); 其他的服务以此类推 http://duyunfei.iteye.com1.5 jbpm学习笔记(五) jbpm不同服务的详细介绍以及使用 走完流程整个生命周期 第 26 / 53 页 五, 下面看一个例子。 首先angel.jpdl.xml代码 Java测试代码: package org.test; import org.jbpm.api.Execution; import org.jbpm.api.ProcessInstance; import org.jbpm.api.history.HistoryProcessInstance; import org.jbpm.api.history.HistoryTask; import org.jbpm.api.task.Task; import org.jbpm.test.JbpmTestCase; public class AngelTest extends JbpmTestCase { http://duyunfei.iteye.com1.5 jbpm学习笔记(五) jbpm不同服务的详细介绍以及使用 走完流程整个生命周期 第 27 / 53 页 String deploymentId; @Override public void setUp() throws Exception { super.setUp(); deploymentId = repositoryService.createDeployment() .addResourceFromClasspath("org/test/angel.jpdl.xml").deploy(); } @Override public void tearDown() throws Exception { repositoryService.deleteDeploymentCascade(deploymentId); super.tearDown(); } public void testAfei() { System.out.println("********testAfei start********"); // 使用执行服务:根据已部署流程定义的名称angel,发起流程实例 ProcessInstance pi = executionService .startProcessInstanceByKey("angel"); // 获取流程实例ID String pid = pi.getId(); // 获取当前活动的执行对象 Execution executionState = pi.findActiveExecutionIn("state"); // 断言当前活动即为state assertNotNull(executionState); // 使用执行服务:发出执行信号结束当前活动,继续流程的执行 executionService.signalExecutionById(executionState.getId()); // 使用执行服务:从持久化层中获取“最新”的流程实例对象 pi = executionService.findProcessInstanceById(pid); http://duyunfei.iteye.com1.5 jbpm学习笔记(五) jbpm不同服务的详细介绍以及使用 走完流程整个生命周期 第 28 / 53 页 Execution executionTask = pi.findActiveExecutionIn("task"); // 断言当前活动即为task assertNotNull(executionTask); // 使用任务服务:获取用户afei的任务,即task活动产生的任务 Task task = taskService.findPersonalTasks("afei").get(0); // 使用任务服务:完成任务 taskService.completeTask(task.getId()); // 使用历史服务:创建历史任务查询 HistoryTask historyTask = historyService.createHistoryTaskQuery() .taskId(task.getId()).uniqueResult(); // 断言上一步完成的任务已成为历史,即可通过历史任务查询获取之 assertNotNull(historyTask); // 断言该流程实例已经结束 assertProcessInstanceEnded(pid); // 使用历史服务:创建历史流程实例查询 HistoryProcessInstance hpi = historyService .createHistoryProcessInstanceQuery().processInstanceId(pid) .uniqueResult(); //断言该流程实例已经成为历史,即可通过历史流程实例查询获取它 assertNotNull(hpi); //单元测试至此执行完毕 System.out.println("********testAfei end********"); } } 这个例子主要是进一步看看Service API的功能及其使用场景。这个例子基本上走完了一个流程的完整生命周 期,下一篇着重介绍Jbpm的核心————jPDL。 http://duyunfei.iteye.com1.5 jbpm学习笔记(五) jbpm不同服务的详细介绍以及使用 走完流程整个生命周期 第 29 / 53 页 http://duyunfei.iteye.com1.5 jbpm学习笔记(五) jbpm不同服务的详细介绍以及使用 走完流程整个生命周期 第 30 / 53 页 1.6 jbpm学习笔记(六) jbpm的核心 jpdl流程定义语言详解(一) 发表时间: 2011-01-12 关键字: JBPM, 活动, XML, 单元测试 jpdl是jbpm的最核心,涉及到各方面的内容,所以我打算用多篇博文记录它 一, Jpdl语言是以xml为表现形式的,根元素是process,我们看看process元素的一些属性 属性 类型 默认值 是 否 必 需 作用描述 Name 名称文 本 无 必 需 作为流程显示的标签 Key 字母, 数字, 下划线 的组合 如果省略,key会根据name生成, name中非字母以及非数字的字符会被 替换为下划线 可 选 用来标示不同的流程定义。拥有同一个key的流程 定义允许有不同的version。对于已发布的同一流 程定义的各个版本来说,“key:name”组合必需 是完全一样的 Version整型 同一流程定义(即二者 的”key:name”相同),后部署的会 比先部署的version加1,如果是第一 次部署,version从1开始 可 选 标示同一流程定义的不同版本 二,下面介绍流程控制活动,这些活动有: Start—开始活动 State—活动 Decision—判断活动 Fork—join----分支/聚合活动 End –结束活动 Task—人工任务活动 Sub-process—子流程活动 Custom—自定义活动 1, Start活动的意义在于指明一个流程的实例应该从哪里开始发起,即流程的入口,在一个流程定义里必需拥 有一个start活动。Start活动必须有一个流出转移(transition),这个转移会在流程通过start活动的时候执行。 2, http://duyunfei.iteye.com 1.6 jbpm学习笔记(六) jbpm的核心 jpdl流程定义语言详解(一) 第 31 / 53 页 State 当需要使用业务流程受到某些特定的外部干预后再继续运行,而在这之前流程”陷入”一个中断等待 的状态时,就需要这么一个state活动。当流程运行到state活动时,会自动陷入等待状态(waitting state),也就 是说流程引擎在收到外部触发信号之前会一直使流程实例在此state活动等待。从这个方面来说:task活动可以 说是一种特殊化的state活动。 State活动除了最基本的name属性和transition等元素外,没有独特的属性或元素。 相关jpdl如下: 下面我们编写代码使此流程运行起来,首先根据此流程定义发起实例: ProcessInstance processInstance=executionService.startProcessInstanceByKey(“StateChoice”); http://duyunfei.iteye.com 1.6 jbpm学习笔记(六) jbpm的核心 jpdl流程定义语言详解(一) 第 32 / 53 页 假如现在流程到达了 wait for response state活动,则实例会一直等待外部触发的出现。此state活动拥有 两个流出转移,外部触发调用通过提供不同的流出转移信号名称(signal name) 来实现选择流转,下面使用 accept作为信号名称: //获取流程执行的ID String executionId=processInstance.findActiveExecutionIn(“wait for response”).getId(); //触发accept型号 processInstance=executionService.signalExecutionById (executionId,”accept”); assertTrue(processInstance.isActive(“submit document”)); 3,decision 根据条件在多个流转路径中选择其一通过,也就是做一个决定性的判断,这时候使用 decision活动是个正确的选择。 Decision活动可以拥有多个流出转移,当流程实例到达decision活动时,会根据最先匹配成功的一个条件 自动地通过相应的流出转移。 (1),使用decision活动的condition 使用decision活动的condition元素 Decision活动中会运行并判断其每一个transition元素里的流转条件----流转条件由condition元素表示。当遇到 一个transition的condition值为true或者一个没有设置condition的transition,那么流程就立刻流向这个 transtion。 相关jpdl如下: http://duyunfei.iteye.com 1.6 jbpm学习笔记(六) jbpm的核心 jpdl流程定义语言详解(一) 第 33 / 53 页 在单元测试中,设置流程变量content的值为good: Map vals=new HashMap (); vals.put("content","good"); //发起流程实例 ProcessInstance processInstance=executionService.startProcessInstanceByKey("decisionConditions",vals); //那么,可以断言流程实例流向了submit活动 assertTrue(processInstance.isActive("submit")); http://duyunfei.iteye.com 1.6 jbpm学习笔记(六) jbpm的核心 jpdl流程定义语言详解(一) 第 34 / 53 页 (2),使用decision活动的expr属性 你可以利用decision活动本身具有的expr(表达式)属性来判断流程的转向,decision活动的expr属性值可直接返 回字符串类型的流出转移名称,指定需要流转的路径。 相关jpdl代码如下 在单元测试中,设置流程变量content的值为good: Map vals=new HashMap(); vals.put("content","good"); http://duyunfei.iteye.com 1.6 jbpm学习笔记(六) jbpm的核心 jpdl流程定义语言详解(一) 第 35 / 53 页 //发起流程实例 ProcessInstance processInstance=executionService.startProcessInstanceByKey("decisionExpr",vals); /那么,可以断言流程实例流向了submit活动 assertTrue(processInstance.isActive("submit")); (3),使用decision活动的handler元素 也许以上两种判断流转的方式对你来说还不够灵活,也许你需要在判断流转时计算大量,复杂的业务逻辑,那 么,自己实现判断处理接口,即通过decision handler的方式,将给你无限自由的空间。 首先,必须实现DecisionHandler接口,将流转判断的决定权委派给这个实现类,这个接口的定义如下: Public interface DecisionHandler{ //这个接口就只有一个方法,提供流程实例的执行上下文(execution)作为参数,需要返回字符串型的转移名称 String decide(OpenExecution execution); } 这个handler需要作为decision活动的子元素被配置。 相关的jpdl如下: http://duyunfei.iteye.com 1.6 jbpm学习笔记(六) jbpm的核心 jpdl流程定义语言详解(一) 第 36 / 53 页 ContentEvaluation这个类的代码如下: package org.test; import org.jbpm.api.jpdl.DecisionHandler; import org.jbpm.api.model.OpenExecution; public class ContentEvaluation implements DecisionHandler { @Override public String decide(OpenExecution execution) { String content = (String) execution.getVariable("content"); if (content.equals("you are good")) { return "good"; } if (content.equals("you are bad")) { return "bad"; } return "ugly"; } } 运行流程实例的单元测试代码如下: Map vals=new HashMap(); //设置流程变量content的值为 you are good vals.put("content","you are good"); //发起流程实例,并设置流程变量 http://duyunfei.iteye.com 1.6 jbpm学习笔记(六) jbpm的核心 jpdl流程定义语言详解(一) 第 37 / 53 页 ProcessInstance pi=executionService.startProcessInstanceByKey("decisionHandler",vals); //断言流向了预期的活动 assertTrue(pi.isActive("submit")); 现在有一个问题出现了,发觉了吗,decision活动和state活动都能实现条件流转,是的,但是这两种方式 有何区别,在实际业务应用中该选择何种方式呢? 区别如下: 如果decision活动定义的流转条件没有任何一个得到满足,那么流程实例将无法进行下去,会抛出异常。 如果state活动有多个流出转移,且同样没有任何一个得到满足,那么流程实例将流向state活动定义的第一条流 出转移,从而进行下去。 也就是说:decision活动具有更加严格的条件判断特性。 下篇讲解fork-join(分支/聚合活动)和end http://duyunfei.iteye.com 1.6 jbpm学习笔记(六) jbpm的核心 jpdl流程定义语言详解(一) 第 38 / 53 页 1.7 jbpm学习笔记(七) fork-join活动详解以及示例,对处理流程的一点想法 发表时间: 2011-01-13 关键字: 活动, JBPM, XML, 单元测试 今天继续学习jbpm活动元素之 fork-join(分支/聚合活动) 当我们需要流程并发(concurrency)执行的时候,就需要使用到fork-join活动组合,fork活动可以使流程在一 条主干上出现并行的分支,join活动则可以使流程的并行分支聚合成一条主干。 Fork活动仅具有jbpm活动的最基本特征,即具有1个name属性和n个流出转移元素。 相关jpdl如下: http://duyunfei.iteye.com 1.7 jbpm学习笔记(七) fork-join活动详解以及示例,对处理流程的一点想法 第 39 / 53 页 编写单元测试代码执行上面的流程定义 package org.test; import java.util.HashSet; import java.util.Set; import org.jbpm.api.ProcessInstance; import org.jbpm.test.JbpmTestCase; public class ForkAndJoinTest extends JbpmTestCase { String deploymentId; http://duyunfei.iteye.com 1.7 jbpm学习笔记(七) fork-join活动详解以及示例,对处理流程的一点想法 第 40 / 53 页 @Override public void setUp() throws Exception { super.setUp(); deploymentId = repositoryService.createDeployment() .addResourceFromClasspath("org/test/forkAndJoin.jpdl.xml") .deploy(); } @Override public void tearDown() throws Exception { repositoryService.deleteDeploymentCascade(deploymentId); super.tearDown(); } public void testForkAndJoin() { // 发起forkAndJoin流程实例 ProcessInstance pi = executionService .startProcessInstanceByKey("forkAndJoin"); String pid = pi.getId(); // 构造一个活动名称集合以验证分支。设置3个分支活动的名称 Set ballsName = new HashSet(); ballsName.add("pingpang"); ballsName.add("football"); ballsName.add("basketball"); // 断言当前活动即为产生的3个分支 assertEquals(ballsName, pi.findActiveActivityNames()); // 发出执行信号通过 "pingpang" 活动,这时候,流程会在最后的聚合活动"play"上等待其他分支的到来 String pingpangExecuId = pi.findActiveExecutionIn("pingpang").getId(); pi = executionService.signalExecutionById(pingpangExecuId); // 在活动名称集合中排除 "pingpang"活动 ballsName.remove("pingpang"); // 此时,仍然可以断言另外2个分支还在等待 http://duyunfei.iteye.com 1.7 jbpm学习笔记(七) fork-join活动详解以及示例,对处理流程的一点想法 第 41 / 53 页 assertNotNull(pi.findActiveExecutionIn("football")); assertNotNull(pi.findActiveExecutionIn("basketball")); // 发出执行信号通过剩下的其中一个分支--football活动 String footballExecuId = pi.findActiveExecutionIn("football").getId(); pi = executionService.signalExecutionById(footballExecuId); // 在活动名称集合中排除 "football"活动 ballsName.remove("football"); // 发出执行信号通过剩下的第二个分支--basketball活动 String basketballExecuId = pi.findActiveExecutionIn("basketball") .getId(); pi = executionService.signalExecutionById(basketballExecuId); // 在活动名称集合中排除"basketball"活动 ballsName.remove("basketball"); // size应该是0 System.out.println("ballsName.size(): " + ballsName.size()); // 断言通过了第一个聚合活动 "to play bigball",到达了"play bigball"活动 assertNotNull(pi.findActiveExecutionIn("play bigball")); //发出执行信号通过"play bigball"活动 String playBigBallExecuId=pi.findActiveExecutionIn("play bigball").getId(); pi=executionService.signalExecutionById(playBigBallExecuId); //最终的聚合活动"play over"等到了它的最后一个流入转移后,流向了end活动,所以流程实例结束 //因此可以断言此流程已经不存在了. assertNull("execution"+pid+"应该不存在了",executionService.findExecutionById(pid)); System.out.println("结束了"); } } Fork-join活动看似其实不难,但是当流程变多的时候,我感觉在写代码的时候容易弄混淆,比如,流程在某 http://duyunfei.iteye.com 1.7 jbpm学习笔记(七) fork-join活动详解以及示例,对处理流程的一点想法 第 42 / 53 页 个阶段执行到哪儿了,该怎样启动下一个流程,甚至有时候感觉有的state根本不需要,不知道其他人是否这样 的感觉,但是我要说的是,假如真的能够体会你自己业务的每一步骤的话,就不会出现这种混乱的思想,举个 简单的例子:假如有这样一个逻辑,某一个订单在发货后,1,客户要接收,2厂家要收款。这两个活动都完成 了,那么才能结束流程,首先,你应该这样去考虑它们的步骤:发订单——》客户接收——客户打钱——厂家 收款——销账(完成)。假如把这个过程再细化的话,首先发完订单,客户处于待接收状态,货到后客户可以 接收,就变成待打钱状态,此时厂家就处于待收款状态,厂家收款后,销账完成。而这个细化的过程即基于每 个状态点(如待接收状态),然后激起流程转移的动作(如接收),使我们真正在处理流程的过程。由于篇幅 原因,下一张再介绍end等活动。 http://duyunfei.iteye.com 1.7 jbpm学习笔记(七) fork-join活动详解以及示例,对处理流程的一点想法 第 43 / 53 页 1.8 jbpm学习笔记(八) task活动讲解之任务分配,候选,自定义任务分配处理 器 发表时间: 2011-01-16 关键字: 活动, JBPM, 应用服务器, QQ, XML 本来是要写end活动的,感觉比较简单,就直接进入task活动。 Task活动是一个重难点。 定义:在jbpm中,task活动一般用来处理涉及人机交互的活动。我们可以使用task活动的assignee属性将一 个任务分配给指定的用户。 示例一:熟练一下基本功能 对应的jpdl如下: 对应的order代码如下: package org.test; import java.io.Serializable; public class Order implements Serializable{ private String owner; public Order(){ } http://duyunfei.iteye.com1.8 jbpm学习笔记(八) task活动讲解之任务分配,候选,自定义任务分配处理器 第 44 / 53 页 public Order(String owner){ this.owner=owner; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } } 注意:假如不实现Serializable的话,会给你报错。 测试代码如下: package org.test; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jbpm.api.ProcessInstance; import org.jbpm.api.task.Task; import org.jbpm.test.JbpmTestCase; public class TestTask extends JbpmTestCase { String deploymentId; @Override public void setUp() throws Exception { super.setUp(); http://duyunfei.iteye.com1.8 jbpm学习笔记(八) task活动讲解之任务分配,候选,自定义任务分配处理器 第 45 / 53 页 deploymentId = repositoryService.createDeployment() .addResourceFromClasspath("org/test/task.jpdl.xml").deploy(); System.out.println("启动成功"); } @Override public void tearDown() throws Exception { repositoryService.deleteDeploymentCascade(deploymentId); super.tearDown(); System.out.println("销毁成功"); } public void testTask() { Map variables = new HashMap(); // 用户ID为afei variables.put("order", new Order("afei")); ProcessInstance pi = executionService.startProcessInstanceByKey( "TaskAssignee", variables); List taskList=taskService.findPersonalTasks("afei"); Task task=taskList.get(0); System.out.println("task.getActivityName()"+task.getActivityName()); } } Jbpm支持将任务分配给一组候选用户,组中的一个用户可以接受这个任务并完成它,这就是任务的候选者机 制。Task活动的候选者属性有:candidate-groups和candidate-users。 示例二:用户组概念 对应的jpdl如下: http://duyunfei.iteye.com1.8 jbpm学习笔记(八) task活动讲解之任务分配,候选,自定义任务分配处理器 第 46 / 53 页 流程实例发起后,任务review会被创建。这个任务不会显示在任何人的个人任务列表中,因为还没有创建 sales-dept组。因此下面获取的个人任务列表将是空(empty)的: taskService.getAssignedTasks(“afei”); taskService.getAssignedTasks(“angel”); 但是此任务会显示在sales-dept组成员的分组任务列表,可以通过service Api的 taskService.findGroupTasks获取。 接下来,我们就将afei和angel这两个用户创建并加入sales-dept住,这需要使用到IdentiryService服务: ProcessInstance pi=executionService.startProcessInstanceByKey("TaskAssignee"); // 首先创建sales-dept组 String groupName = "sales-dept"; identityService.createGroup(groupName); // 创建用户afei identityService.createUser("afei", "afei", "du", "664659008@qq.com"); // 将afei加入sales-dept组 identityService.createMembership("afei", groupName); http://duyunfei.iteye.com1.8 jbpm学习笔记(八) task活动讲解之任务分配,候选,自定义任务分配处理器 第 47 / 53 页 // 创建用户angel identityService.createUser("angel", "angel", "peng", "295737589@qq.com"); // 将angel加入sales-dept组 identityService.createMembership("angel", groupName); /* 此时,在流程实例发起后,review任务就会出现在用户afei和angel的分组任务列表中,即一下代码的执行结果非空 */ List afeiTasks = taskService.findGroupTasks("afei"); List angelTasks = taskService.findGroupTasks("angel"); System.out.println(afeiTasks.size()); Task afeiTask = afeiTasks.get(0); // afei接受任务操作 taskService.takeTask(afeiTask.getId(), "afei"); assertNull(taskService.findGroupTasks("afei")); 与candidate-groups属性类似的,candidate-users属性可以用来处理逗号分隔的系列用户ID,candidate- users属性可以和其他任务分配属性结合使用。 但是,如果我们需要在分配任务时加入一些复杂的业务逻辑计算呢。显示我们得找出另外的解决方案。“任务 分配处理器”是个很不错的选择。 首先,得实现AssignmentHandler接口。 package org.test; import org.jbpm.api.model.OpenExecution; import org.jbpm.api.task.Assignable; import org.jbpm.api.task.AssignmentHandler; public class AssignTask implements AssignmentHandler { private String assignee; http://duyunfei.iteye.com1.8 jbpm学习笔记(八) task活动讲解之任务分配,候选,自定义任务分配处理器 第 48 / 53 页 @Override public void assign(Assignable assignable, OpenExecution execution) throws Exception { assignable.setAssignee(assignee); System.out.println("assignee: "+assignee); } } Jpdl如下: http://duyunfei.iteye.com1.8 jbpm学习笔记(八) task活动讲解之任务分配,候选,自定义任务分配处理器 第 49 / 53 页 Ok,单元测试如下: Map vals=new HashMap(); vals.put("order",new Order("afei")); ProcessInstance pi = executionService .startProcessInstanceByKey("TaskAssignee",vals); // afei是通过jpdl定义注入的任务分配者 List taskList = taskService.findPersonalTasks("afei"); // 断言afei有一个任务 assertEquals(1, taskList.size()); Task task = taskList.get(0); // 断言任务名称如定义的 "review" assertEquals("review", task.getName()); // 断言任务的分配者如预期 assertEquals("afei", task.getAssignee()); 当然 就像你看到的,AssignmentHandler提供的execution对象可以获得流程上下文和变量,可以结合其他 任何API来计算出任务的分配者和候选者,单个用户和用户组。比如你可以这样做: 在启动流程实例时候,加一个参数: Map vals=new HashMap(); vals.put("order",new Order("angel")); 然后在你的任务分配处理器实现类中加入: Order order=(Order)execution.getVariable("order"); assignable.setAssignee(order.getOwner()); 就可以自动控制分配人了。 http://duyunfei.iteye.com1.8 jbpm学习笔记(八) task活动讲解之任务分配,候选,自定义任务分配处理器 第 50 / 53 页 下一篇继续task活动其他内容!!! http://duyunfei.iteye.com1.8 jbpm学习笔记(八) task活动讲解之任务分配,候选,自定义任务分配处理器 第 51 / 53 页 1.9 jbpm学习笔记(九) task活动之泳道的概念 发表时间: 2011-01-18 关键字: 活动, JBPM, XML, 工作 任务泳道的概念:在实际的业务应用中,经常会遇到这样一种场景:流程定义中的多个任务需要被分配或候选 给同一个群用户。那么我们可以统一将这个“同一群用户”定义为“一个泳道”。同一流程定义中的任何一个 任务都可以应用泳道。属于同一个泳道的任务将会被分配或候选给这个泳道中的所有用户。 泳道的概念也可以理解为流程定义的“全局用户组”。在某些情况下,泳道可能与后面提到的身份认证组件中 的权限角色相似,但是实际上他们并不是同一个东西。 先来熟悉一下简单的例子,jpdl如下: 上面定义的泳道“check”引用了一个用户组 managers。在流程运行前,这个用户组需要被创建出来,利用身 份认证服务 IdentityService: identityService.createGroup("order_managers"); // 创建用户 afei并加入managers组 http://duyunfei.iteye.com 1.9 jbpm学习笔记(九) task活动之泳道的概念 第 52 / 53 页 identityService.createUser("afei", "du", "yunfei"); identityService.createMembership("afei", "order_managers"); 在发起流程实例后,用户afei将成为任务 checkOrder的唯一候选者。首先,让afei接受这个任务: 注意,使用findGroupTasks才能看到task,用findPersonalTasks是出不来的。 taskService.takeTask(taskId,”afei”); 接受这个任务将使afei成为任务的分配者,同时泳道“check”也会发生变化,afei在这个流程实例中会被固化 为分配者。 接下来,afei可以完成任务了: taskService.completeTask(taskId); 完成此任务后流程实例将会流转到下一个任务“repeatCheckOrder”。这个任务也引用了之前的泳道。因此, 任务会直接分配给afei。可以通过如下代码验证: tasks = taskService.findPersonalTasks("afei"); // 断言afei直接拿到了任务 System.out.println("tasks.size(): " + tasks.size()); task = tasks.get(0); // 断言是否为预期的任务和分配者 assertEquals("repeatCheckOrder", task.getName()); assertEquals("afei", task.getAssignee()); // taskService.takeTask(task.getId(), "afei"); taskService.completeTask(task.getId()); 注意:假如此时takeTask也是不对的,因为已经被afei给take了。 下一张,任务变量! http://duyunfei.iteye.com 1.9 jbpm学习笔记(九) task活动之泳道的概念 第 53 / 53 页
还剩52页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 15 金币 [ 分享pdf获得金币 ] 10 人已下载

下载pdf

pdf贡献者

sweetbaybe

贡献于2011-11-03

下载需要 15 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf