JBPM简介
JBPM是基于JAVA的工作流引擎。工作流引擎,给我们提供流程逻辑的定义方法,给我们提供根据流程逻辑来调度业务对象的功能。能使我们避免在代码中硬编码流程逻辑,因为硬编码的逻辑难于理解和复用,并且非常容易受到变化的影响,维护起来极度困难。
所谓业务对象,从现实生活的角度理解,就是那些需要在不同的人之间流动的信息。比如一个请假单据、一个报销单据、上级下发的一份文件、一次用车申请。任何一个业务流程,都会涉及到多个环节,在这些环节中,不同的人有不同的任务需要处理。
JBPM就负责在不同的环节中传递各种信息,并在信息到达某个环节之后,给某个相关责任人分配任务(比如“审批”任务)。
应用JBPM的基本步骤
定义流程
使用JPDL流程定义语言定义流程规则,得到流程定义(ProcessDefinition)文件(比如,把请假单的流转过程定义出来)
部署流程
将流程定义文件部署到数据库中相应的表中存储起来
执行流程
根据流程定义文件的规则,针对某一个具体的业务对象进行调度。比如:孙三创建了一个请假单,现在就把这个请假单对象交给JBPM来进行调度(在不同的审批者之间进行调度)。JBPM将会启动一个流程实例(ProcessInstance)来调度某一个具体的业务对象!
快速开始的实例
我们将基于JBPM4.4来开始我们的JBPM之旅!将模拟(利用JUnit测试单元)实现一个请假审批的基本流程。
Eclipse插件的安装
我们在Eclipse(不是MyEclipse)中安装JBPM的插件(这个插件的主要功能是提供了一个图形化的流程定义界面,方便进行流程定义)。
在安装插件之前,请自行下载JBPM4.4。
【目前JBPM4.4版本对应的这个基于Eclipse的编辑器插件,功能不是很完整,也有很多BUG,比如:在handler中定义的<field>和<arg>参数,在你修改了图形之后,会自动去除!】
下面分步介绍安装过程。
第一步 打开插件安装界面
选择相应菜单,如下图所示:
打开下面的界面
第二步 打开添加site的界面
点击Add…按钮,打开下面的界面
第三步 选择插件包并按照
输入Name: jbpm4.4
点击Archive…按钮,导航到:jbpm4.4/install/src/gpd/jbpm-gpd-site.zip
点击OK
【请取消“Contact all update site during install to find required software”前面的复选框!】
一直点击Next,直到Finish即可完成安装
第四步 配置JBPM运行环境
配置JBPM Runtimes
如上图所示,点击Window->Preferences打开上述界面,并选中:JBoss jBPM下面的Runtime Locations,点击Add…按钮,输入名称,并选中JBPM4.4的解压目录即可。
第五步 配置流程定义文件自动提示功能
点击Window->Preferences打开下面的界面,并进入XML->XML catelog,在User Specified Entries下面增加一个entry。点击旁边的Add…打开界面,选中jpdl-4.4.xsd文件,Key Type: Namespace Name;Key:http://jbpm.org/4.4/jpdl,如下所示:
第六步 修改eclipse.ini文件
关闭Eclipse,在Eclipse安装根目录下,修改eclipse.ini文件,在最末尾加上一行:
-Dfile.encoding=UTF-8
这是为了避免在设计流程的时候,出现中文乱码问题。
然后重启Eclipse即可。
依赖包介绍
activation.jar |
发送EMAIL需要的包 |
antlr-2.7.6.jar |
hibernate依赖包 |
commons-collections-3.1.jar |
hibernate依赖包 |
dom4j-1.6.1.jar |
hibernate依赖包 |
drools-api.jar |
JBoss组织下面的一个规则引擎 |
drools-compiler.jar |
JBoss组织下面的一个规则引擎 |
drools-core.jar |
JBoss组织下面的一个规则引擎 |
freemarker.jar |
模板引擎 |
hibernate3.jar |
Hibernate |
janino.jar |
一个开源的JAVA编译器,能够把JAVA源代码编译为字节码 |
javassist-3.9.0.GA.jar |
hibernate依赖包 |
jbpm.jar |
JBPM核心包 |
joda-time.jar |
对日期和时间进行处理的工具类库,可替代java.util.Calendar中的相关功能 |
jta-1.1.jar |
Hibernate依赖包 |
juel-api.jar |
EL表达式的一个开源实现包(EL表达式不仅仅可以用于JSP中) |
juel-engine.jar |
EL表达式的一个开源实现包(EL表达式不仅仅可以用于JSP中) |
juel-impl.jar |
EL表达式的一个开源实现包(EL表达式不仅仅可以用于JSP中) |
livetribe-jsr223.jar |
jsr223是把其它脚本语言嵌入JAVA的一个规范,这个JAR包是对这个规范的实现 |
log4j-1.2.16.jar |
日志记录 |
mail.jar |
发送EMAIL的依赖包 |
mvel2.jar |
MVEL,是一个类似于OGNL的表达式语言解释工具,性能优于OGNL |
mysql-connector-java-3.1.13-bin.jar |
MySql驱动 |
slf4j-api-1.5.8.jar |
Hibernate依赖包 |
slf4j-log4j12-1.5.8.jar |
Hibernate依赖包 |
缺省情况下,JBPM使用Hibernate来对各种信息进行持久化,所以,上述列表中也有Hibernate相关的依赖包。
第一个项目的创建
· 首先创建普通的JAVA项目
· 引入上述依赖包
· 在类路径根目录下添加如下文件,命名为jbpm.cfg.xml:
<?xml version="1.0" encoding="UTF-8"?>
<jbpm-configuration>
<import resource="jbpm.default.cfg.xml"/> <import resource="jbpm.businesscalendar.cfg.xml"/> <import resource="jbpm.tx.hibernate.cfg.xml"/> <import resource="jbpm.jpdl.cfg.xml"/> <import resource="jbpm.identity.cfg.xml"/> <import resource="jbpm.jobexecutor.cfg.xml"/>
</jbpm-configuration> |
· 在类路径根目录下添加如下文件,命名为:jbpm.hibernate.cfg.xml:
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- 数据库链接的相关配置--> <property name="hibernate.connection.url">jdbc:mysql://localhost/jbpm</property> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">mysql</property>
<!-- 数据库方言--> <property name="dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
<!-- 自动打印出SQL语句--> <property name="show_sql">true</property>
<!-- 自动创建数据库表--> <property name="hibernate.hbm2ddl.auto">update</property>
<!-- 映射文件列表--> <mapping resource="jbpm.execution.hbm.xml"/> <mapping resource="jbpm.history.hbm.xml"/> <mapping resource="jbpm.identity.hbm.xml"/> <mapping resource="jbpm.repository.hbm.xml"/> <mapping resource="jbpm.task.hbm.xml"/> </session-factory> </hibernate-configuration>
|
这个文件是hibernate配置文件,JBPM底层使用hibernate来存取数据。此配置的目的是希望JBPM能够把有关信息存储到MYSQL数据库中,便于我们查看。
流程定义
流程定义,是一个XML文件,JBPM4中常用的流程定义语言是:JPDL(JBPM Process Definition Language)。
我们可以选择新建一个流程定义:
在流程定义设计器上,设计流程:
注意中间要使用Task节点,最终定义好的流程定义文件如下所示:
<?xmlversion="1.0" encoding="UTF-8"?>
<processkey="LEAVE" name="请假单"xmlns="http://jbpm.org/4.4/jpdl"> <startform="baoxiao" g="234,7,48,48" name="start1"> <transitiong="-71,-17" name="提交给张三审批"to="张三审批"/> </start> <taskassignee="张三"g="215,113,92,52" name="张三审批"> <transitiong="-71,-17" name="提交给李四审批"to="李四审批"/> </task> <taskassignee="李四"g="218,216,92,52" name="李四审批"> <transitiong="-47,-17" name="to end1" to="end1"/> </task> <endg="247,326,48,48" name="end1"/> </process> |
在完成流程的设计之后,我们可以编写代码,把这个流程定义文件部署到数据库中:
publicclassJbpm_01_DeployProcessDefinition extendsTestCase{
//把流程定义文件的相关信息保存到数据库中! publicvoidtestDeployProcessDefinition(){
//流程引擎 ProcessEngine engine = newConfiguration() .setResource("jbpm.cfg.xml").buildProcessEngine();
//从流程引擎中获得跟数据库操作有关的服务 engine.getRepositoryService() .createDeployment() //现在要部署流程定义 .addResourceFromClasspath("process01.jpdl.xml") //添加流程定义文件 .deploy(); //部署,将流程定义的相关信息插入数据库\ } } |
在运行完上述代码之后,我们可以到数据库中查看相关的信息:
上面的数据库表,描述了有关ProcessDefinition对象的信息。ProcessDefinition对象有:name,id,key,version等属性。
Name就是给人类看的名称,比如:“请假单”
Key就是一个键值,比如:LEAVE
Version是版本号,对于相同的流程定义(名称和KEY相同),如果重复部署(比如上述代码多次运行),其version将一直递增。
Id就是KEY和VERSION组合的值。比如:LEAVE-1
上面数据库表中呈现的pdid,pdkey,pdversion和OBJNAME_字段的值,就是这些属性。
流程执行
流程定义完成之后,就可以创建流程实例(ProcessInstance)来按照这些流程定义进行流转。流程实例的概念很容易理解。
比如,公司的请假制度是:先给张三审批,然后还得给李四审批,最后还得王五去审批,这三个人审批都结束之后,请假单才能生效。这些描述就是流程定义(ProcessDefintion)。
现在假设员工“小赵”生病了要请假,在现实中,他需要拿到请假条,并且按照请假条上固定的格式填写好请假的内容(比如:谁请假,请假的时间,请假单的天数等等)之后,按照公司的规定,把请假条首先拿给张三去审批。张三审批完成之后,再拿给李四去审批……
“小赵”拿到请假条,填写内容这样的事情,我们可以称之为“创建流程实例(ProcessInstance)”。小赵将请假条交给张三去审批这样的事情,我们可以称之为“提交”;张三审批完成之后再拿给李四去审批这样的事情,也可以称之为“提交”。
所以,流程定义(ProcessDefinition)我们可以理解为流转规则的定义;而流程实例(ProcessInstance)则可以理解为按照特定规则对某些数据执行流转。比如上述例子中,“小赵的请假条”,我们就可以理解为一个流程实例(ProcessInstance)对象。每个流程实例都会包含有各种数据,比如“小赵的请假条”这个流程实例,可能就包含了:请假者是小赵,请假时间是某年某月某日,请假天数是3天,请假事项是“有病要去医院”等等这些数据。这些数据可以称为“流程实例变量”。
在JBPM4中,创建流程实例并把流程实例提交到第一个环节(比如张三审批这个环节是第一个环节)称为“启动流程实例(startProcessInstance)”。
publicclassJbpm_02_StartProcessInstance extendsTestCase{
//根据流程定义,启动一个新的流程实例 publicvoidtestStartProcessInstance(){
//流程引擎 ProcessEngine engine = newConfiguration() .setResource("jbpm.cfg.xml").buildProcessEngine();
Map instanceVariables = newHashMap(); instanceVariables.put("leaver", "小赵"); //请假者 instanceVariables.put("leaveDate", newDate()); //请假时间 instanceVariables.put("leaveDays", 5); //请假天数 instanceVariables.put("reason", "有病要去医院"); //请假事项/原因
//启动流程实例 ProcessInstance processInstance = engine.getExecutionService() .startProcessInstanceByKey("LEAVE",instanceVariables);
System.out.println("流程实例【"+processInstance.getId()+"】已经被创建了!"); } } |
在上面的例子中,我们调用startProcessInstanceByKey方法来启动流程实例,传了两个参数:
第一个参数是流程定义的KEY
第二个参数是流程实例变量
我们调用这个方法,JBPM将自动查询对应流程定义KEY的最新版本的流程定义对象来创建流程实例对象,并把流程实例对象提交到第一个环节。
当然,实际上更加常见的用法是:像请假单的这些数据,可以存放在我们自己的业务表中,然后在启动流程实例的时候,直接用业务表中的主键值作为流程实例的KEY即可(即无需把所有的业务数据都传递给JBPM),如下所示:
publicvoidtestStartProcessInstance02(){
//流程引擎 ProcessEngine engine = newConfiguration() .setResource("jbpm.cfg.xml").buildProcessEngine();
//启动流程实例 ProcessInstance processInstance = engine.getExecutionService() .startProcessInstanceByKey("LEAVE","101");
System.out.println("流程实例【"+processInstance.getId()+"】已经被创建了!"); } |
在这个例子中,我们用的是另外的一个方法来启动流程实例,第一个参数的意义与前面例子的一样,第二个参数则是流程实例的KEY。
流程实例也是有KEY这种概念的,流程实例的KEY,刚才说了,一般是将业务对象的键值作为流程实例的KEY。比如,上面我们给了一个“101”,这是我们假设某个请假单的ID是101,现在,我们要给这个请假单创建一个流程实例对象(以便于这个请假单能在JBPM中进行流转)。
当然,还有更多的方法,也可以用于启动流程实例,具体请参考JBPM4的API,我们在课堂上对此将做详细的介绍。
在启动流程实例之后,数据库中JBPM4_EXECUTION表将被添加相应的记录:
ProcessInstance与Execution
上面我们了解了所谓流程实例(ProcessInstance),实质就是指得一个业务对象(比如:“小赵的请假单”、“小李的报销单”、“某某单位的某次发文”等等)。而Execution是一个执行控制对象,你必须要理解的一点就是每个Execution对象必然会指向一个节点(Activity)。Execution指向哪里,就代表它执行到了哪里。每个Activity都有自己的行为。比如TaskActivity的行为就是创建任务实例(TaskImpl),并把任务实例与参与者(assignee)相关联。这样,通过assignee就可以查找到其任务实例列表。
Execution有name、key、id、state等属性,Execution是树型结构,在fork和join流程中,将会创建子Execution对象。
子Execution对象的name属性是进入这个子Execution对象时的transition的名称。
ProcessInstance也是一种Execution类型的对象。实际上,对于ProcessInstance对象来说,它对应的ProcessInstance(因为ProcessInstance也是Execution,所以它也有对应的ProcessInstance对象)就是它自己。而对于ProcessInstance的那些子Execution对象(孙Execution对象等等等等)而言,也肯定会有指向ProcessInstance对象的一个引用!
对于根Execution对象(即ProcessInstance对象),它的ID是:流程定义的KEY.流程实例的KEY,比如上面例子中创建的流程实例的ID就是:”LEAVE.101”。
我们可以通过这个ID(或其它方法)来查询流程实例对象或Execution对象,从而得知这些Execution指向哪里。
//流程引擎 ProcessEngine engine = newConfiguration() .setResource("jbpm.cfg.xml").buildProcessEngine();
ProcessInstance instance = engine.getExecutionService().findProcessInstanceById("LEAVE.101");
Set<String> currentActivityNames = instance.findActiveActivityNames();
//流程实例现在流转到哪个Activity了 System.out.println(currentActivityNames.toString()); |
上述代码将输出:[张三审批],表明现在执行到了张三审批这个环节。
Task
在我们上述流程定义中,张三审批这个环节,用的是一个Task类型的Activity来定义的。这种类型的Activity的行为是:创建任务实例(TaskImpl),并把任务实例与参与者(assignee)相关联。Task是一个接口,而TaskImpl是具体的实现类。
任务实例(Task)这种概念,其实质是用来定义Execution与assignee之间的关联的。从上面我们知道流程已经执行到了张三审批这个环节了,因此,我们下一步的目标就是:通过张三,找出流转到他手上的所有的业务对象信息。下面是一个示例:
ProcessEngine engine = newConfiguration() .setResource("jbpm.cfg.xml").buildProcessEngine();
//根据用户,查询它的任务列表 List<Task> tasks = engine.getTaskService().findPersonalTasks("张三"); for(Task t:tasks){ System.out.println("任务ID:"+t.getId()+","+t.getAssignee()+"手上有【"+t.getExecutionId()+"】等待:"+t.getActivityName()); } |
在数据库中,亦可查询到相关信息:
关于Task及其与Execution等之间的关系,我们在课堂上将做详细解析。
提交
张三如果审批完成,他需要继续向下提交,下面是示例代码:
//根据任务ID,完成此任务 //任务被完成之后,将自动流转到下一个环节 engine.getTaskService().completeTask("10002");
//任务被完成之后,下面的查询将无数据 List<Task> tasks = engine.getTaskService().findPersonalTasks("张三"); for(Task t:tasks){ System.out.println(t.getAssignee()+"手上有【"+t.getExecutionId()+"】等待:"+t.getActivityName()); } |
completeTask就是完成某个任务实例,我们在查询出某人有哪些任务实例之后,他就可以选择其中一个任务实例来完成(complete)。任务实例被完成之后,任务实例对象将被删除,并被转移到历史库中。
在张三提交完成之后,JBPM4_TASK表将变为:
你也可以到JBPM4_HIST_TASK表中查询有关刚才那个Task的历史信息(比如它的完成时间等信息)。
后续工作
张三审批完成之后,我们可以继续用李四来查询其手上的任务列表,并结束任务对象;然后继续用王五来查询其手上的任务列表,并结束任务对象。
当所有环节都完成之后,流程实例对象及各种任务实例对象都会被删除,并被转移到历史库中。
大家可继续执行,直到执行结束!