Jbpm4.4+hibernate3.5.4+spring3.0.4+struts2.1.8整合例子(附完整的请假流程例子,jbpm基础,常见问题解决)...

Jbpm4.4+hibernate3.5.4+spring3.0.4+struts2.1.8 整合例子(附完整的请假流程例子)。

1.jbpm4.4 测试环境搭建

2.Jbpm4.4+hibernate3.5.4+spring3.0.4+struts2.1. 整合环境搭建

3.jbpm4.4 基础知识

4.整合过程中常见问题的解决

5.请假流程例子( s2sh+jbpm )

6.总结及参考文章

jbpm4.4测试环境搭建

刚接触 jbpm 第一件事就是快速搭建环境,测试 jbpm 所给的例子。 Jbpm 是一个工作流引擎框架,如果没有 javaEE 开发环境, jbpm 也提供了安装脚本( ant ),一键提供安装运行环境。同时也可以将 jbpm 整合到 eclipse 或者 myeclipse 中。

一、快速搭建环境的步骤是:

1.安装 jbpm-myeclipse 插件,这个插件随 jbpm4.4 一起发布,位于 jbpm-4.4/install/src/gpd 目录下,这个安装好后,就可以在myeclipse 中编辑流程图了(可视化流程设计)

在myeclipse->help->myeclipse configuration centre->software->add site->add from archive file选择jbpm-4.4/install/src/gpd下的jbpm-gpd-site.zip

安装这个插件应该注意断网,避免其到网上更新。同时注意:需要选择

 

双击每一项,确保每一项被加入到了。

(说明:事实上不用选完,带source 的部件不用选择,为了省事就全部选择了)

提示:如果安装时不断网,jbpm 插件会自动到网上更新。同时会弹出一个错误窗口,安装速度异常缓慢。安装完成后,myeclipse 的references 菜单会变得面目全非。

二、搭建jbpm运行环境:

 

三、然后配置jpdl支持:

四、确定是否配置jbpm正确:

在myeclipse->new->other->

关于myeclipse中配置jbpm请参考jbpm的帮助文档,文档给的是在eclipse下配置jbpm。

五、测试运行环境:

新建一个java 项目,将jbpm-4.4/examples 下的src目录,copy到项目中。然后引入相关 jar包,jbpm.jar和lib下的所有包,先不考虑jar包选择问题。Src中包括了jbpm中的基本元素的使用。如start,state,end,sql,script,fork,join等。然后跟着jbpm的帮助文档,一点一点的学习。

说说以上文件的作用:第一个是jbpm的配置文件,在这个文件又引入其他的文件,在被引入的文件有一个文件包含了

<hibernate-configuration> <cfg resource="jbpm.hibernate.cfg.xml" />
</hibernate-configuration> <hibernate-session-factory />

用于创建 hibernate 的 sessionfactory 并交给 jbpm 的 IOC 容器管理。

第二个文件是 hibernate 配置文件,里面包含了 jbpm 框架需要的表的 hbm.xml 配置文件。

Jbpm4.4+hibernate3.5.4+spring3.0.4+struts2.1. 整合环境搭建

我的开发环境:

tomcat6.0.28+mysql5.1.30+ Jbpm4.4+hibernate3.5.4+spring3.0.4+struts2.1.8+myeclipse8.6+java jdk 6.0.23

在搭建环境之前,先认识一下jbpm 。JBPM在管理流程时,是需要数据库表的支持的,因为底层的逻辑有那么复杂。默认下载下来的配置,使用的是(hsqldb)内存数据库。实际应用中,我们就需要连接到我们的数据库里来。所以要事先建好相关的表,相应的 sql 文件在 /jbpm-4.4/install/src/db下,当然,你也可以使用hibernate的hibernate.hbm2ddl.auto自动建表,本人建议自己用建表语句,会少很多麻烦(本人在此处可没少碰麻烦)。 如果不结合其他的框架进行整个开发( 如:spring 、hibernate),JBPM4 也有自己的一套IOC 容器, 能后将自己的服务配置到IOC 容器中, 能够很容易的运行容器所配置的服务, 这样它也能够在代码中减少一陀一陀的工厂类等代码的调用, 降低了偶核性, 但是如果结合spring 框架来进行整个开发的话, 那么就有两个容器, 两个SessionFactory, 但是系统中只考虑一个容器来。对服务进行管理, 那么我们就要将jbpm4 的服务移植到spring 的IOC 容器中, 让spring 来进行统一管理, 这样通过spring 的容器来管理服务、事务。

整合目标:

将jbpm4 的IOC 移植到Spring 中,让spring 管理一个sessionfactory ,同时需要明确一点的是:jbpm4 对外提供服务是ProcessEngine 。如:

private RepositoryService repositoryService ;

private ExecutionService executionService ;

private HistoryService historyService ;

private TaskService taskService ;

private IdentityService identityService ;

上面这些服务就是通过 ProcessEngine 获得的。

Spring 配置文件:

<!--jbpm4.4 工作流   -->
    < bean id = "springHelper" class = "org.jbpm.pvm.internal.processengine.SpringHelper" >
       < property name = "jbpmCfg" value = "jbpm.cfg.xml" />
    </ bean >

< bean id = "sessionFactory" class = "org.springframework.orm.hibernate3.LocalSessionFactoryBean" > <!-- <property name="configLocation"> <value>classpath:jbpm.hibernate.cfg.xml</value> </property> --> < property name = "dataSource" ref = "dataSource" /> < property name = "hibernateProperties" > < props > < prop key = "hibernate.dialect" > org.hibernate.dialect.MySQLInnoDBDialect </ prop > < prop key = "hibernate.show_sql" > true </ prop > < prop key = "hibernate.connection.pool_size" > 1 </ prop > < prop key = "hibernate.format_sql" > true </ prop > < prop key = "hibernate.hbm2ddl.auto" > update </ prop > <!-- <prop key="hibernate.current_session_context_class">thread</prop> --> </ props > </ property > < property name = "mappingLocations" > < list > < value > classpath:jbpm.execution.hbm.xml </ value > < value > classpath:jbpm.history.hbm.xml </ value > < value > classpath:jbpm.identity.hbm.xml </ value > < value > classpath:jbpm.repository.hbm.xml </ value > < value > classpath:jbpm.task.hbm.xml </ value > </ list > </ property > </ bean > < bean class = "org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" > < property name = "locations" value = "classpath:jdbc.properties" ></ property > </ bean > < bean id = "dataSource" class = "org.springframework.jdbc.datasource.DriverManagerDataSource" > < property name = "driverClassName" value = "${jdbc.driverClassName}" /> < property name = "url" value = "${jdbc.url}" /> < property name = "username" value = "${jdbc.username}" /> < property name = "password" value = "${jdbc.password}" /> </ bean >

Jar 包选择:(没有选择,所以会有很多无用的)

基础知识:

在 jbpm4.4 目录 install/src/db/create 下有:

这些 sql 脚本所创建的表是 jbpm 能正常工作所必须的。我们可以直接运行这些 sql 在数据库建立起相关的表(共 18 张,如下):

每张表对应的含义:

(1) JBPM4_DEPLOYMENT

(2) JBPM4_DEPLOYPROP

(3) JBPM4_LOB :存储上传一个包含 png 和 jpdl.xml 的 zip 包 的相关数据 
       jbpm4_deployment 表多了一条记录 
       jbpm4_deployprop 表多了四条记录 , 对应 langid,pdid,pdkey,pdversion 
       jbpm4_lob 表多了二条记录 , 保存流程图 png 图片和 jpdl.xml

(4) JBPM4_HIST_PROCINST 与

(5) JBPM4_HIST_ACTINST 分别存放的是 Process Instance 、 Activity Instance 的历史记

(6) JBPM4_EXECUTION 主要是存放 JBPM4 的执行信息, Execution 机制代替了 JBPM3 的 Token 机制(详细参阅 JBPM4 的 PVM 机制)。

(7) JBPM4_TASK 存放需要人来完成的 Activities ,需要人来参与完成的 Activity 被称为 Task 。

(8) JBPM4_PARTICIPATION 存放 Participation 的信息, Participation 的种类有 Candidate 、 Client 、 Owner 、 Replaced Assignee 和 Viewer 。而具体的Participation 既可以是单一用户,也可以是用户组。

(9) JBPM4_SWIMLANE 。 Swim Lane 是一种 Runtime Process Role 。通过 Swim Lane ,多个 Task 可以一次分配到同一 Actor 身上。

(10) JBPM4_VARIABLE 存的是进行时的临时变量。

(11) JBPM4_HIST_DETAIL 保存 Variable 的变更记录。

(12) JBPM4_HIST_VAR 保存历史的变量。

(13) JBPM4_HIST_TASKTask 的历史信息。

(14) JBPM4_ID_GROUP

(15) JBPM_ID_MEMBERSHIP

(16) JBPM4_ID_USER 这三张表很常见了,基本的权限控制,关于用户认证方面建议还是自己开发一套, JBPM4 的功能太简单了,使用中有很多需要难以满足。

(17) JBPM4_JOB 存放的是 Timer 的定义。

(18) JBPM4_PROPERTY

你可以直接运行脚本,整合中有hibernate ,所以就用hibernate 自动创建。事实上jbpm 也是采用的hibernate 作为其持久化工具。

jbpm 4.4 中一些概念( 转自family168)

1、流程定义(ProcessDefinition): 对整个流程步骤的描述., 相当于我们在编程过程过程用到的类, 是个抽象的概念.

2、流程实例(ProcessInstance) 代表着流程定义的特殊执行例子, 相当于我们常见的对象. 他是类的特殊化.

  最典型的属性就是跟踪当前节点的指针.

3、流程引擎(ProcessEngine), 服务接口可以从 ProcessEngine 中获得, 它是从 Configuration 构建的, 如下:

ProcessEngine processEngine = new Configuration()
      .buildProcessEngine();

  从流程引擎中可以获得如下的服务:

RepositoryService repositoryService = processEngine.getRepositoryService();
ExecutionService executionService = processEngine.getExecutionService();
TaskService taskService = processEngine.getTaskService();
HistoryService historyService = processEngine.getHistoryService();
ManagementService managementService = processEngine.getManagementService();

4. 部署流程(Deploying a process):

RepositoryService 包含了用来管理发布资源的所有方法,

如下可以发布流程定义.

String deploymentid = repositoryService.createDeployment()
    .addResourceFromClasspath("*.jpdl.xml")
    .deploy();

这个id 的格式是(key)-{version}.

5. 删除流程定义:

repositoryService.deleteDeployment(deploymentId); 可以用级联的方式, 也可以remove

6. 启动一个新的流程实例:

ProcessInstance processInstance = executionService.startProcessInstanceByKey("key");

如果启动指定版本的流程定义 , 用下面的方法 :

ProcessInstance processInstance =executionService.startProcessInstanceById("ID");

7. 使用变量

当一个新的流程实例启动时就会提供一组对象参数。 将这些参数放在variables 变量里, 然后可以在流程实例创建和启动时使用。

Map<String,Object> variables = new HashMap<String,Object>();
variables.put("customer", "John Doe");
variables.put("type", "Accident");
variables.put("amount", new Float(763.74));
ProcessInstance processInstance =
    executionService.startProcessInstanceByKey("ICL", variables);

8. 执行等待的流向:

当使用一个 state 活动时,执行(或流程实例) 会在到达state 的时候进行等待,

直到一个signal (也叫外部触发器)出现。 signalExecution 方法可以被用作这种情况。

执行通过一个执行id (字符串)来引用。

executionService.signalExecutionById(executionId);

9.TaskService 任务服务:

TaskService 的主要目的是提供对任务列表的访问途径。 例子代码会展示出如何为id 为 johndoe 的

用户获得任务列表:

List<Task> taskList = taskService.findPersonalTasks("johndoe");

10、JBPM4 –ProcessEngine

在jBPM 内部通过各种服务相互作用。 服务接口可以从ProcessEngine 中获得, 它是从Configuration 构建的。

获得ProcessEngine : processEngine =Configuration.getProcessEngine ();

JBPM4 – RepositoryService

RepositoryService 包含了用来管理发布资源的所有方法。

部署流程

String deploymentid = repositoryService.createDeployment()  
    .addResourceFromClasspath("org/jbpm/examples/services/Order.jpdl.xml")  
    .deploy(); 

ZipInputStream zis = new ZipInputStream( this .getClass()
              .getResourceAsStream( "/com/jbpm/source/leave.zip" ));

// 发起流程,仅仅就是预定义任务,即在系统中创建一个流程,这是全局的,与具体的登陆 用户无关。然后,在启动流程时,才与登陆用户关联起来

String did = repositoryService .createDeployment()
              .addResourcesFromZipInputStream(zis).deploy(); 

通过上面的 addResourceFromClass 方法,流程定义 XML 的内容可以从文件,网址,字符串,输入流或 zip 输入流中获得。

每次部署都包含了一系列资源。每个资源的内容都是一个字节数组。 jPDL 流程文件都是以 .jpdl.xml 作为扩展名的。其他资源是任务表单和 java 类。

部署时要用到一系列资源,默认会获得多种流程定义和其他的归档类型。 jPDL 发布器会自动识别后缀名是 .jpdl.xml 的流程文件。

在部署过程中,会把一个 id 分配给流程定义。这个 id 的格式为 {key}-{version} , key 和 version 之间使用连字符连接。

如果没有提供 key (指在流程定义文件中,对流程的定义),会在名字的基础自动生成。生成的 key 会把所有不是字母和数字的字符替换成下划线。

同一个名称只能关联到一个 key ,反之亦然。

如果没有为流程文件提供版本号, jBPM 会自动为它分配一个版本号。请特别注意那些已经部署了的名字相同的流程文件的版本号。它会比已经部署的同一个 key 的流程定义里最大的版本号还大。没有部署相同 key 的流程定义的版本号会分配为 1 。

删除流程定义

删除一个流程定义会把它从数据库中删除。

repositoryService.deleteDeployment(deploymentId); 

如果在发布中的流程定义还存在活动的流程实例,这个方法就会抛出异常。

如果希望级联删除一个发布中流程定义的所有流程实例,可以使用 deleteDeploymentCascade 。

JBPM4 – TaskService

TaskService 的主要目的是提供对任务列表的访问途径。 例子代码会展示出如何为 id 为 johndoe 的用户获得任务列表

List<Task> taskList = taskService.findPersonalTasks("johndoe"); 

一般来说,任务会对应一个表单,然后显示在一些用户接口中。 表单需要可以读写与任务相关的数据。

// read task variables  
Set<String> variableNames = taskService.getVariableNames(taskId);  
variables = taskService.getVariables(taskId, variableNames); 

// write task variables  
variables = new HashMap<String, Object>();  
variables.put("category", "small");  
variables.put("lires", 923874893);  
taskService.setVariables(taskId, variables); 

taskSerice 也用来完成任务。 
taskService.completeTask(taskId);  
taskService.completeTask(taskId, variables);  
taskService.completeTask(taskId, outcome);  
taskService.completeTask(taskId, outcome, variables); 

这些 API 允许提供一个变量 map ,它在任务完成之前作为流程变量添加到流程里。 它也可能提供一个 “ 外出 outcome” ,这会用来决定哪个外出转移会被选中。 逻辑如下所示:

如果一个任务拥有一个没用名称的外向转移: 

taskService.getOutcomes() 返回包含一个 null 值集合,。 
taskService.completeTask(taskId) 会使用这个外向转移。 
taskService.completeTask(taskId, null) 会使用这个外向转移。 
taskService.completeTask(taskId, "anyvalue") 会抛出一个异常。

如果一个任务拥有一个有名字的外向转移: 

taskService.getOutcomes() 返回包含这个转移名称的集合。 
taskService.completeTask(taskId) 会使用这个单独的外向转移。 
taskService.completeTask(taskId, null) 会抛出一个异常(因为这里没有无名称的转移)。 
taskService.completeTask(taskId, "anyvalue") 会抛出一个异常。 
taskService.completeTask(taskId, "myName") 会根据给定的名称使用转移。

如果一个任务拥有多个外向转移,其中一个转移没有名称,其他转移都有名称: 

taskService.getOutcomes() 返回包含一个 null 值和其他转移名称的集合。 
taskService.completeTask(taskId) 会使用没有名字的转移。 
taskService.completeTask(taskId, null) 会使用没有名字的转移。 
taskService.completeTask(taskId, "anyvalue") 会抛出异常。 
taskService.completeTask(taskId, "myName") 会使用名字为 'myName' 的转移。

如果一个任务拥有多个外向转移,每个转移都拥有唯一的名字: 

taskService.getOutcomes() 返回包含所有转移名称的集合。 
taskService.completeTask(taskId) 会抛出异常,因为这里没有无名称的转移。 
taskService.completeTask(taskId, null) 会抛出异常,因为这里没有无名称的转移。 
taskService.completeTask(taskId, "anyvalue") 会抛出异常。 
taskService.completeTask(taskId, "myName") 会使用名字为 'myName' 的转移。

任务可以拥有一批候选人。候选人可以是用户也可以是用户组。用户可以接收自己是候选人的任务。接收任务的意思是用户会被设置为被分配给任务的人。在那之后,其他用户就不能接收这个任务了。

人们不应该在任务做工作,除非他们被分配到这个任务上。用户界面应该显示表单,如果他们被分配到这个任务上,就允许用户完成任务。对于有了候选人,但是还没有分配的任务,唯一应该暴露的操作就是 “ 接收任务 ” 。

JBPM4 – ExecutionService

最新的流程实例 -- ByKey 
下面是为流程定义启动一个新的流程实例的最简单也是 最常用的方法:

ProcessInstance processInstance = executionService.startProcessInstanceByKey ("ICL");

上面 service 的方法会去查找 key 为 ICL 的最新版本的流程定义, 然后在最新的流程定义里启动流程实例。

当 key 为 ICL 的流程部署了一个新版本, startProcessInstanceByKey 方法会自动切换到最新部署的版本。

原来已经启动的流程,还是按照启动时刻的版本执行。

指定流程版本 -- ById 
换句话说,你如果想根据特定的版本启动流程实例, 便可以使用流程定义的 id 启动流程实例。如下所示:

ProcessInstance processInstance = executionService.startProcessInstanceById ("ICL-1");

使用 key

我们可以为新启动的流程实例分配一个 key( 注意: 这个 key 不是 process 的 key ,而是启动的 instance 的 key ) ,这个 key 是用户执行的时候定义的,有时它会作为 “ 业务 key” 引用。一个业务 key 必须在流程定义的所有版本范围内是唯一的。通常很容易在业务流程领域找到这种 key 。比如,一个订单 id 或者一个保险单号。 

ProcessInstance processInstance = executionService.startProcessInstanceByKey ("ICL", "CL92837");

// 2 个参数: 
//  第一个参数 processkey ,通过这个 key 启动 process 的一个实例 
//  第二个参数为这里所说的实例 key(instance key)

key 可以用来创建流程实例的 id ,格式为 {process-key}.{execution-id} 。所以上面的代码会创建一个 id 为 ICL.CL92837 的流向( execution )。

如果没有提供用户定义的 key ,数据库就会把主键作为 key 。 这样可以使用如下方式获得 id : 

ProcessInstance processInstance = executionService.startProcessInstanceByKey ("ICL"); 
String pid = processInstance.getId();

最好使用一个用户定义的 key 。 特别在你的应用代码中,找到这样的 key 并不困难。提供给一个用户定义的 key ,你可以组合流向的 id ,而不是执行一个基于流程变量的搜索 - 那种方式太消耗资源了。

使用变量

当一个新的流程实例启动时就会提供一组对象参数。 将这些参数放在 variables 变量里, 然后可以在流程实例创建和启动时使用。 

Map<String,Object> variables = new HashMap<String,Object>(); 
variables.put("customer", "John Doe"); 
variables.put("type", "Accident"); 
variables.put("amount", new Float(763.74));
ProcessInstance processInstance = executionService.startProcessInstanceByKey ("ICL", variables);

启动 instance

启动 instance ,必须要知道 processdefinition 的信息: processdefinition 可以通过 2 种方式获取: 
ByKey :通过 ProcessKey ,启动该 Process 的最新版本 
ById :通过 Process 的 ID ,启动该 Process 的特定的版本

其他的参数,其余还可以在启动 Instance 的时候,给流程 2 个参数: 
InstanceKey :这个 instanceKey 必须在整个流程定义的所有范围版本中唯一,如果用户不给于提供,系统也会自己生成; 
一个 Map<String, ?> 表:启动流程时候给予的变量信息

执行等待的流向

当使用一个 state 活动时,执行(或流程实例)会在到达 state 的时候进行等待,直到一个 signal (也叫外部触发器)出现。 signalExecution 方法可以被用作这种情况。执行通过一个执行 id (字符串)来引用。

在一些情况下,到达 state 的执行会是流程实例本身。但是这不是一直会出现的情况。在定时器和同步的情况,流程是执行树形的根节点。所以我们必须确认你的 signal作用在正确的流程路径上。

获得正确的执行的比较好的方法是给 state 活动分配一个事件监听器,像这样: 

<state name="wait">  
  <on event="start">  
    <event-listener class="org.jbpm.examples.StartExternalWork" />  
  </on>  
  ...  
</state> 

在事件监听器 StartExternalWork 中,你可以执行那些需要额外完成的部分。在这个事件监听器里,你也可以通过 execution.getId() 获得确切的流程 id 。那个流程 id,在额外的工作完成后,你会需要它来提供给 signal 操作的:

executionService.signalExecutionById (executionId); 

这里有一个可选的(不是太推荐的)方式,来获得流程 id ,当流程到达 state 活动的时候。只可能通过这种方式获得执行 id ,如果你知道哪个 JBPM API 调用了之后,流程会进入 state 活动:

// assume that we know that after the next call  
// the process instance will arrive in state external work  
ProcessInstance processInstance = executionService.startProcessInstanceById(processDefinitionId); 

// or ProcessInstance processInstance =  
//  executionService.signalProcessInstanceById(executionId);  
Execution execution = processInstance.findActiveExecutionIn("external work");  
String executionId = execution.getId(); 

JBPM4 – HistoryService

在流程实例执行的过程中,会不断触发事件。从那些事件中,运行和完成流程的历史信息会被收集到历史表中。

HistoryService 提供了 对那些信息的访问功能。

如果想查找某一特定流程定义的所有流程实例, 可以像这样操作:

List<HistoryProcessInstance> historyProcessInstances = historyService  
  .createHistoryProcessInstanceQuery()  
  .processDefinitionId("ICL-1")  
  .orderAsc(HistoryProcessInstanceQuery.PROPERTY_STARTTIME)  
  .list(); 

单独的活动流程也可以作为 HistoryActivityInstance 保存到历史信息中。

List<HistoryActivityInstance> histActInsts = historyService  
    .createHistoryActivityInstanceQuery()  
    .processDefinitionId("ICL-1")  
    .activityName("a")  
    .list(); 

也可以使用简易方法 avgDurationPerActivity 和 choiceDistribution 。可以通过 javadocs 获得这些方法的更多信息。

有时,我们需要获得指定流程实例已经过的节点的完整列表。下面的查询语句可以用来获得所有已经执行的节点列表:

List<HistoryActivityInstance> histActInsts = historyService  
    .createHistoryActivityInstanceQuery()  
    .processInstanceId("ICL.12345")  
    .list(); 

上面的查询与通过 execution id 查询有一些不同。有时 execution id 和流程实例 id 是不同的, 当一个节点中使用了定时器, execution id 中就会使用额外的后缀, 这就会导致当我们通过 execution id 查询时, 这个节点不会出现在结果列表中。

整合过程中常见问题的解决

错误 1 : 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 javax/el/ExpressionFactory used in the signature

错误的解决办法。( Tomcat6.0.28 )

exception

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/OnDuty/wfmanage_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 javax/el/ExpressionFactory used in the signature org.apache.jasper.servlet.JspServlet.service(JspServlet.java:275) javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

root cause

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/OnDuty/wfmanage_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 javax/el/ExpressionFactory used in the signature org.apache.jsp.OnDuty.wfmanage_jsp._jspInit(wfmanage_jsp.java:27) org.apache.jasper.runtime.HttpJspBase.init(HttpJspBase.java:52) org.apache.jasper.servlet.JspServletWrapper.getServlet(JspServletWrapper.java:159) org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:329) org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:342) org.apache.jasper.servlet.JspServlet.service(JspServlet.java:267) javax.servlet.http.HttpServlet.service(HttpServlet.java:717)

原因是项目中WEB-INF/lib 中的三个jar 包 (juel.jar, juel-engine.jar, juel-impl.jar ) 和tomcat6 下lib 中jar 包( el-api.jar )冲突

解决方法: 

方法一:换成tomcat5.5 一点问题也没有了(有新版本了还用老版本?)

方法二:将 juel.jar, juel-engine.jar, juel-impl.jar 这三个包复制到tomcat6 下lib 中,并删除原来的 el-api.jar ,切记要把WEB-INF/lib 中的juel.jar, juel-engine.jar, juel-impl.jar 删除。不然还是要冲突。

错误 2 : org.jbpm.api.JbpmException: No unnamed transitions were found for the task '??'

如果一个任务拥有一个没用名称的外向转移:

taskService.getOutcomes() 返回包含一个 null 值集合。taskService.completeTask(taskId) 会使用这个外向转移。taskService.completeTask(taskId, null) 会使用这个外向转移。 taskService.completeTask(taskId, "anyvalue") 会抛出一个异常。

如果一个任务拥有一个有名字的外向转移:

gtaskService.getOutcomes() 返回包含这个转移名称的集合。 taskService.completeTask(taskId) 会使用这个单独的外向转移。taskService.completeTask(taskId, null) 会抛出一个异常(因为这里没有无名称的转移)。 taskService.completeTask(taskId, "anyvalue")会抛出一个异常。 taskService.completeTask(taskId, "myName") 会根据给定的名称使用转移。

如果一个任务拥有多个外向转移,其中一个转移没有名称,其他转移都有名称:

taskService.getOutcomes() 返回包含一个 null 值和其他转移名称的集合。 taskService.completeTask(taskId) 会使用没有名字的转移。taskService.completeTask(taskId, null) 会使用没有名字的转移。 taskService.completeTask(taskId, "anyvalue") 会抛出异常。taskService.completeTask(taskId, "myName") 会使用名字为 'myName' 的转移。

如果一个任务拥有多个外向转移,每个转移都拥有唯一的名字:

taskService.getOutcomes() 返回包含所有转移名称的集合。 taskService.completeTask(taskId) 会抛出异常,因为这里没有无名称的转移。taskService.completeTask(taskId, null) 会抛出异常,因为这里没有无名称的转移。 taskService.completeTask(taskId, "anyvalue") 会抛出异常。 taskService.completeTask(taskId, "myName") 会使用名字为 'myName' 的转移。

解决方案:

根据以上分析,可得到解决方案:

1 、只拥有一个外向转移时(对应上文所述 1 、 2 情况):

Map map = new HashMap();

map.put("",…… ) // 各种参数

taskService.setVariables(taskId,map);

taskService.completeTask(taskId);

2 、拥有多个外向转移时(上文 3 、 4 种情况):

Map map = new HashMap();

map.put("",…… ) // 各种参数

taskService.setVariables(taskId,map);

// 如想转移至有名称的外向转移:

taskService.completeTask(taskId," 外向转移名称 ");

// 如想转移至无名称的外向转移:

taskService.completeTask(taskId);

错误3 :*.jpdl.xml 中文乱码问题。

在myeclipse 的配置文件myeclipse.ini 中加入:

-DFile.encoding=UTF-8

请假流程例子( s2sh+jbpm )

流程图:

<? xml version = "1.0" encoding = "UTF-8" ?>

< process name = "leave" xmlns = "http://jbpm.org/4.4/jpdl" >
   < start g = "214,37,48,48" name = "start1" >
      < transition g = "-47,-17" name = "to 申请 " to = " 申请 " />
   </ start >

< task assignee = "#{owner}" form = "request.html" g = "192,126,92,52" name = " 申请 " > < transition g = "-71,-17" name = "to 经理审批 " to = " 经理审批 " /> </ task > < task assignee = "manager" form = "manager.html" g = "194,241,92,52" name = " 经理审批 " > < transition g = "-29,-14" name = " 批准 " to = "exclusive1" /> < transition g = "105,267;103,152:-47,-17" name = " 驳回 " to = " 申请 " /> </ task > < decision expr = "#{day > 3 ? ' 老板审批 ' : ' 结束 '}" g = "218,342,48,48" name = "exclusive1" > < transition g = "415,367:-47,-17" name = " 老板审批 " to = " 老板审批 " /> < transition g = "-31,-16" name = " 结束 " to = "end1" /> </ decision > < end g = "219,499,48,48" name = "end1" /> < task assignee = "boss" form = "boss.html" g = "370,408,92,52" name = " 老板审批 " > < transition g = "415,524:-91,-18" name = " 结束 " to = "end1" /> </ task > </ process >

步骤:

发布流程:将画好的流程图,发布到jbpm 框架中(放到jbpm 数据库中),这个流程是全局的,与用户无关。发布流程后会返回一个流程id ,我们会用流程id 得到 ProcessDefinition 流程定义。 发布方法如下:

public void deploy() {
       // repositoryService.createDeployment().addResourceFromClasspath(
       // "/com /jbpm /source/leave.jpdl.xml").deploy();
       ZipInputStream zis = new ZipInputStream( this .getClass()
              .getResourceAsStream( "/com/jbpm/source/leave.zip" ));

       // 发起流程,仅仅就是预定义任务,即在系统中创建一个流程,这是全局的,与具体的登陆 用户无关。然后,在启动流程时,才与登陆用户关联起来
       String did = repositoryService .createDeployment()
              .addResourcesFromZipInputStream(zis).deploy();
    }

启动流程:流程定义好后,并不能用,我们需要将其实例化,实例化流程将关联用户,同时将实例写入数据库中。启动流程方法如下:

public void start(String id, Map<String , Object> map) {
       executionService .startProcessInstanceById(id, map);
    }

流程一旦启动就通过start 节点,流到下一个任务节点。

获取待办任务列表:不同的用户登录后通过如下方式获得自己的待办任务

 public List<Task> getTasks(String roleName) {
       return taskService .findPersonalTasks(roleName);
    }

在流程中每一个任务节点都关联了一个 action 请求,用于处理待办任务的视图( view )

不多说了,哥就相信源码: http://download.csdn.net/source/3223403

另一例子:http://zwllxs.iteye.com/blog/726303

下载地址:http://download.csdn.net/source/2671387

总结及参考文章:

http://www.blogjava.net/paulwong/archive/2009/09/07/294114.html

http://zjkilly.iteye.com/blog/738426

http://fish119.iteye.com/blog/779379

http://alimama.iteye.com/blog/567651

其他参考资料:family168 网,http://code.google.com/p/family168/downloads/list

控制流程活动:

原子活动:

本文转自:http://www.blogjava.net/wangxinsh55/archive/2011/07/24/354925.html

转载于:https://www.cnblogs.com/dreammyle/p/4016372.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1.JPDL的流程定义元素 1)第一层:GraphElement 这个容易理解,因为在画流程定义时,每个拖拉的对象都是一个graph的元素。GraphElement有四个属性: (1)processDefine 表示当前元素属于哪个流程定义 (2)events 表示可以接收哪些event (3)name 名字 (4)exceptionHandlers 异常处理类集合(List) 2)第二层:node、processDefinition、Transition、Task 它们都继承自GraphElement (1)processDefinition表示流程定义(implements NodeCollection),它有下面的属性:name、version、nodes、startState。nodes表示流程中所有的node,startState用于启动流程时找到首节点。 (2)Transition表示转移,它有三个属性:from(Node),to(Node),supportedEventTypes表示支持的event类型 (3)node表示节点,它有四个属性:leaving transitions、arriving transitions、action、superState。 (4)Task 定义任务 3)第三层:各种不同的node 它们都继承自node。 Decision、EndState、Fork、Join、Merge、Milestone、 InterleaveEnd、InterleaveStart、ProcessState、State。 2.jBPM的token jbpm中最重要的概念,应该是令牌(Token)和信令(Signal)。在整个流程实例运行过程中,我们可以迅速的利用token得到其当前的current state。在解决“并行”等(比如Fork)问题时,jBpm让Token对象维护了父子关系,这种关系在涉及到Fork的时候会产生。 jBpm让Token这个对象身兼了多种使命: (1)快速定位current state (2)用于fork,join算法 (3)用于告知任务执行者的任务索引。 如下代码: //pd是process definition,pi是process instance ProcessInstance pi = new ProcessInstance( pd ); //得到根令牌 Token token = pi.getRootToken(); //发信令 token.signal(); Token的signal方法也可以传入transition参数,这个方法把信令发送给Token,这样,令牌将被激活,并沿指定的transition离开当前的状态(如果没有指定transition,将沿缺省的transition 离开当前状态)。 jbpm是怎么实现的呢?其实很简单: 1)Token记录了当前的状态(current state),只有当前的状态(或称节点)拥有该令牌 2)向TOKEN发signal后,当前状态收到该signal 3)当前状态把令牌传给signal中指定的transition 4)transition收到令牌后,不强占,马上把令牌传给下个状态. 5)根据令牌的位置,流程的状态已经发生改变. 3.process definition 一个process definition代表了一个正式的业务流程,它以一个流程图为基础。这个流程图由许多node和transition组成。每个node在这个流程图里都有着各自特殊的类型,这些不同的类型决定了node在运行时的不同行为。一个process definition只有一个start state 。 4.token 一个token代表了一条执行路径,它包含了这条执行路径的当前的执行状态(current state)。 5.process instance 一个process instance(流程实例)即一个process definition(流程定义)的流程执行实例。一个process definition可以对应多个process instance。当一个process instance被创建的时候,一个主执行路径token同时被创建,这个token叫做root token,它指向流程定义的start state(processDefinition.getStartState()==token.getNode())。 6.signal 一个signal 发送给token通知token 继续流程的执行。如果signal 没有指定transition,token将沿缺省的transition离开当前状态,如果signal 指定transition,token将沿指定的transition离开当前的状态。看源代码可以看到发给process instance的signal 其实都是发送给了root token。 7.Actions jbpm提供了灵活的action ,当流程执行,token 进入node和transition时,会触发相应的一些event(事件)。在这些event上上我们自己写的action,就会带动action 的执行。action里是我们自己的相关java操作代码,非常方便。注意的是event(事件)是内置的,无法扩展。另外,action也可以直接挂在node上,而不依赖于event(事件)的触发,这个很重要。 8.node 一个流程图由许多node和transition组成。每个node都有一种类型,这个类型决定了当流程执行到这个node时的不同行为。jbpm有一组node type可以供你选择,当然你可以定制自己node 。 node的作用 node有两个主要的作用: 1)执行java代码,比如说创建task instance(任务实例)、发出通知、更新数据库等等。很典型的就是在node 上挂上我们的action 2) 控制流程的执行: A、等待状态:流程进入到这个node时将处于等待状态,直到一个signal 的发出 B、流程将沿着一个leaving transition越过这个node,这种情况特殊一点,需要有个action挂在这个node上(注意这个action不是event触发的!),action中将会调用到API里 executionContext.leaveNode(String transitionName),transitionName即这里的leaving transition名字。 C、创建新的执行路径: 很典型的就是fork node。流程在这里会分叉,产生新的执行路径。这样就创建了新的token,每个新的token代表一个新的执行路径。注意的是,这些新的token和产生前的token是父子关系! D、结束执行路径:一个node可以结束一条执行路径,这同样意味着相应的token的结束和流程的结束。 9.流程图中的node type 1)task-node 一个task-node可以包含一个或多个task,这些task分配给特定的user。当流程执行到task-node时,task instance将会被创建,一个task对应一个task instance。task instances 创建后,task-node就处于等待状态。当所有的task instances被特定的user执行完毕后,将会发出一个新的signal 到token,即流程继续执行。 2)state state是一个纯粹的wait state(等待状态)。它和task-node的区别就是它不会创建task instances。很典型的用法是,当进入这个节点时(通过绑定一个action到node-enter event),发送一条消息到外部的系统,然后流程就处于等待状态。外部系统完成一些操作后返回一条消息,这个消息触发一个signal 到token,然后流程继续执行。(不常用) 3)decision 当需要在流程中根据不同条件来判断执行不同路径时,就可以用decision节点。两种方法:最简单的是在transitions里增加condition elements(条件),condition是beanshell script写的,它返回一个boolean。当运行的时候,decision节点将会在它的 leaving transitions里循环,同时比较 leaving transitions里的condition,最先返回'true'的condition,那个leaving transitions将会被执行;作为选择,你可以实现DecisionHandler接口,它有一个decide()方法,该方法返回一个String(leaving transition的名字)。 4)fork fork节点把一条执行路径分离成多条同时进行(并发)的执行路径,每条离开fork节点的路径产生一个子token。 5)join 默认情况下,join节点会认为所有到达该节点的token都有着相同的父token。join 节点会结束每一个到达该节点的token,当所有的子token都到达该节点后,父token会激活。当仍然有子token处于活动状态时,join 节点是wait state(等待状态)。 6)node node节点就是让你挂自己的action用的(注意:不是event触发!),当流程到达该节点时,action会被执行。你的action要实现ActionHandler接口。同样,在你的action里要控制流程。 10. Actions的说明 存在两种action,一种是 event触发的action,一种是挂在node 节点的action。要注意它们的区别,event触发的action无法控制流程,也就是说它无法决定流程经过这个节点后下一步将到哪一个leaving transition;而挂在node 节点的action就不同,它可以控制流程。不管是哪一种action都要实现ActionHandler接口。 11. Task(任务) jbpm一个相当重要的功能就是对任务进行管理。Task(任务)是流程定义里的一部分,它决定了task instance的创建和分配。Task(任务)可以在task-node节点下定义,也可以挂在process-definition节点下。最普遍的方式是在task-node节点下定义一个或多个任务。默认情况下,流程在task-node节点会处于等待状态,直到所有的任务被执行完毕。任务的名称在整个流程中必须是唯一的。一个TaskNode对应多个Task。 对于这样的流程定义: xml 代码 1. <task-node name='a'> 2. <task name='laundry' /> 3. <task name='dishes' /> 4. <task name='change nappy' /> 5. <transition to='b' /> 6. </task-node> 只有当节点中的三个任务都完成后,流程才进入后面的节点 对于这样的流程定义: xml 代码 1. <task-node name='a' signal='first'>> 2. <task name='laundry' /> 3. <task name='dishes' /> 4. <task name='change nappy' /> 5. <transition to='b' /> 6. </task-node> 当第一个任务完成后,token就指向后面的节点 对于这样的流程定义: xml 代码 1. <task-node name='a' signal='never'>> 2. <task name='laundry' /> 3. <task name='dishes' /> 4. <task name='change nappy' /> 5. <transition to='b' /> 6. </task-node> 三个任务都完成后,token仍然不会指向后面的节点;需要自己手动调用processInstance.signal()才会驱动流程到下面的节点。 对于这样的流程定义: xml 代码 1. <task-node name='a' signal='unsynchronized'>> 2. <task name='laundry' /> 3. <task name='dishes' /> 4. <task name='change nappy' /> 5. <transition to='b' /> 6. </task-node> token不会在本节点停留,而是直接到后面的节点 12. jbpm的任务管理实现 一个Task instance(任务实例)可以被分配给一个actorId (java.lang.String)。所有的Task instance都被保存在数据库中的表jbpm_taskinstance里。当你想得到特定用户的任务清单时,你就可以通过一个与用户关联的actorId来查询这张表。 一个流程定义有一个TaskMgmtDefinition;一个TaskMgmtDefinition对应多个swimlane,同时对应多个task;一个swimlane有多个task,可以从TaskMgmtDefinition中通过task的名称直接获取相应的task; swimlane对象有四个属性,分别是name(名字)、assignmentDelegation(分配代理类)、taskMgmtDefinition、tasks(Set 对应多个task),可以增加task task对象主要的属性:taskMgmtDefinition、swimlane、assignmentDelegation、taskNode,需要注意的是swimlane和assignmentDelegation中间只是可以一个属性有值,因为它们都和任务的分配有关系。 一个流程实例有一个TaskMgmtInstance;一个TaskMgmtInstance对应多个swimlaneInstance,同时对应多个taskInstance;一个swimlaneInstance有多个taskInstance,可以从TaskMgmtInstance中直接获取相应的taskInstance; swimlaneInstance对象主要有五个属性,分别是name、actorId、pooledActors(Set)、swimlane、taskMgmtInstance。 taskInstance对象的主要属性:name、actorId、task、swimlaneInstance、taskMgmtInstance、pooledActors。 当对任务进行分配时,一般需要实现AssignmentHandler这个接口,这个接口的方法只有一个: void assign( Assignable assignable, ExecutionContext executionContext) throws Exception; 一个典型的实现(把名字是'change nappy'的任务交给NappyAssignmentHandler这个类来分配) xml 代码 1. <task name='change nappy'> 2. <assignment class='org.jbpm.tutorial.taskmgmt.NappyAssignmentHandler' /> 3. task> NappyAssignmentHandler类: java 代码 1. public void assign(Assignable assignable, ExecutionContext executionContext) { 2. assignable.setActorId("papa"); 3. } 同样,Assignable只是一个接口,它有两个方法:setActorId()和setPooledActors(),Assignable的具体实现类也是两个:swimlaneInstancehe和taskInstance。这样就不不难理解整个任务分配流程了: 1、流程进入TaskNode节点,执行TaskNode类的execute()方法,该方法首先获得TaskMgmtInstance实例,然后通过它来创建TaskInstance。taskMgmtInstance.createTaskInstance(task, executionContext); 2、在上面的createTaskInstance(task, executionContext)里,该方法调用了taskInstance.assign(executionContext)对taskInstance进行分配。 3、在assign(executionContext)方法里,首先会判断task属性里是否存在swimlane,如果有的话,这个taskInstance就会分配给swimlane指定的ActorId或 PooledActors;如果不存在,再去找task属性里 assignmentDelegation(分配代理类)通过代理类(即我们自己写的实现AssignmentHandler这个接口的类)指定ActorId或 PooledActors。 13. jbpm的用户角色管理 jbpm在用户角色管理上共设计了四个类:Entity、Membership、Group、User。 Entity类是其他三个类的父类,它包含了两个属性:name(String)、permissions(Set); User类继承Entity类,包含三个属性:password(String)、email(String)、memberships(Set); Group类继承Entity类,包含四个属性: type(String)、parent(Group)、children(Set)、memberships(Set); Membership类继承Entity类,包含三个属性:role(String)、user(User)、group(Group) 很明显,一个user对应一个用户,一个group对应一个用户组,它们之间通过membership关联,并且一个user可以属于多个不同类型(type)的group,user和 group之间是多对多的关系。Membership类的role属性个人感觉用途不大,反倒是name属性代表了user在group里的role(角色)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值