1. JBPM入门知识回顾
1.1. 开发环境搭建
什么是工作流? 解决实际中怎样的问题?
软件系统面对各种复杂的业务流程,如果针对每个流程采用分别编码方式(将流程逻辑写到代码中)完成,不便于进行业务流程变更和扩展 。
工作流框架,将业务流程管理起来(保存到数据表中),只需要针对通用流程办理去编写程序 流程定义管理、流程实例管理、任务办理、流程关联变量 ,(程序与业务无关后) 去修改或增加新的流程,不需要修改之前的代码。
什么是JBPM ?
JBPM 是一个工作流框架 ,是JBOSS的开源免费的框架 ,课程主要学习4.4版本,JBPM默认和Hibernate框架整合。
安装流程设计器?
JBPM业务流程设计器分为在线和线下设计器,MyEclipse的GPD插件安装(线下设计器), 使用线下设计器,设计业务流程后,产生 jpdl.xml 和 png 文件,将产生流程文件,制作zip 压缩包,通过上传文件的方式,将业务流程发布到软件系统中。
在项目导入JBPM的jar包和配置文件?
${JBPM_HOME}/jbpm.jar 核心jar包
${JBPM_HOME}/lib/*.jar (导入jar包,防止jar包冲突)
项目需要JBPM两个配置文件 jbpm.cfg.xml 、jbpm.hibernate.cfg.xml
开发JBPM
Configuration configuration = new Configuration();
configuration.buildProcessEngine(); // 创建流程引擎 ,建立JBPM18张数据表
1.2. 流程定义管理
任何使用工作流框架的系统,都需要将业务流程,交给工作流管理
通过设计器设计业务流程, 将业务流程部署到系统中
1.2.1. 流程定义发布
processEngine.getRepositoryService().createDeployment() 获得发布对象
deployment.addResourceFromClassPath(xxx.jpdl.xml)
deployment.addResourceFromZipInputStream
(new ZipInputStream(new FileInputStream(zipFile)));
deployment.deploy();
流程定义发布: jbpm4_deployment、jbpm4_lob、jbpm4_deployprop
1.2.2. 流程定义查看
流程定义属性查询:
processEngine.getRepositoryService().createProcessDefinitionQuery();
流程图查看 :
processEngine.getRepositoryService().getResourceAsStream(deploymentId, resourceName)
1.2.3. 流程定义删除
processEngine.getRepositoryService().deleteDeploymentCascade(deploymentId);
// 这里 deploymentId 发布编号,是 jbpm4_deployment 表主键 DBID
1.3. 流程实例管理
运行一个流程定义,产生具体业务实例
1.3.1. 启动流程实例
根据id启动:
processEngine.getExecutionService().startProcessInstanceById(pdId) ;
// 这里pdId 流程定义编号 jbpm4_deployprop 表 pdid
根据key启动:
processEngine.getExecutionService().startProcessInstanceByKey(pdKey);
// 这里pdkey 流程定义关键字 jbpm4_deployprop 表 pdkey
// 如果存在很多相同key的流程,默认启动最高版本的流程定义
1.3.2. 流程实例流转
向后一步:
processEngine.getExecutionService().signalExecutionById(executionId);
// 这里executionId 流程实例编号 jbpm4_execution表 ID_
直接中止流程:
processEngine.getExecutionService().endProcessInstance(executionId,. ProcessInstance.STATE_END);
流程实例影响数据表:
jbpm4_execution、jbpm4_hist_procinst、jbpm4_task、jbpm4_hist_task、jbpm4_hist_actinst
1.4. 任务管理
在流程流转时,遇到task节点,需要指定用户去办理任务 ,通常不会使用 signalExecutionById, 而会去由特定用户办理任务,是流程自动流转
1.4.1. 查看当前流程实例的任务
processEngine.getTaskService().createTaskQuery().executionId(executionId).list();
1.4.2. 查看我的个人任务
要求在设计流程时,需要为 节点执行 assignee的属性 (指定任务办理人)
processEngine.getTaskService().findPersonalTasks(userId);
1.4.3. 办理个人任务
processEngine.getTaskService().completeTask(taskId);
// 这里 taskId 任务编号, jbpm4_task表主键DBID
// 在办理任务,流程自动向后流转
1.5. 流程变量管理
业务流转时,产生一些业务数据,这些数据要和流程实例进行关联
1.5.1. 流程变量读写操作
第一种: 启动流程实例,关联变量
process.getExecutionService().startProcessInstanceByKey(pdKey, variables);
第二种: ExecutionService 结合 executionId 读写变量
process.getExecutionService().getVariable(executionId, variableName) ;
process.getExecutionService().setVariable(executionId, variableName,variableValue)
第三种: TaskService 结合 taskId 读写变量
process.getTaskService().getVariable(taskId, variableName) ;
process.getTaskService().setVariables(taskId, variables);
1.5.2. 向流程实例关联复杂对象
JBPM流程变量 支持复杂对象
Long类型主键的PO对象
String 类型主键的PO对象
实现Serializable接口的对象
2. JPDL流程定义语言
jPDL定义jPDL(JBoss jBPM Process Definition Language)是构建于jBPM框架上的流程语言。
devguide 开发手册 (JBPM的项目中使用)
javadocs API手册
userguide 用户手册 (JBPM 本身使用)
先学习 userguide ,再学习devguide
目标: 掌握JBPM 六个Service 作用
JPDL 将流程中活动节点分为两大类
控制流程活动节点 Control flow activities
自动流转活动节点 Automatic activities
重点学习控制流程活动节点 Control flow activities
2.1. process节点(jpdl文件的根节点)
每次发布一个流程定义
jbpm4_deployment 存放发布信息
jbpm4_lob 存放上传资源文件
jbpm4_deployprop 存放流程定义属性信息
注意: 如果指定key 和 version 有注意事项
name 和key,如果name和之前流程相同,key也必须和之前流程相同,反之 如果key和之前流程相同,name也必须和之前的流程相同
name 用于显示,通常写中文
key 用于编码,通常写英文
org.jbpm.api.JbpmException:
error: invalid name '测试流程' in process 测试流程. Existing process has name 'process' and key 'process'
error: invalid name '测试流程' in process 测试流程. Existing process has name 'process' and
如果指定version,切记不要和之前相同key、相同version流程冲突,通常在开发中不指定version 。
在JBPM中流程定义编号,必须是唯一的,pdId 是由 pdkey和pdversion 组成的
2.2. transition节点(流转节点)
transition 含有流出的含义 ,表示从一个节点流向另一个节点
开始活动只能有一个transition
结束活动,不能有transition
其它活动节点,可以有一个或者多个transition
<task name="task1" g="214,114,92,52">
<!-- 在一个节点如果含有多个transition,只有一个transition 可以不写name属性 (默认transition) -->
<transition to="task2" g="-53,-17"/> <!-- 默认transition -->
<transition name="to task3" to="task3" g="-53,-17"/>
</task>
// 向后流转
executionService.signalExecutionById("transition.70001");
如果没有指定向哪里流转,执行默认transition (没有name属性 )— task2
可以在流转时,指定模板transition的name属性
// 指定transitionName的流转
executionService.signalExecutionById("transition.90001", "to task3");
2.3. start和end 节点
一个流程中只能有一个开始节点 ,可以有多个结束节点
实际开发中,没有必要画太多 结束节点, 可以在流程任意位置 中止流程
中止流程代码
processEngine.getExecutionService()
.endProcessInstance(processInstanceId, ProcessInstance.STATE_ENDED);
2.4. state节点 (状态节点、等待节点)
state出现等待效果,不是人来办理的 ,是由程序回调,自动触发
<process name="state" xmlns="http://jbpm.org/4.4/jpdl">
<start name="start1" g="199,26,48,48">
<transition name="to 去网银给支付宝" to="去网银给支付宝" g="-107,-17"/>
</start>
<end name="end1" g="198,289,48,48"/>
<task name="去网银给支付宝" g="149,111,159,51">
<transition name="to 等待支付宝回应" to="等待支付宝回应" g="-107,-17"/>
</task>
<state name="等待支付宝回应" g="154,186,132,52">
<transition name="to end1" to="end1" g="-47,-17"/>
</state>
</process>
注意: 这里回应,由程序自动完成的 ,不需要人工干预 ,不适合使用task节点
问题: state节点和task节点有何不同 ?
Task节点 是任务节点,需要人工办理,办理任务后 TaskService.completeTask(taskId)
,
流程自动流转
State节点 是等待节点,不需要人工干预,当程序满足特定条件 ,自动向后流转
ExecutionService.signalExecutionById(executionId)
2.5. decision节点 (条件判断节点)
两种设置条件的方式
方式一 : 通过 expression 属性变量 设置条件表达式
方式二 : 通过 指定 DecisionHandler 实现程序,控制条件流转
第一种 通过 expression 属性指定 该流转到哪个分支
<decision name="exclusive1" g="236,151,48,48" expr="#{condition}">
<transition name="to 学生半票" to="学生半票" g="-71,-17"/>
<transition name="to 军人半票" to="军人半票" g="-71,-17"/>
<transition name="to 领导免费" to="领导免费" g="-71,-17"/>
<transition name="to 正常收费" to="正常收费" g="-71,-17"/>
</decision>
当程序运行 decision节点,需要有一个流程变量condition , 使用condition的值与transition的name去匹配,和哪个transition 就会流向哪个transition
在购买火车票设置流程变量
// 设置流程变量
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("condition", "to 领导免费");
processEngine.getTaskService().setVariables("10008", variables);
第二种 : 通过Handler程序控制流转
<decision name="exclusive1" g="236,151,48,48">
<handler class="cn.itcast.jbpm.b_handler.MyDecisionHandler"></handler>
<transition name="to 学生半票" to="学生半票" g="-71,-17"/>
<transition name="to 军人半票" to="军人半票" g="-71,-17"/>
<transition name="to 领导免费" to="领导免费" g="-71,-17"/>
<transition name="to 正常收费" to="正常收费" g="-71,-17"/>
</decision>
提供 handler程序
public class MyDecisionHandler implements DecisionHandler {
@Override
// 参数 openExecution,作用是用来读写流程变量
public String decide(OpenExecution openExecution) {
return "to 学生半票";
}
}
原理当运行判断节点,执行handler中decide方法,用decide方法返回值和 transition的name属性比,和哪个一致,就流向哪个transition
注意:如果同时配置了expression与Handler,则expression有效,忽略Handler
handler代码比 expression表达式更加灵活
2.6. fork、join节点(分支聚合节点)
为了解决流程中并发操作,设计的节点
企业会签业务 ,当前一个流程节点,需要多个任务公共签字才能完成 !
fork出现后,程序节点中必须提供 join节点 !
jbpm4_execution 表中每个分支,产生一个子流程实例
* jbpm4_hist_procinst
在历史记录表中 不会保存子流程实例运行信息
分支使流程实例 具有多个当前任务节点 !!!
2.7. task节点 (任务节点)
何为任务节点: 在业务流程中,需要特定人来办理的活动节点,所有工作流引擎都是基于任务办理和form表单提交 来实现业务流程的自动流转的!
任务的分类:有两种
个人任务 (有指定某个人 才能完成的任务 )
组任务 (可以由一组人中任何一个人 ,进行完成的任务 )
2.7.1. 个人任务操作
三种个人任务指定方式
<task name="员工提交申请" g="207,91,92,52" assignee="张三">
<transition name="to 部门经理审批" to="部门经理审批" g="-95,-17"/>
</task>
<task name="部门经理审批" g="211,177,92,52" assignee="#{manager}">
<transition name="to 总经理审批" to="总经理审批" g="-83,-17"/>
</task>
<task name="总经理审批" g="214,249,92,52">
<assignment-handler class="cn.itcast.jbpm.b_handler.MyPersonlTaskHandler" />
<transition name="to end1" to="end1" g="-47,-17"/>
</task>
个人任务操作 :
查看我的个人任务
办理个人任务
员工提交申请,为员工指定负责部门经理
// 为员工指定负责的经理
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("manager", "老王");
processEngine.getTaskService().setVariables("8", variables);
运行总经理审批,Handler执行
public class MyPersonlTaskHandler implements AssignmentHandler {
@Override
// Assignable对象 用来指定任务负责人
// OpenExecution 用来读写流程变量
public void assign(Assignable assignable, OpenExecution openExecution) throws Exception {
// 指定任务负责人
assignable.setAssignee("老毕");
}
}
重点: 在流程执行过程中,为个人任务节点,更换 任务负责人 !
TaskService 提供
2.7.2. 组任务操作
由一组人 都可以完成的任务
组任务分为两大种分配方式 :
candidate-users 属性进行分配
candidate-groups 属性进行分配
在 AssignmentHandler 中 为任务指定组用户
<task name="填写财务报销申请" g="216,86,123,52" candidate-users="张三,李四,王五">
<transition name="to 经理审批" to="经理审批" g="-71,-17"/>
</task>
<task name="经理审批" g="235,171,92,52" candidate-groups="经理">
<transition name="to 财务拨款" to="财务拨款" g="-71,-17"/>
</task>
<task name="财务拨款" g="236,246,92,52">
<assignment-handler class="cn.itcast.jbpm.b_handler.MyGroupTaskHandler" />
<transition name="to end1" to="end1" g="-47,-17"/>
</task>
备注: candidate-users 为任务一次指定多个用户 (张三,李四,王五 是用户编号), candidate-groups为任务一次指定多个组 (经理 是 组编号) ,assignmentHandler 是通过程序方式,为任务指定组用户
启动流程后,进入“填写财务报销申请”节点,在jbpm4_task表存在记录
ASSIGNEE 为 null,说明这个任务 不是某个人的 个人任务
在jbpm4_participation 表中,查看组任务的信息
组任务的常见操作 :
1、查询组任务
// 查询组任务 jbpm4_participation 表
List<Task> list = processEngine.getTaskService().findGroupTasks("李四");
System.out.println(list.size());
for (Task task : list) {
System.out.println("任务编号:" + task.getId());
System.out.println("任务名称:" + task.getName());
}
2、拾取组任务 (将组任务变为个人任务 )
// 拾取组任务,变为个人任务, jbpm4_task 表 ASSIGNEE 栏有值
processEngine.getTaskService().takeTask("8", "王五");
改变 task表 ASSIGNEE 栏值
变为个人任务后,按照个人任务的操作,进行查询和办理
3、 如果王五拾取组任务,不能办理,必须更换任务的负责人
// 改变任务的负责人,为 李四
processEngine.getTaskService().assignTask("8", "李四");
// 如果把任务负责人 设置 null , 将任务重新放回到组中,成为组任务
processEngine.getTaskService().assignTask("8", null);
办理 填写报销申请任务,流转 经理审批 节点
<task name="经理审批" g="235,171,92,52" candidate-groups="经理">
jbpm4_participation 出现组用户信息
GROUPID_ 为经理,这个组任务由 经理这个组内用户来完成 !
问题: 经理这个组中有哪些用户 ??
这三张表 用来存放 JBPM系统组和用户的信息的
jbpm4_id_group 存放组信息
jbpm4_id_user 存放用户信息
jbpm4_id_membership 存放组和用户的关系信息
需要 向JBPM系统内,添加一些 组和用户的信息 ! 使用 IdentityService
// 创建组
identityService.createGroup("经理");
// 创建用户
identityService.createUser("1", "张", "老");
identityService.createUser("2", "王", "老");
identityService.createUser("3", "李", "老");
// 创建关系
identityService.createMembership("1", "经理");
identityService.createMembership("2", "经理");
identityService.createMembership("3", "经理");
办理经理审批任务, 流转 财务拨款 ,执行指定handler程序
public class MyGroupTaskHandler implements AssignmentHandler {
@Override
public void assign(Assignable assignable, OpenExecution openExecution) throws Exception {
// 向系统指定组用户
assignable.addCandidateUser("小丽"); // candidate-users
assignable.addCandidateUser("小明");
assignable.addCandidateGroup("财务");// candidate-groups
}
}
为一个已经存在的任务,临时添加组用户
第一种, 将新用户加入 group 组内 (财务)
第二种,
processEngine.getTaskService().addTaskParticipatingUser(taskId,userId, Participation.CANDIDATE);
3. 任务泳道swimlanes的使用
JBPM 绘制流程设计图 ,是什么图 ? —- UML 活动图
UML 统一建模语言,提供九种图 用来进行软件 设计和分析
用例图 — 需求分析
类图 描述程序类结构,用于设计阶段
时序图(序列图) 用于分析和设计
活动图
泳道的作用,在流程节点中,指定一些节点由特定一个人或者一组人来完成 !
问题 :
<task name="报销申请" g="188,81,92,52" candidate-users="张三,李四,王五">
<transition name="to 发钱" to="发钱" g="-47,-17"/>
</task>
<task name="发钱" g="192,165,92,52">
<transition name="to 签字" to="签字" g="-47,-17"/>
</task>
<task name="签字" g="196,237,92,52" candidate-users="张三,李四,王五">
<transition name="to end1" to="end1" g="-47,-17"/>
</task>
如果 报销申请和 签字,都使用组任务,申请和签字可能会出现不是一个人完成的 !
如何保证,申请的人和签字的人 是同一个人呢 ? —- 泳道
<!-- 保证 申请和签字是同一个 人 -->
<swimlane name="commitperson" candidate-users="张三,李四,王五">
</swimlane>
<task name="报销申请" g="188,81,92,52" swimlane="commitperson">
<transition name="to 发钱" to="发钱" g="-47,-17"/>
</task>
<task name="发钱" g="192,165,92,52">
<transition name="to 签字" to="签字" g="-47,-17"/>
</task>
<task name="签字" g="196,237,92,52" swimlane="commitperson">
<transition name="to end1" to="end1" g="-47,-17"/>
</task>
提交申请时,变为个人任务后, 到达签字环节时,不再是组任务,直接变为申请人的个人任务!
4. JBPM和项目整合
4.1. 导入jar包
如果使用不是maven的项目, 将之前使用 JBPM的jar包 (挑选不重复的放入你的项目)
如果项目使用 maven管理,使用坐标导入jar包
在pom.xml 先配置仓库
<repositories>
<!--JBPM依赖包 maven2资源库 -->
<repository>
<id>Jboss-JBPM-Repositories</id>
<name>JbossJBPM</name>
<url>https://repository.jboss.org/nexus/content/repositories/releases</url>
</repository>
</repositories>
使用坐标导入jar包
<dependency>
<groupId>org.jbpm.jbpm4</groupId>
<artifactId>jbpm-api</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>org.jbpm.jbpm4</groupId>
<artifactId>jbpm-jpdl</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>org.jbpm.jbpm4</groupId>
<artifactId>jbpm-pvm</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>org.jbpm.jbpm4</groupId>
<artifactId>jbpm-log</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>org.jbpm.jbpm4</groupId>
<artifactId>jbpm-bpmn</artifactId>
<version>4.4</version>
</dependency>
JBPM 运行需要 Javamail
<!-- javamail -->
<dependency>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
<version>1.4</version>
</dependency>
4.2. 配置文件导入
项目 已经使用spring 文件 配置 hibernate 属性
参考 : JBPM devguide 开发手册
1、 将 jbpm.cfg.xml 复制 src/main/resources 目录
2、 修改jbpm.cfg.xml
将
<import resource="jbpm.tx.hibernate.cfg.xml" />
换为
<import resource="jbpm.tx.spring.cfg.xml" />
3、 将原来 hibernate 配置写入spring 配置文件
修改 applicationContext-common.xml
修改mysql方言
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
在配置文件引入JBPM的hbm文件
<!-- 映射JBPM的 hbm -->
<property name="mappingResources">
<list>
<value>jbpm.repository.hbm.xml</value>
<value>jbpm.execution.hbm.xml</value>
<value>jbpm.history.hbm.xml</value>
<value>jbpm.task.hbm.xml</value>
<value>jbpm.identity.hbm.xml</value>
</list>
</property>
4、 通过 JBPM提供工具类,将ProcessEngine 流程引擎对象配置为 Spring 容器中Bean
抽取 applicationContext-jbpm.xml
配置整合JBPM
<!-- 整合jbpm 配置 -->
<bean id="springHelper" class="org.jbpm.pvm.internal.processengine.SpringHelper">
<property name="jbpmCfg" value="jbpm.cfg.xml"></property>
</bean>
<bean id="processEngine" factory-bean="springHelper"
factory-method="createProcessEngine" />
编写测试用例,发布一个流程定义 !
4.3. 在使用tomcat运行项目时,存在jar冲突问题
问题一: 在打成war包 部署 项目时
启动tomcat 报错
16:13:33,031 ERROR ContextLoader:307 - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean wit
h name 'sessionFactory' defined in class path resource [applicationContext-commo
n.xml]: Invocation of init method failed; nested exception is java.lang.NoSuchMe
thodError: org.slf4j.impl.StaticLoggerBinder.getSingleton()Lorg/slf4j/impl/Stati
cLoggerBinder;
原因分析 : slf4j-jdk 的jar 和 slf4j-log4j的jar 冲突
<dependency>
<groupId>org.jbpm.jbpm4</groupId>
<artifactId>jbpm-pvm</artifactId>
<version>4.4</version>
<exclusions>
<exclusion>
<artifactId>slf4j-jdk14</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
问题二:
javax.servlet.ServletException: java.lang.LinkageError: loader constraint violation: when resolving interface method "javax.servlet.jsp.JspApplicationContext.getExpressionFactory()Ljavax/el/ExpressionFactory;" the class loader (instance of org/apache/jasper/servlet/JasperLoader) of the current class, org/apache/jsp/index_jsp, and the class loader (instance of org/apache/catalina/loader/StandardClassLoader) for resolved class, javax/servlet/jsp/JspApplicationContext, have different Class objects for the type p.JspApplicationContext.getExpressionFactory()Ljavax/el/ExpressionFactory; used in the signature
原因分析 :
Jbpm导入 juel-api的jar 和 tomcat/lib/el-api.jar 冲突
解决: 将 juel-*.jar 三个jar 移动tomcat/lib 下,删除webapp项目中对应jar包
4.4. 解决maven运行tomcat jar冲突问题
第一步 : 不要发布juel-*.jar 三个jar包
<dependencyManagement>
<dependencies>
<dependency>
<groupId>juel</groupId>
<artifactId>juel-api</artifactId>
<version>2.2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>juel</groupId>
<artifactId>juel-impl</artifactId>
<version>2.2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>juel</groupId>
<artifactId>juel-engine</artifactId>
<version>2.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</dependencyManagement>
第二步: 在目标tomcat 引入juel的jar
<!-- 使用 plugin 配置tomcat-maven-plugin -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>tomcat-maven-plugin</artifactId>
<version>1.1</version>
<!-- 对插件进行配置 -->
<configuration>
<!-- 内嵌插件 端口 -->
<port>80</port>
<!-- 远程发布 -->
<url>http://localhost:8080/manager/text</url>
<server>myserver</server>
</configuration>
<!-- 引入 juel jar 包 -->
<dependencies>
<dependency>
<groupId>de.odysseus.juel</groupId>
<artifactId>juel-api</artifactId>
<version>2.2.6</version>
</dependency>
<dependency>
<groupId>de.odysseus.juel</groupId>
<artifactId>juel-impl</artifactId>
<version>2.2.6</version>
</dependency>
</dependencies>
</plugin>