工作流(Workflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者促使此目标的实现”。
通俗的说,流程就是多个人在一起合作完成某件事情的步骤,把步骤变成计算机能理解的形式就是工作流。
工作流管理系统(WfMS,Workflow Management System)的主要功能是通过计算机技术的支持去定义、执行和管理工作流,协调工作流执行过程中工作之间以及群体成员之间的信息交互。应能提供以下三个方面的功能支持:
1. 定义工作流:包括具体的活动、规则等
2. 运行控制功能:在运行环境中管理工作流过程,对工作流过程中的活动进行调度
3. 运行交互功能:指在工作流运行中,WfMS与用户(活动的参与者)及外部应用程序工具交互的功能。
一、定义工作流
二、执行工作流
采用工作流管理系统的优点
1. 提高系统的柔性,适应业务流程的变化
2. 实现更好的业务过程控制,提高顾客服务质量
3. 降低系统开发和维护成本
工作流框架有:Jbpm、OSWorkflow、ActiveBPEL、YAWL等
jBPM 即java Business Process Management,是基于java的业务流程管理系统。jBPM是市面上相当流行的一款开源工作流引擎,引擎底层基于Active Diagram模型。jBPM4.4使用了hibernate(3.3.1版),因此可以很好的支持主流数据库。jBPM4.4共有18张表。
jBPM官方主页:http://www.jboss.org/jbpm
jBPM requires a JDK (standard java) version 5 or higher. http://java.sun.com/javase/downloads/index.jsp
To execute the ant scripts, you'll need apache ant version 1.7.0 or higher: http://ant.apache.org/bindownload.cgi
1, jBPM下载地址:http://sourceforge.net/projects/jbpm/files/
2, Eclipse下载地址( Eclipse IDE for Java EE Developers (163 MB),Version:3.5 ):http://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/galileo
GPD(Graphical Process Designer)是一个Eclipse插件。
安装方法说明(jBPM4.4User Guide, 2.11.2. Install the GPD plugin into eclipse):
Help --> Install New Software...
Click Add...
In dialog Add Site dialog, click Archive...
Navigate to install/src/gpd/jbpm-gpd-site.zip and click 'Open'
Clicking OK in the Add Site dialog will bring you back to the dialog 'Install'
Select the jPDL 4 GPD Update Site that has appeared
Click Next... and then Finish
Approve the license
Restart eclipse when that is asked
查看是否成功安装了插件:WindowàPreference中是否有Jboss jBPM项。
流程定义文件的xsd文件的路径为:JBPM_HOME/src/jpdl-4.4.xsd。
添加到Eclipse中的方法为(jBPM4.4 User Guide, 2.11.5. Adding jPDL 4 schema to the catalog):
Click Window --> Preferences
Select XML --> XML Catalog
Click 'Add...'
The 'Add XML Catalog Entry' dialog opens
Click the button with the map-icon next to location and select 'File System...'
In the dialog that opens, select file jpdl-4.4.xsd in the src directory of the jBPM installation root.
Click 'Open' and close all the dialogs
1. ${JBPM_HOME}/jbpm.jar(核心包)
2. JBPM_HOME/lib/*.jar,不添加以下jar包:servlet-api.jar, junit.jar。其中junit.jar一定不要添加,因为是3.8.2版本,与我们使用的junit4有冲突。
3. 所使用的数据库对应的驱动的jar包(第2步所添加的jar包中已包含mysql的jdbc驱动jar包)。
1. 配置文件可以从JBPM_HOME/examples/src/中拷贝:
jbpm.cfg.xml、
logging.properties、
jbpm.hibernate.cfg.xml。
2. 修改logging.properties中的日志输出级别[t1] 为WARNING: java.util.logging.ConsoleHandler.level=WARNING
3. 修改jbpm.hibernate.cfg.xml中的数据库连接信息。如果使用MySql,使用的方言一定要是org.hibernate.dialect.MySQL5InnoDBDialect[t2] 。
4. 数据库连接编码一定要是UTF-8。否则可能会在部署含有中文字符的流程定义时会抛异常,说sql语法错误。
说明:如果要改变jbpm.hibernate.cfg.xml的文件名称,需要做:
1、从JBPM_HOME/src/中拷贝jbpm.tx.hibernate.cfg.xml放到工程的src/下,然后进行修改。
2、修改jbpm.tx.hibernate.cfg.xml中的hibernate主配置文件的路径配置(指定的是相对于classpath的相对路径)。
1, 方法一:执行sql脚本文件${JBPM4.4_HOME}/install/src/db/create/jbpm.*.create.sql
2, 方法二:使用Hibernate的自动建表,在jbpm.hibernate.cfg.xml中配置:hibernate.hbm2ddl.auto=update。
例如:
public void testCreateSchema() { // hbm2ddl.auto=update
new org.hibernate.cfg.Configuration().configure("jbpm.hibernate.cfg.xml").buildSessionFactory();
}
ProcessDefinition,流程定义:
一个流程的步骤说明。如一个请假流程、报销流程、借款流程等,是一个规则。
例:
ProcessInstance,流程实例:
代表流程定义的一次执行。如张三昨天按请假流程请了一次假。一个流程实例包括了所有运行阶段, 其中最典型的属性就是跟踪当前节点的指针。
Execution,执行:
一般情况下,一个流程实例是一个执行树的根节点。
使用树状结构的原因在于, 这一概念只有一条执行路径, 使用起来更简单。 业务API不需要了解流程实例和执行之间功能的区别。 因此, API里只有一个执行类型来引用流程实例和执行。
假设汇款和存档可以同时执行,那么主流程实例就包含了2个用来跟踪状态的子节点:
Interacting with jBPM occurs through services. The service interfaces can be obtained from the ProcessEngine which is build from a Configuration. A ProcessEngine is thread safe and can be stored in a static member field.
使用默认的配置文件(jbpm.cfg.xml)生成Configuration并构建ProcessEngine:
ProcessEngine processEngine = new Configuration()
.buildProcessEngine();
或是使用如下代码获取使用默认配置文件的、单例的ProcessEngine对象:
ProcessEngine processEngine = Configuration.getProcessEngine();
或是使用指定的配置文件(要放到classPath下):
ProcessEngine processEngine = new Configuration()
.setResource("my-own-configuration-file.xml")
.buildProcessEngine();
jBPM所有的操作都是通过Service完成的,以下是获取Service的方式:
RepositoryService repositoryService = processEngine
.getRepositoryService();
ExecutionService executionService = processEngine
.getExecutionService();
TaskService taskService = processEngine
.getTaskService();
HistoryService historyService = processEngine
.getHistoryService();
ManagementService managementService = processEngine
.getManagementService();
各个Service的作用:
RepositoryService
管理部署对象和流程定义
ExecutionService
管理执行的,包括启动、推进、删除Execution等操作
TaskService
管理任务的
HistoryService
历史管理(执行完的数据管理,主要是查询)
IdentityService
jBPM的用户、组管理
ManagementService
方法调用链
每一个方法都是流程有关的一个业务操作,默认是一个独立的事务。
功能说明
相应的查询API
查询“流程定义”
ProcessDefinitionQuery processDefinitionQuery =
processEngine.getRepositoryService()
.createProcessDefinitionQuery();
查询“执行对象”
(流程实例)
ProcessInstanceQuery processInstanceQuery =
processEngine.getExecutionService() //
.createProcessInstanceQuery();
查询“任务”
TaskQuery taskQuery = //
processEngine.getTaskService()//
.createTaskQuery();
查询“执行历史”
(流程实例历史)
HistoryProcessInstanceQuery historyProcessInstanceQuery =
processEngine.getHistoryService()
.createHistoryProcessInstanceQuery();
查询“任务历史”
HistoryTaskQuery historyTaskQuery =
processEngine.getHistoryService()
.createHistoryTaskQuery();
以上列出的Query对象有:
1. ProcessDefinitionQuery
2. ProcessInstanceQuery
3. TaskQuery
4. HistoryProcessInstanceQuery
5. HistoryTaskQuery
这些Query对象的使用方法都是一致的,如下所示:
1, 添加过滤条件:调用其中的有关方法指定条件即可。如:
a) processDefinitionQuery.processDefinitionKey("设备购置计划")是指定查询key为”设备购置计划”的流程定义;
b) taskQuery.assignee("张三")是指定办理人为”张三”的任务。
2, 添加排序条件:
a) 调用 xxQuery.orderAsc(property),表示按某属性升序排列
b) 调用 xxQuery.orderDesc(property),表示按某属性降序排列
c) 可指定多个排序条件,就是代表第1顺序,第2顺序…等。
d) 属性名在各自的Query对象(接口)中有常量定义,如:
i. ProcessDefinitionQuery.PROPERTY_ID
ii. ProcessDefinitionQuery.PROPERTY_KEY
iii. TaskQuery.PROPERTY_NAME
iv. TaskQuery.PROPERTY_ASSIGNEE
3, 指定分页有关信息:
a) 调用方法xxQuery.page(firstResult, maxResults);
b) 这是指定first与max的值(就是Hibernate中的Query.setFirstResult()与Query.setMaxResults())
c) 如果没有调用这个方法,代表要查询出符合条件的所有记录。
4, 查询得到结果:
a) 调用方法xxQuery.list(); 表示查询列表
b) 调用方法 xxQuery.uniqueResult(); 表示查询唯一的结果
c) 调用方法xxQuery.count(); 表示查询符合条件的记录数量
没有更新功能
注意区分Deployment与ProcessDefinition
String deploymentId = processEngine.getRepositoryService()
.createDeployment()
.addResourceFromClasspath("process/test.jpdl.xml")
.addResourceFromClasspath("process/test.png")
.deploy();
String deploymentId = processEngine.getRepositoryService()
.createDeployment()
.addResourcesFromZipInputStream(zipInputStream)
.deploy();
1, .addResourceFromClasspath(resource); 可以调用多次以添加多个文件。文件重复添加也不会报错。
2, .addResourceFromInputStream(resourceName, inputStream)添加一个文件(使用InputStream)
3, .addResourcesFromZipInputStream(zipInputStream)添加多个文件,里面也可以有文件夹。
4, 以上方法可以在一起调用。
repositoryService.deleteDeployment(deploymentId);
repositoryService.deleteDeploymentCascade(deploymentId);
RepositoryService.createProcessDefinitionQuery()
// 1,构建查询
ProcessDefinitionQuery pdQuery = processEngine.getRepositoryService()
.createProcessDefinitionQuery()//
.orderAsc(ProcessDefinitionQuery.PROPERTY_NAME)//
.orderDesc(ProcessDefinitionQuery.PROPERTY_VERSION);
// 2,查询出总数量与数据列表
long count = pdQuery.count();
List<ProcessDefinition> list = pdQuery.page(0, 100).list();// 分页:取出前100条记录
// 3,显示结果
System.out.println(count);
for (ProcessDefinition pd : list) {
System.out.println("id=" + pd.getId()//
+ ",deploymentId=" + pd.getDeploymentId()//
+ ",name=" + pd.getName()//
+ ",version=" + pd.getVersion()//
+ ",key=" + pd.getKey()); //
}
// 1,查询,按version升序排序,则最大版本排在最后面
List<ProcessDefinition> all = processEngine.getRepositoryService()//
.createProcessDefinitionQuery()//
.orderAsc(ProcessDefinitionQuery.PROPERTY_VERSION)
.list();
// 2,过滤出所有不同Key的最新版本(因为最大版本在最后面)
Map<String, ProcessDefinition> map = new HashMap<String, ProcessDefinition>(); // map的key是流程定义的key,map的vlaue是流程定义对象
for (ProcessDefinition pd : all) {
map.put(pd.getKey(), pd);
}
Collection<ProcessDefinition> result = map.values();
// 3,显示结果
for (ProcessDefinition pd : result) {
System.out.println("deploymentId=" + pd.getDeploymentId()//
+ ",\t id=" + pd.getId()// 流程定义的id,格式:{key}-{version}
+ ",\t name=" + pd.getName()
+ ",\t key=" + pd.getKey()
+ ",\t version=" + pd.getVersion());
}
// 资源的名称,就是 jbpm4_lob 表中的 NAME_ 列表值
String deploymentId = "90001";
String resourceName = "test.png";
InputStream in = processEngine.getRepositoryService()
.getResourceAsStream(deploymentId, resourceName);
String processDefinitionId = "test-1"; // 流程定义的id
String activityName = "start1"; // 活动的名称
ActivityCoordinates c = processEngine.getRepositoryService()
.getActivityCoordinates(processDefinitionId, activityName);
System.out.println("x=" + c.getX()
+ ",y=" + c.getY()
+ ",width=" + c.getWidth()
+ ",height=" + c.getHeight());
说明:流程实例创建后,直接就到开始活动后的第一个活动,不会在开始活动停留。
ProcessInstance pi = processEngine.getExecutionService()
.startProcessInstanceByKey(processDefinitionKey);
// 准备流程变量
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("申请人", "张三");
variables.put("报销金额", 1000.00);
// 启动流程实例,并设置一些流程变量
ProcessInstance pi = processEngine.getExecutionService()
.startProcessInstanceByKey(processDefinitionKey, variables);
processEngine.getExecutionService().signalExecutionById(executionId);
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("审批结果", "同意");
processEngine.getExecutionService()
.signalExecutionById(executionId, variables);
String outcome= "to end1";
processEngine.getExecutionService()
.signalExecutionById(executionId, outcome);
String outcome= "to end1";
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("审批结果", "同意");
processEngine.getExecutionService()
.signalExecutionById(executionId, outcome, variables);
方式1:TaskService.findPersonalTasks(userId);
方式2:List<Task> list = taskService.createTaskQuery()
.assignee(userId)
.list();
// 显示任务信息
for (Task task : taskList) {
System.out.println("id=" + task.getId()// 任务的id
+ ",name=" + task.getName()// 任务的名称
+ ",assignee=" + task.getAssignee()// 任务的办理人
+ ",createTime=" + task.getCreateTime() // 任务的创建(生成)的时间
+ ",executionId=" + task.getExecutionId());// 任务所属流程实例的id
}
方式1: taskService.findGroupTasks(userId);
方式2: List<Task> list = processEngine.getTaskService()//
.createTaskQuery()//
.candidate(userId)//
.list();
String taskId = "420001";
processEngine.getTaskService().completeTask(taskId);
processEngine.getTaskService().completeTask(taskId, outcome);
processEngine.getTaskService().completeTask(taskId, outcome, variables);
String taskId = "420001";
// 1,设置为false代表:办理完任务后不向后移动(默认为true)
TaskImpl taskImpl = (TaskImpl) processEngine
.getTaskService().getTask(taskId);
taskImpl.setSignalling(false);
// 2,办理完任务
processEngine.getTaskService().completeTask(taskId);
1, TaskService.takeTask(taskId, userId),拾取组任务到个人任务列表中,如果任务有assignee,则会抛异常。
2, processEngine.getTaskService().assignTask(taskId, userId),转交任务给其他人,(如果任务有assignee,则执行这个方法代表重新分配。也可以把assignee设为null表示组任务没有人办理了)
ExecutionService.setVariable(executionId, name, value);
Object obj = executionService.getVariable(executionId, name);
6.6.1.2. 方式2:根据 taskId 设置或获取流程变量TaskService.setVariables(taskId, variables); // 一次设置多个变量
Object obj = executionService.getVariable(executionId, name);
6.6.1.3. 流程变量所支持的值的类型(jBPM User Guide,7.2. Variable types)7.2. Variable types
jBPM supports following Java types as process variables:
java.lang.String
java.lang.Long
java.lang.Double
java.util.Date
java.lang.Boolean
java.lang.Character
java.lang.Byte
java.lang.Short
java.lang.Integer
java.lang.Float
byte[] (byte array)
char[] (char array)
hibernate entity with a long id
hibernate entity with a string id
serializable
For persistence of these variable, the type of the variable is checked in the order of this list. The first match will determine how the variable is stored.
String processInstanceId = "test.10001";
processEngine.getExecutionService()
.endProcessInstance(processInstanceId, ProcessInstance.STATE_ENDED);
是.jpdl.xml的根元素,可以指定的属性有:
属性名
作用说明
name
流程定义的名称,用于显示。
key
流程定义的key,用于查询。
如未指定,则默认为name的值。
version
版本,如果指定,则不能与已有的流程定义的版本重复;如未指定,则此key的流程定义的第1个为版本1,以后的是版本递增(每次加1)
1, 一个活动中可以指定一个或多个Transition(Start中只能有一个,End中没有)。
a) 开始活动中只能有一个Transition。
b) 结束活动中没有Transition。
c) 其他活动中有1条或多条Transition
2, 如果只有一个,则可以不指定名称(名称是null);如果有多个,则要分别指定唯一的名称。
代表流程的开始边界,一个流程有且只能有一个Start活动。开始活动只能指定一个Transition。在流程实例启动后,会自动的使用这个唯一的Transition离开开始活动,到一下个活动。
代表流程的结束边界,可以有多个,也可以没有。如果有多个,则到达任一个结束活动,整个流程就都结束了;如果没有,则到达最后那个没有Transition的活动,流程就结束了。
功能:等待。
分配任务:
1, actor=#{String型的变量}
2, AssignmentHandler,需要在<task>元素中写<assignment-handler class="AssignmentHandlerImpl"/>子元素。
a) 指定的类要实现AssignmentHandler接口
b) 在其中可以使用Assignable.setAssignee(String),分配个人任务。
1,使用expression,如:expr="#{'to state2'}"
2,使用Handler,要实现DecisionHandler接口
3,如果同时配置了expression与Handler,则expression有效,忽略Handler。
这是多个分支并行(同时)执行的,并且所有的分支Execution都到Join活动后才离向后执行。
1,在<custom>元素中指定class属性为指定的类。
2,这个类要实现ExternalActivityBehaviour接口,其中有两个方法:
1,execute(ActivityExecution),节点的功能代码
2,signal(ActivityExecution, String, Map),在当前节点等待时,外部发信号时的行为
3,在execute()方法中,可以调用以下方法对流程进行控制
1,ActivityExecution.waitForSignal(),在当前节点等待。
2,ActivityExecution.takeDefaultTransition(),使用默认的Transition离开,当前节点中定义的第一个为默认的。
3,ActivityExecution.take(String transitionName),使用指定的Transition离开
4,ActivityExecution.end(),结束流程实例
4,也可以实现ActivityBehaviour接口,只有一个方法execute(ActivityExecution),这样就不能等待,否则signal时会有类转换异常。
1, 在根元素中,或在节点元素中,使用<on event="">元素指定事件,其中event属性代表事件的类型。
2, 在<on>中用子元素<event-listener class="EventListenerImpl" />,指定处理的类,要求指定的类要实现EventListener接口
3, 事件类型:
a) <on>元素放在根元素(<process>)中,可以指定event为start或end,表示流程的开始与结束。
b) <on>元素放在节点元素中,可以指定event为start或end,表示节点的进入与离开
c) 在Start节点中只有end事件,在End节点中只有start事件。
d) 在<transition>元素中直接写<event-listener class="">,就是配置事件。(因为在这里只有一个事件,所以不用写on与类型)
e) 在<task>元素中还可以配置assign事件,是在分配任务时触发的。
1,删除配置:<import resource="jbpm.tx.hibernate.cfg.xml" />
2,增加配置:<import resource="jbpm.tx.spring.cfg.xml" />
<!-- 配置ProcessEngine(整合jBPM4) -->
<!-- jbpmCfg是相对于classpath的相对路径,默认值为jbpm.cfg.xml -->
<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" />
@Test // 测试ProcessEngine
public void testProcessEngine() {
ProcessEngine processEngine = (ProcessEngine)ac.getBean("processEngine");
Assert.assertNotNull(processEngine);
}
如果做了JBPM4.4与Spring整合(使用了jbpm.tx.spring.cfg.xml),则在程序中就一定要使用Spring注入ProcessEngine,千万不能使用Configuration.getProcessEngine()生成ProcessEngine,因为这时内部的代码有以下逻辑:如果整合了Spring但没有ApplicationContext,就默认读取applicationContext.xml创建ApplicationContext实例并从中获取名为”ProcessEngine”的对象。而这时如果把pe = Configuration.getProcessEngine()写成某Spring中管理的bean的初始化代码,就会有无限循环,不停的创建ApplicationContext了!
1, 修改 jbpm.tx.hibernate.cfg.xml
a) 不让jBPM自行管理事务:去掉<standard-transaction-interceptor />
b) 让Jbpm使用SessionFactory.getCurrentSession():修改为 <hibernate-session current="true" />
2, 配置可以使用SessionFactory.getCurrentSession(),在jbpm.hibernate.cfg.xml 中配置:<property name="hibernate.current_session_context_class">thread</property>
3, 要使用同一个SessionFactory,且都要使用 SessionFactory.getCurrentSession() 获取 Session:
a) 同一个SessionFactory:SessionFactory sf = processEngine.get(SessionFactory.class)
b) 在 BaseDaoImpl 中增加:
i. getSession() { return HibernateUtils.getSessionFactory().getCurrentSession(); }
ii. getProcessEngine(){ return org.jbpm.api.Configuration.getProcessEngine(); }
4, 统一的打开与提交或回滚事务:使用 OpenSessionInViewFilter 控制事务。
at org.apache.jsp.WEB_002dINF.jsp.UserAction.loginUI_jsp._jspInit(loginUI_jsp.java:39)
at org.apache.jasper.runtime.HttpJspBase.init(HttpJspBase.java:52)
at org.apache.jasper.servlet.JspServletWrapper.getServlet(JspServletWrapper.java:159)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:329)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:320)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:266)
... 40 more
说明:原因是Jbpm的juel.jar, juel-engine.jar, juel-impl.jar包和Tomcat6.0中的el-api.jar包冲突了。
有三个解决办法:
1,方法一:在MyEclipse的Preferences -> MyEclipse -> Application Servers -> Tomcat -> .. -> Paths 中配置 Append to classpath,选中 juel.jar, juel-engine.jar, juel-impl.jar 这三个jar包就可以了。
2,方法二:将 juel.jar, juel-engine.jar, juel-impl.jar 这三个包复制到tomcat6下 lib/ 中,并删除原来的el-api.jar,
切记还要把工程中 WEB-INF\lib 下的 juel.jar, juel-engine.jar, juel-impl.jar 删除,不然还是要冲突。
3,方法三:换成tomcat5.5,就没有问题了。
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`itcastoa_20100909/jbpm4_execution`, CONSTRAINT `FK_EXEC_INSTANCE` FOREIGN KEY (`INSTANCE_`) REFERENCES `jbpm4_execution` (`DBID_`))
解决办法:把方言设为 MySQL5InnoDBDialect,不能是 MySQLDialect。
[t1]在java.util.logging.Level的javadoc中列出了可配置的所有输出级别为(由高到低):
· SEVERE (highest value)
· WARNING
· INFO
· CONFIG
· FINE
· FINER
· FINEST (lowest value)
[t2]如使用MySQLDialect,就会在流程实例结束时抛异常:com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`jbpm44_20101028/jbpm4_execution`, CONSTRAINT `FK_EXEC_INSTANCE` FOREIGN KEY (`INSTANCE_`) REFERENCES `jbpm4_execution` (`DBID_`))。