《partner4java 讲述jBPM4》之第二步:描述工作流程 & 处理工作流程

通过上一章的工作流开发展示,我们可以得出结论(“白话”):

1、“环境”:

首先jBPM4给开发者提供了一套环境,如:java框架、数据库。

数据库无可厚非是我们流程的各数据保存的地方(因为保存数据总要有一定的规则,所以数据表是已定义好的);java框架为jBPM4提供的一套开发架构,用于分析流程定义、管理流程等。

2、“描述工作流程”:

从架构师的思路出发,我们的流程千变万化,那么如何告知我们的架构具体某个业务流程应该按什么样的流程执行呢?

就当下流行的描述“手法”-- xml ,但是这个xml应该按照一定的规则去定义吧(且解析总需要有规则)?于是出现了“jPDL”,被称为jBPM最宝贵的资产。但是,当某种技术比较流行时,一些大佬就坐不住了,他们就会推出一些规范,于是乎出现了“BPEL”。但是很抱歉“BPEL”就jBPM4来讲并没有完全支持,所以我们后面的重点在“jPDL”。

3、“流程运转”:

当流程使用XML定义之后,无可厚非要转换成Java对象并保存进数据库(也就是发布),这里我们借助了Hibernate。

转换为“对象后”,我们就应该“驱赶”流程定义对象按照描述的流程进行流转。“驱赶”之前我们要赋予流程定义“生命”,换句话说就是以一些特性或者“某人”的名义启动,每次启动都为一个单独的流程实例

当启动一个流程实例之后,就需要“驱赶”,也就是向下执行

工作流比较大的特点就应该是每一步和具体人员相关,或转言之每一步都应该由指定的人或组处理,那么相对于指定的人这一步工作为任务

(大体相关类可见上一篇最后图)


一:环境

数据库,脚本就包含在我们的下载包中:jbpm-4.4\install\src\db

核心数据表:
jbpm4_deployment 流程定义的部署记录
jbpm4_deployprop 已部署的流程定义的具体属性
jbpm4_lob 流程定义的相关资源,包括jPDL XML、图片、用户代码Java类等,以二进制格式统一存储在此表中
jbpm4_job 异步活动或定时执行的Job记录
jbpm4_variable 流程实例的变量
jbpm4_execution 流程实例及执行对象
jbpm4_swimlane 任务泳道。这属于流程定义的数据
jbpm4_participation 任务参与者(任务的相关用户,区别于任务的分配人)。这属于流程实例的数据
jbpm4_task 流程实例的任务记录

历史数据表:
jbpm4_hist_procinst 保存历史的流程实例记录
jbpm4_hist_actinst 保存历史的活动实例记录
jbpm4_hist_task 保存历史的任务实例记录
jbpm4_hist_var 保存历史的流程变量数据
jbpm4_hist_detail 保存流程实例、活动实例、任务实例运行过程中历史明细数据,例如起止时间、平均处理时间、任务注释等,为效率分析等流程数据挖掘服务提供基础数据支持。

身份认证数据表:
jbpm4_id_user 保存用户记录
jbpm4_id_membership 保存用户和用户组之间的关联关系
jbpm4_id_group 保存用户组记录

引擎属性数据表:
jbpm4_property 初始化设定的种子数据

配置文件:

一般情况下,你可以先拷入jbpm-4.4\examples\src下的5个文件到类路径。

jbpm.cfg.xml:

在官方发布包jbpm.jar的根路径中包含了一些默认提供的配置文件。用户可以选择包含或排除某些功能,通过向jbpm.cfg.xml配置文件中导入选定的配置文件:

<jbpm-configuration>
  <!-- 流程引擎的主配置文件 -->
  <import resource="jbpm.default.cfg.xml" />
  <!-- 身份认证相关的配置文件 -->
  <import resource="jbpm.identity.cfg.xml" />
  <import resource="jbpm.jboss.idm.cfg.xml" />
  <!-- 基于JBoss应用服务器实现分布式远程调用的配置文件 -->
  <import resource="jbpm.jbossremote.cfg.xml" />
  <!-- Job执行器配置文件。用于配置异步活动和定时器Job的执行策略 -->
  <import resource="jbpm.jobexecutor.cfg.xml" />
  <!-- 任务声明周期状态定义的配置文件。默认的有开启(open)状态、挂起(suspended)状态、取消(cancelled)状态、完成(completed)状态 -->
  <import resource="jbpm.task.lifecycle.cfg.xml" />
  <!-- Hibernate事务、JTA事务和Spring事务的配置文件 -->
  <import resource="jbpm.tx.hibernate.cfg.xml" />
  <import resource="jbpm.tx.jta.cfg.xml" />
  <import resource="jbpm.tx.spring.cfg.xml" />
  <!-- 流程变量数据类型映射的配置文件 -->
  <import resource="jbpm.variable.types.xml" />
  <!-- 基于jBPM4的IoC架构,将引擎组件绑定在运行环境中的配置文件(通过依赖注入) -->
  <import resource="jbpm.wire.bindings.xml" />
  <import resource="jbpm.jpdl.bindings.xml" />
  <!-- 工作日历的配置文件 -->
  <import resource="jbpm.businesscalendar.cfg.xml" />
  <import resource="jbpm.jpdl.cfg.xml" />
  <import resource="jbpm.bpmn.cfg.xml" />

</jbpm-configuration>

jbpm.hibernate.cfg.xml:

hibernate配置文件,参照hibernate相关文章

jbpm.mail.properties、jbpm.mail.templates.xml:

邮件相关(如我们每一步操作,进行邮件提醒),在后面详细介绍


二、“流程运转”

(我们先学习一下流程运转,因为考虑到流程运转非常简单就几个服务类,而且后面的流程定义测试需要用到,如果对上一章的helloworld进行了学习,学习流程运转服务类应该会比较容易)

我们定义的流程,需要被实例化(或加载),因此我们要创建流程实例;
当流程实例在执行中时,我们要控制和监控流程,以确保业务流程执行在监控之中;
当流程实例执行完毕,jBPM4会将其归档到“历史流程”中去,从而提高运行中流程实例的执行效率,而我们需要从历史流程中进行数据分析以优化和重组业务。

以上这些功能的开发,都需要依赖jBPM4提供的Service API去帮助我们实现。包括:
·管理流程部署
·管理流程实例
·管理流程任务
·管理流程历史

·其他

1、概览 流程引擎API:

流程引擎对象 -- org.jbpm.api.ProcessEngine 是jBPM4所有Service API之源。
(ProcessEngine是由Configuration类构建的,根据配置产生)

ProcessEngine是线程安全的,所以我们可以全局共享一个。

//这种格式会默认从classpath根目录下加载默认文件jbpm.cfg.xml
ProcessEngine processEngine = Configuration.getProcessEngine();

//从classpath下查找一个指定的文件
ProcessEngine processEngine = new Configuration().setResource("jbpm.cfg.xml").buildProcessEngine();
除了setResource文件名称外,还提供了其他方式的资源载入。

// 流程资源服务的接口。提供对流程定义的部署、查询、删除等操作
RepositoryService repositoryService = processEngine.getRepositoryService();

// 流程执行服务的接口。提供启动流程实例、“执行”推进、设置流程变量等操作
ExecutionService executionService = processEngine.getExecutionService();

// 流程管理控制服务的接口。只提供异步工作相关执行和查询操作。
ManagementService managementService = processEngine.getManagementService();

// 人工任务服务的接口。提供对任务的创建、提交、查询、保存、删除等操作
TaskService taskService = processEngine.getTaskService();

// 流程历史服务的接口。提供对流程历史库中历史流程实例、历史活动实例等记录的查询操作。还提供诸如某个流程定义中所有活动的平均执行时间、某个流程定义中某个转移经过的次数等数据分析服务。
HistoryService historyService = processEngine.getHistoryService();

// 身份认证服务的接口。提供对流程用户、用户组以及组成员关系的相关服务。
IdentityService identityService = processEngine.getIdentityService();

(这六种Service的获取,ProcessEngine还提供了两种通用获取方式<T> T get(Class<T> paramClass)、Object get(String paramString))

2、流程定义管理:

RepositoryService提供了管理jBPM4发布资源的所有接口。
添加:

// repositoryService获取NewDeployment,通过NewDeployment进行addResourceFromXXX操作(可以以多种方式获取资源),然后发布。
// 发布的文件为我们定义的.jpdl.xml文件
String deployId = super.repositoryService.createDeployment().addResourceFromClasspath("leave.jpdl.xml")
		.deploy();
在部署过程中,流程引擎会把一个ID分配给流程定义,ID的格式为{key}-{version}流程键和版本。
如果流程定义没有指定key(键),key则会在流程名称的基础上自动生成。生成的key会把所有不是字母和数字的字符替换成下划线,例如空格。
同一个流程名称只能关联一个key,反之亦然。

删除:
可以通过RepositoryService提供的API删除一个已经部署的流程定义,如果使用了数据库,会从数据库中彻底删除。
super.repositoryService.deleteDeployment(deploymentId);
但是如果要删除的流程定义还存在未完成流程实例的话,执行deleteDeployment方法会抛出异常。

如果希望级联删除一个已经发布的流程定义以及其所有产生的流程实例的话,可以:
repositoryService.deleteDeploymentCascade(deploymentId);

查询已部署的流程定义:

List<Deployment> deployments = super.repositoryService.createDeploymentQuery().list();
for(Deployment deployment:deployments){
	System.out.println(deployment);
}

3、发起新的流程实例:

通过不同的方式发起新的流程实例。
比如我们发起了一个为leave的.jpdl.xml文件。

// 会启动key为leave最新发布的版本
ProcessInstance processInstance = executionService.startProcessInstanceByKey("leave");

// 根据特定的流程定义版本发起流程实例
ProcessInstance processInstance = executionService.startProcessInstanceById("leave-" + departmentId);

发起流程标识键:
//我们可以获取自动生成的id,这个id为数据库主键id。
ProcessInstance processInstance = executionService.startProcessInstanceByKey("leave");
processInstance.getId();

如果我们需要和其他系统相关联,自己指定一个id,可以使用两个参数的重载方法,第二个参数为id。
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, String processInstanceKey)
但是:一个标识键(业务键)必须在此流程定义所有版本的流程实例范围内是唯一的。


4、为发起实例赋予变量:
如果新的流程实例需要一些输入参数启动(或者此实例的启动需要记录一些参数),那么可以将这些参数放在流程变量里,然后在发起流程时传入流程变量对象。
流程变量对象一般是一个Map:

Map<String, Object> variables = new HashMap<String, Object>();
variables.put("name", "partner4java");
variables.put("age", 25);
ProcessInstance processInstance = executionService.startProcessInstan

根据ProcessInstance的id查询相关信息:
Map<String, Object> map = executionService.getVariables(processInstance.getId(), executionService
.getVariableNames(processInstance.getId()));
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println(key + ":" + map.get(key));
}


5、向下执行:
当流程进入一个state活动时,执行会在到达state的时候进入等待状态 -- wait state,这是jBPM的一个重要概念,task等活动也会陷入等待状态,支付到一个signal命令,才能进入下一个步骤的活动。
executionService的signalExecution*方法可以用来发出一个signal命令。

发出signal需要传入executionId -- 实例id。
Execution executionInState = processInstance.findActiveExecutionIn("state1");
assertNotNull(executionInState);
executionService.signalExecutionById(executionInState.getId());


(那么学到这里,你是否想自己先试一下这些工具类再继续学习,跳转到第三部分“描述工作流程”,学习流程定义标签中的start、state、end三个标签,从“可以这么说” -到- “应该为jpdl和png的zip包”,还有不要说很难,请动手,动手后再说容不容易理解)


6、任务服务

(可能你会有遗憾,任务的用户是如何指定的,在我们“描述工作流程”中有介绍)

TaskService的主要目的是提供对任务列表的访问操作,这里的任务是指jBPM4 task活动的人机交互业务。
查找某用户的待办任务列表:

List<Task> tasks = taskService.findPersonalTasks("partner4java");

发起的任务通常会携带一些参数数据,例如在界面中提交的审批相关数据:

//获取所有variable的名称
Set<String> variableNames = taskService.getVariableNames(task.getId());
//获取所有variables数据
Map<String, Object> variables = taskService.getVariables(task.getId(), variableNames);
for(String name:variableNames){
	System.out.println("name:" + name + " value:" + variables.get(name));
}
TaskService也可以用来完成任务:

//根据指定的任务id完成任务(前提是你的路径“无需选择”)
completeTask(String taskId)

//根据指定的任务ID,同时传入变量
completeTask(String taskId, Map<String, ?> variables)

//指定下一步的转移路径,完成任务
completeTask(String taskId, String outcome)

//指定下一步的转移路径,并传入变量
completeTask(String taskId, String outcome, Map<String, ?> variables)
(如果传入变量,只会覆盖上一步传入变量,不会删除不冲突变量)

Demo:
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("time", new Date());
variables.put("owner", "partner4java23423424");
taskService.completeTask(task.getId(), "user_to_manager_approval", variables);
任务可以拥有多个候选人,候选人可以是单个用户也可以是用户组。
用户可以接收候选人是自己的任务,接收任务的意思是用户会被流程引擎设置为任务的分配者,接口任务是个“排他”操作,因此在任务被“接收 -- 分配”之后,其他的用户就不能接收并办理此任务了。

对于有候选人,但是还没有被分配的任务,唯一应该暴漏给用户的操作就是“接收任务”。

7、其他相关服务

历史服务:
在流程实例执行的过程中,会不断触发事件,通过这些事件,已完成流程实例的历史信息会被收集到流程历史数据表中。

查询某一流程定义的所有历史流程实例:
historyService.createHistoryProcessInstanceQuery()
.processDefinitionId("leave-" + deploymentId).orderAsc(HistoryProcessInstanceQuery.PROPERTY_STARTTIME)
.list()

avgDurationPerActivity -- 获取指定流程定义中每个活动的平均执行时间

choiceDistribution -- 获取指定活动定义每个转移的经过次数

管理服务:

ManagementService即管理服务,通常用来管理Job(异步工作)。
ManagementService的功能在诸如jBPM4 Web控制台等客户端应用上被调用。

ManagementService只有三个方法。
主要是创建JobQuery,用于Job查询。

查询服务:

从jBPM4开始,流程查询系统由一组新的API来支持。
你如果对表结构了解,也可以自己通过Hibernate的HQL进行查询。

流程实例或者任务等查询都是基于对应的service,如流程实例查询:
executionService
//获取流程实例查询对象
.createProcessInstanceQuery()
//指定实例id
.processInstanceId(mProcessInstance.getId())
//或者指定流程定义id
//.processDefinitionId("leave-4")
//.page(0, 50)
//查询执行,获取结果集列表
.list();

三:“描述工作流程”

可以这么说,jPDL(jBPM Process Define Language,jBPM流程定义语言)是jBPM4独有的、最重要的“资产”。
jPDL的设计目标是尽量的精简和尽可能地对开发者友好,即提供所有您期望从业务流程定义语言中得到的同时,也可以很“简练”地描述业务流程的定义和图形结构,最终使得业务分析师和流程开发者能使用“同一种语言说话”、极大地减少了他们之间的交流障碍。

通用的活动属性:
name -- 活动的名称(必须的)
transition -- 定义活动的流出转移

1、process(流程 -- 根标签)
在jPDL中process元素是是每个流程定义的顶级元素,即任何流程定义都必须以如下形式开始和结束:
<process>
...
</process>
属性:
name 必须 在面对最终用户展示和交互时,作为流程显示的标签
key 可选 用来标识不同的流程定义。拥有同一个key的流程定义允许有不同的version。对于所有已发布的统一流程定义的各个版本来说,“key:name”组合必须是完全一样的。如果省略,key会根据name生成,name中非字母以及非数字的字符会被替换为下画线。
version 可选 标识同一流程定义的不同版本。 同一流程定义(即二者“key:name”相同),后部署的会比选部署的version加1如果是第一次部署,version从1开始。

子元素:
description 描述流程的文案
activities 活动。流程定义中会有很多不同的活动,例如start、state、decision等,但至少要有1个是start活动

流转控制活动
流转控制完全可以组装出一条完整的流程定义,实现各种基本的流程流转控制场景。
·start 开始活动
·state 状态活动
·end 结束活动
·decision 判断活动
·fork-join 分支/聚合活动
·task 人工任务活动
·sub-process 子流程活动
·custom 自定义活动

2、start(开始活动):
start活动的意义在于指明一个流程的实例应该从哪里开始发起,即流程的入口。
在一个流程定义里必须拥有一个start活动。
start活动必须有一个转移(transition),这个转移会在流程通过start活动的时候执行。
(注意:一个流程定义有且只能有一个start活动 -- 在group中的start活动除外)
属性:
·name start活动的名字,在无流入转移指向start活动的情况下,name属性可以不指定
·g “坐标”用来标识位置
元素:
·transition 流出转移,指向流程的下一个步骤

3、state(状态活动)
当需要使业务流程受到某些特定的外部干预(处理)后再继续进行,而在外部干预(处理)之前流程"陷入"一个中断等待的状态,需要的就是这么一个state活动来实现。
当流程运行到state活动时,会自动陷入等到状态(waitting state),也就是说流程引擎在收到外部触发信号之前会一直使流程实例在此state活动等待。(从这个方面来说,task活动可以说是一种特殊化的state活动)
state活动除了最基本的name属性和transition等元素外,没有独特的属性或元素。

state_hello.jpdl.xml:

<process name="state_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<!-- 流程开始后转移到 a state -->
	<start g="186,9,80,40">
		<transition to="a"/>
	</start>
	<!-- a state转移到b state -->
	<state g="175,82,80,40" name="a">
		<transition to="b"/>
	</state>
	<!-- b state转移到end -->
	<state g="177,150,80,40" name="b">
		<transition to="c"/>
	</state>
	<end g="191,215,80,40" name="c"/>
</process>
运行代码:
ProcessEngine processEngine = Configuration.getProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
String deploymentId = repositoryService.createDeployment().addResourceFromClasspath("state_hello.jpdl.xml")
		.deploy();

ExecutionService executionService = processEngine.getExecutionService();
ProcessInstance processInstance = executionService.startProcessInstanceById(repositoryService
		.createProcessDefinitionQuery().deploymentId(deploymentId).uniqueResult().getId());

Execution executionInA = processInstance.findActiveExecutionIn("a");
//断言流程实例在state a
Assert.assertNotNull(executionInA);

//触发外部执行信号,行下执行
processInstance = executionService.signalExecutionById(executionInA.getId());

Execution executionInB = processInstance.findActiveExecutionIn("b");
//断言流程实例在state b
Assert.assertNotNull(executionInB);
在state活动里可以定义多个transition元素,我们通过信号触发指定转移路径的名称,就可以选择其中的一个transition通过。
state_hello2.jpdl.xml:
<process name="state_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<!-- 流程开始后转移到 a state -->
	<start g="187,5,80,40">
		<transition to="a"/>
	</start>
	<!-- a state转移到b state -->
	<state g="175,82,80,40" name="a">
		<transition g="-30,-22" name="to_b" to="b"/>
		<transition g="-28,-22" name="to_c" to="c"/>
	</state>
	<!-- b state转移到end -->
	<state g="102,180,80,40" name="b">
		<transition to="d"/>
	</state>
	<state g="264,175,74,41" name="c">
		<transition to="d"/>
	</state>
	<end g="201,273,80,40" name="d"/>
</process>
运行代码:
Execution executionInA = processInstance.findActiveExecutionIn("a");
// 断言流程实例在state a
Assert.assertNotNull(executionInA);

// 触发外部执行信号,行下执行
processInstance = executionService.signalExecutionById(executionInA.getId(), "to_b");

Execution executionInB = processInstance.findActiveExecutionIn("b");
// 断言流程实例在state b
Assert.assertNotNull(executionInB);
// 断言流程实例流向了预期的活动
Assert.assertTrue(processInstance.isActive("b"));


4、end(结束活动)
end活动会终结流程。
默认情况下,当流程实例运行到end活动会结束。但是在到达end活动的流程示例中仍然活跃的流程活动将会被保留继续执行。

在jBPM4中,流程定义允许多个end活动,如果您需要:执行到特定的end活动时,流程实例会被完全结束,即其他仍然活跃的并发活动也需要被结束,那么,可以设置end活动的属性“ends="execution"”来实现这种需求。

在实际应用中,我们经常需要知道流程实例是以何种“状态”结束的。
为了表明流程实例的结束状态,可以利用end活动的state属性标识,或者直接利用jBPM4提供的特殊end活动:end-cancel活动和end-error活动。

end活动的state属性:
state 用来自定义流程实例的状态。可以通过API:processInstance.getState()

end_hello.jpdl.xml:

<process name="end_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start name="start" g="195,14,80,40">
		<transition to="m_state"/>
	</start>
	
	<state name="m_state" g="189,84,80,40">
		<transition name="to_end1" to="end1" g="-51,-22"/>
		<transition name="to_end2" to="end2" g="-51,-22"/>
		<transition name="to_end3" to="end3" g="256,117:21,34"/>
	</state>
	
	<end name="end1" state="ended1" g="87,243,80,40"/>
	<end name="end2" state="ended2" g="240,247,80,40"/>
	<end name="end3" state="ended3" g="379,253,80,40"/>
</process>
执行代码:
// 启动实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey("end_hello");

// 获取m_state
Execution state = processInstance.findActiveExecutionIn("m_state");

// 向下执行,并指定路径“to_end2”
processInstance = executionService.signalExecutionById(state.getId(), "to_end2");

// 判断活动是否已经结束
Assert.assertTrue(processInstance.isEnded());
// 获取end的state值
Assert.assertEquals("ended2", processInstance.getState());

5、decision(判断活动)

根据条件在多个流转路径中选择其一通过,也就是做一个决定性的判断,这时候使用decision活动是个正确的选择。
decision活动可以拥有多个流出转移,当流程实例到达decision活动时,会根据最先匹配成功的一个条件自动地通过相应的流出转移。

·使用decision活动的condition元素
decision活动中会运行并判断其没一个transition元素里的流转条件 -- 流转条件由condition元素表示。
当遇到一个transition的condition值为true或者一个没有设置condition的transition,那么流程就立即转向这个condition。

decision活动的condition元素属性:
expr(必需) 描述转移条件的表达式
lang(可选) 指定转移条件表达式的语言类型(默认为EL表达式语言)

decision_hello.jpdl.xml:

<process name="decision_hello" xmlns="http://jbpm.org/4.4/jpdl">
   <start g="471,39,48,48" name="start1">
   	<transition to="a"/>
   </start>
   
   <state name="a" g="459,103,80,40">
   		<transition to="b"/>
   </state>
   
   <decision name="b" g="474,183,80,40">
   		<!-- 以下是两个流转条件表达式: -->
   		<transition to="c">
   			<!-- 变量name等于c -->
   			<condition expr="#{name == 'c'}"/>
   		</transition>
   		<transition to="d">
   			<!-- 变量name等于b -->
   			<condition expr="#{name == 'b'}"/>
   		</transition>
   		<!-- 无条件转移 -->
   		<transition to="e"/>
   </decision>
   
   <state name="c" g="340,277,80,40"/>
   <state name="d" g="469,275,80,40"/>
   <state name="e" g="636,274,80,40"/>
</process>
运行代码:
// 获取state a的执行实例
Execution executionInA = processInstance.findActiveExecutionIn("a");
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("name", "d");
// 携带参数,执行到下一步(判断流转)
processInstance = executionService.signalExecutionById(executionInA.getId(), variables);

// 断言是否流向了我们指定的state
Assert.assertFalse(processInstance.isActive("c"));
Assert.assertTrue(processInstance.isActive("d"));
·使用decision活动的expr属性
您可以利用decision活动本身具有的expr(表达式)属性来判断流程的转向。
decision活动的expr属性值可直接返回字符串类型的流出转移名称,指定需要转移的路径。


decision活动的属性:
expr(必须) 描述转移名称的表达式
lang(可选) 指定转移条件表达式语言的类型(EL表达式语言)

decision_hello2.jpdl.xml:

<process name="decision_hello2" xmlns="http://jbpm.org/4.4/jpdl">
	<start g="476,1,80,40" name="start">
		<transition to="first_state" />
	</start>
	<state g="457,74,80,40" name="first_state">
		<transition to="choose_line" />
	</state>
	<!-- "#{line}"即为判断表达式,这里会返回变量line的字符串,为我们转移名称 -->
	<decision expr="#{line}" g="478,137,80,40" name="choose_line">
		<!-- line值为to_a时的转移 -->
		<transition g="-29,-22" name="to_a" to="a" />
		<!-- line值为to_b时的转移 -->
		<transition g="-30,-22" name="to_b" to="b" />
		<!-- line值为to_c时的转移 -->
		<transition g="-28,-22" name="to_c" to="c" />
	</decision>
	<state g="283,251,80,40" name="a">
		<transition to="end" />
	</state>
	<state g="464,262,80,40" name="b">
		<transition to="end" />
	</state>
	<state g="677,254,80,40" name="c">
		<transition to="end" />
	</state>
	<end g="484,407,80,40" name="end" />
</process>
运行代码:
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("line", "to_c");
// 携带参数启动实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey("decision_hello2", variables);

Execution execution = processInstance.findActiveExecutionIn("first_state");
// 继续向下执行
processInstance = executionService.signalExecutionById(execution.getId());

// 判断是否立传到了state c
Assert.assertTrue(processInstance.isActive("c"));
·使用decision活动的handler元素
如果您需要在判断流程时计算大量、复杂的业务逻辑,那么,自己实现判断处理接口,即通过decision handler的方式来实现。

首先,必须实现DecisionHandler接口,将流转判断的决定权委派给这个实现类。
/** interface for supplying user programmed decisions.
 * 
 * @author Tom Baeyens */
public interface DecisionHandler extends Serializable {

  //接口唯一的方法,提供流程实例的执行上下文(execution)作为参数,需要返回字符串型的转移名称
  /** the name of the selected outgoing transition */ 
  String decide(OpenExecution execution);
}
这个handler需要作为decision活动的子元素被配置。
decision_hello3.jpdl.xml:
<process name="decision_hello3" xmlns="http://jbpm.org/4.4/jpdl">
	<start g="476,1,80,40" name="start">
		<transition to="first_state" />
	</start>
	<state g="457,74,80,40" name="first_state">
		<transition to="choose_line" />
	</state>
	<decision g="478,137,80,40" name="choose_line">
		<handler class="com.partner4java.demo.decision.MyDecisionHandler"/>
		<!-- line值为to_a时的转移 -->
		<transition g="-29,-22" name="to_a" to="a" />
		<!-- line值为to_b时的转移 -->
		<transition g="-30,-22" name="to_b" to="b" />
		<!-- line值为to_c时的转移 -->
		<transition g="-28,-22" name="to_c" to="c" />
	</decision>
	<state g="283,251,80,40" name="a">
		<transition to="end" />
	</state>
	<state g="464,262,80,40" name="b">
		<transition to="end" />
	</state>
	<state g="677,254,80,40" name="c">
		<transition to="end" />
	</state>
	<end g="484,407,80,40" name="end" />
</process>

public class MyDecisionHandler implements DecisionHandler {
	private static final long serialVersionUID = -7062576970169892452L;

	@Override
	public String decide(OpenExecution paramOpenExecution) {
		String line = (String) paramOpenExecution.getVariable("line");
		if (line != null && line.length() > 0) {
			switch (line) {
			case "a":
				return "to_a";
			case "b":
				return "to_b";
			case "c":
				return "to_c";
			default:
				break;
			}
		}
		return null;
	}

}

运行代码:
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("line", "c");
// 携带参数启动实例
ProcessInstance processInstance = executionService.startProcessInstanceByKey("decision_hello3", variables);

Execution execution = processInstance.findActiveExecutionIn("first_state");
// 继续向下执行
processInstance = executionService.signalExecutionById(execution.getId());

// 判断是否立传到了state c
Assert.assertTrue(processInstance.isActive("c"));

6、fork-join(分支/聚合活动)

当我们需要流程并发(concurrency)执行的时候,就需要使用到fork-join活动的组合,fork活动可以使流程在一条主干上出现并行的分支,join活动则可以使流程的并行分支聚合成一条主干。

fork活动具有jBPM活动的最基本特性,即具有1个name属性和n个流出转移元素。

join活动的独特属性:
multiplicity(可选) 流程执行中,当指定的流入转移数量(multiplicity)到达join活动后,流程即会聚合,沿着join活动的唯一流出转移继续执行流转。其他未到达的流入转移则会忽略,从而实现按流入转移数量聚合的场景,因此,multiplicity属性不应该大于join活动定义流入转移数量。(默认数量为流入转移数)
lockmode(可选) 指定hibernate的数据锁模式。因为join活动支持并发自动活动事务,因此需要在join活动上防止两个还没有聚合的同步事务活动互相锁定对象的事务资源,从而导致死锁。(值为字符串枚举:none、read、upgrade、upgrade_nowait、wirte,默认值upgrade)

fork_join_test.jpdl.xml:

<process name="fork_join_test" xmlns="http://jbpm.org/4.4/jpdl">
	<start name="start" g="514,2,80,40">
		<transition to="m_fork" />
	</start>

	<!-- 流转在这产生了四个并行的分支 -->
	<fork name="m_fork" g="522,79,80,40">
		<transition to="a_state" />
		<transition to="b_state" />
		<transition to="c_state" />
		<transition to="d_state" />
	</fork>

	<state name="a_state" g="501,219,80,40">
		<transition to="m_join" />
	</state>
	<state name="b_state" g="642,213,80,40">
		<transition to="m_join" />
	</state>
	<state name="c_state" g="798,215,80,40">
		<transition to="m_join" />
	</state>
	
	<!-- fork的分支d_state,没有再进行聚合,而是直接走到end,也就是说本路线会直接引起活动结束 -->
	<state name="d_state" g="302,307,80,40">
		<transition to="end" />
	</state>

	<!-- 分支活动:a_state、b_state、c_state在此聚合 -->
	<join name="m_join" g="656,322,80,40" multiplicity="2">
		<transition to="end" />
	</join>

	<end name="end" g="549,445,80,40" />
</process>

运行代码:
Set<String> names = new HashSet<String>();
names.add("a_state");
names.add("b_state");
names.add("c_state");
names.add("d_state");

// 判断,目前是否定义的4个分支都为活动状态
Set<String> activityNames = processInstance.findActiveActivityNames();
Assert.assertEquals(names, activityNames);

// 使a_state走向下一步
Execution executionA = processInstance.findActiveExecutionIn("a_state");
processInstance = executionService.signalExecutionById(executionA.getId());

// 移除已处理的活动
names.remove("a_state");

// m_join活动还没有开始,因为我们设置了multiplicity="2",还需要一个活动走到聚合处
activityNames = processInstance.findActiveActivityNames();
Assert.assertEquals(names, activityNames);

// 使b_state走向下一步
Execution executionB = processInstance.findActiveExecutionIn("b_state");
processInstance = executionService.signalExecutionById(executionB.getId());

// m_join活动已经执行,我们设置了multiplicity="2",b_state执行后就已经有两个活动到达聚合处,聚合join执行执行到了end,所以,目前已经没有运行状态的活动
names = new HashSet<String>();

activityNames = processInstance.findActiveActivityNames();
Assert.assertEquals(names, activityNames);
// 活动是否已经结束
Assert.assertTrue(processInstance.isEnded());

执行到这里,你可能会有疑问,每一次我们判断执行到某个点太麻烦了吧?能不能图形的给我们展示出来呢?

参照下一章“《partner4java 讲述jBPM4》之第三步:图形化查看执行位置”

如果暂时看不懂,可以部署到您的web容器,以地址访问传入相应id的形式查看当前流程执行情况(注意:如果使用图形查看,部署的应该为jpdl和png的zip包)。

(如果刚才是从学习第二部分“流程运转”,向下执行后,跳转学习到这里,那么现在您再跳回去吧,继续接着从“6、任务服务”学习)


7、task(人工任务活动)

在jBPM中,task活动一般用来处理涉及人机交互的活动。
task活动的功能在jBPM乃至整个工作流的应用中都具有极其重要的意义,因为处理人工任务、电子表单是工作流应用中最繁琐和细致的工作。

·关于任务的分配者
我们可以使用task活动的assignee属性(分配者属性)简单的将一个任务分配给指定的用户。
属性:
assignee(可选) 被分配到任务的用户ID

task_hello.jpdl.xml:

<process name="task_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start g="454,53,80,40" name="start">
		<transition to="m_task"/>
	</start>
	<!-- EL表达式的值标识分配者ID -->
	<task assignee="#{username}" g="446,191,80,40" name="m_task">
		<transition to="end"/>
	</task>
	<end g="467,298,80,40" name="end"/>
</process>

执行代码:
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("username", "partner4java");
ProcessInstance processInstance = executionService.startProcessInstanceByKey("task_hello", variables);

// 获取用户的任务列表
List<Task> tasks = taskService.findPersonalTasks("partner4java");
System.out.println(tasks);
·关于任务的候选者
jBPM支持将任务分配给一组候选用户,组中的一个用户可以接受这个任务并完成,这就是任务的候选者机制。
task活动的候选者属性:
candidate-groups(可选) 使用逗号分隔的用户组ID列表。所有组的用户将会成为任务的候选者。
candidate-users(可选) 使用逗号分隔的用户ID列表。所有列表中的用户将会成为任务的候选者。

task_hello2.jpdl.xml:
<process name="task_hello2" xmlns="http://jbpm.org/4.4/jpdl">
	<start name="start" g="337,27,80,40">
		<transition to="m_task"/>
	</start>
	<task candidate-groups="admin_group" name="m_task" g="324,136,80,40">
		<transition to="end"/>
	</task>
	<end name="end" g="342,257,80,40"/>
</process>

执行代码:
String deploymentId = repositoryService.createDeployment()
				.addResourceFromClasspath("jpdl/task_hello2.jpdl.xml").deploy();

/** *************************开启流程,目前还没有人接受任务**************************** */
ProcessInstance processInstance = executionService.startProcessInstanceByKey("task_hello2");
// 获取user partner4java的任务
List<Task> tasks1 = taskService.findGroupTasks("partner4java");
// 目前没有数据
Assert.assertEquals(tasks1.size(), 0);

// 获取user helloworld的任务
List<Task> tasks2 = taskService.findGroupTasks("helloworld");
// 目前没有数据
Assert.assertEquals(tasks2.size(), 0);

/** *************************通过identityService身份认证服务添加账户 *****************/
// 首先创建组admin_group
identityService.createGroup("admin_group");
// 创建用户
identityService.createUser("partner4java", "changlong", "wang");
// 将用户添加到组
identityService.createMembership("partner4java", "admin_group");

// 创建用户
identityService.createUser("helloworld", "changlong", "wang");
// 将用户添加到组
identityService.createMembership("helloworld", "admin_group");

/** ************************* 现在你会发现两个任务都有可以接受的任务 *****************/
tasks1 = taskService.findGroupTasks("partner4java");
// 现在应该存在一条可以处理的任务
Assert.assertEquals(tasks1.size(), 1);

tasks2 = taskService.findGroupTasks("helloworld");
// 现在应该存在一条可以处理的任务
Assert.assertEquals(tasks2.size(), 1);

/** ************************* 让一个用户接受任务 ********************************/
for (Task task : tasks2) {
	System.out.println(task + ":" + task.getProgress());
	// 接受任务:当用户partner4java接受了任务之后,partner4java就会由任务的候选者变为任务的分配者。
	// 同时,此任务会从所有候选者的任务列表中消失。它会出现在partner4java的已分配任务列表中
	taskService.takeTask(task.getId(), "partner4java");
}

/** *****接受了任务之后,任务就不会存在于用户组中,而是转移到了具体的用户任务列表中 ***/
tasks1 = taskService.findGroupTasks("partner4java");
Assert.assertEquals(tasks1.size(), 0);
tasks2 = taskService.findGroupTasks("helloworld");
Assert.assertEquals(tasks2.size(), 0);

List<Task> mTasks1 = taskService.findPersonalTasks("partner4java");
Assert.assertEquals(mTasks1.size(), 1);
List<Task> mTasks2 = taskService.findPersonalTasks("helloworld");
Assert.assertEquals(mTasks2.size(), 0);

for (Task task : mTasks1) {
	//完成任务
	taskService.completeTask(task.getId());
}

identityService.deleteUser("partner4java");
identityService.deleteUser("helloworld");
identityService.deleteGroup("admin_group");
·关于任务泳道
在实际的业务应用中,经常会遇到这样一种场景:流程定义中的多个任务需要被分配或候选给同一群用户。
那么我们可以统一将这个“同一群用户”定义为“一个泳道”。
泳道作为流程定义的直接子元素被整个流程定义所见,因此同一流程定义中的任何一个任务都可以引用泳道。
属于同一个泳道的任务将会被分配或者候选给这个泳道中的所有用户。
泳道的概念也可以理解为流程定义的“全局用户组”。泳道也可以被当做一个流程规则。

task的泳道属性:
swimlane(可选) 引用一个在流程中定义的泳道
(swimlane属性是任务活动对泳道的引用,泳道本身是作为process流程定义的子元素被定义在整个流程范围内的)

泳道(swimlane)元素的属性:
name(必须) 这个泳道名称将在任务的泳道属性中引用
assignee(可选) 引用的单个用户ID
candidate-groups(可选) 使用逗号分隔的用户组ID列表。此组中的所有用户将作为引用此泳道任务的候选人。
candidate-users(可选) 使用逗号分隔的用户ID列表。此列表中的所有用户将作为引用此泳道任务的候选人。

swimlane_hello.jpdl.xml:
<process name="swimlane_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<!-- 在这里定义泳道,泳道是为流程定义的子元素 -->
	<swimlane candidate-groups="admin_group" name="m_swimlane"/>
	
	<start name="start" g="511,58,80,40">
		<transition to="m_task"/>
	</start>
	<!-- 以下两个任务的分派工作,都交给上面定义的泳道完成 -->
	<task name="m_task" swimlane="m_swimlane" g="497,180,80,40">
		<transition to="n_task"/>
	</task>
	<task name="n_task" swimlane="m_swimlane" g="505,280,80,40">
		<transition to="end"/>
	</task>
	
	<end name="end" g="528,431,80,40"/>
</process>
执行代码:
通过打印可以总结出:
在启动之后,swimlane的candidate-groups组包含的所有用户都可以通过findGroupTasks查找到可被接受任务。
当某一用户接受任务之后,findGroupTasks都查找不到任务;只能通过findPersonalTasks查找已经接受的任务。
当本次任务完成之后,下一步任务如果也指定了swimlane,则会自动选择上一步接受任务的用户为代办用户。
StringBuilder builder = new StringBuilder();
String deploymentId = repositoryService.createDeployment().addResourceFromClasspath(
		"jpdl/swimlane_hello.jpdl.xml").deploy();

// 添加用户
testAddUser();

ProcessInstance processInstance = executionService.startProcessInstanceByKey("swimlane_hello");
builder.append("启动之后(findGroupTasks):");
List<Task> tasks1 = taskService.findGroupTasks("partner4java");
builder.append("partner4java获取到的任务数:" + tasks1.size() + ":" + tasks1);
List<Task> tasks2 = taskService.findGroupTasks("helloworld");
builder.append("helloworld获取到的任务数:" + tasks2.size() + ":" + tasks2);

for (Task task : tasks2) {
	// 接受任务
	taskService.takeTask(task.getId(), "partner4java");
}

builder.append("\n partner4java接受任务之后(findGroupTasks):");
tasks1 = taskService.findGroupTasks("partner4java");
builder.append("partner4java获取到的任务数:" + tasks1.size() + ":" + tasks1);
tasks2 = taskService.findGroupTasks("helloworld");
builder.append("helloworld获取到的任务数:" + tasks2.size() + ":" + tasks2);

builder.append("\n partner4java接受任务之后(findPersonalTasks):");
tasks1 = taskService.findPersonalTasks("partner4java");
builder.append("partner4java获取到的任务数:" + tasks1.size() + ":" + tasks1);
tasks2 = taskService.findPersonalTasks("helloworld");
builder.append("helloworld获取到的任务数:" + tasks2.size() + ":" + tasks2);

for (Task task : tasks1) {
	// 完成任务
	taskService.completeTask(task.getId());
}

builder.append("\n partner4java完成任务之后(findPersonalTasks):");
tasks1 = taskService.findPersonalTasks("partner4java");
builder.append("partner4java获取到的任务数:" + tasks1.size() + ":" + tasks1);
tasks2 = taskService.findPersonalTasks("helloworld");
builder.append("helloworld获取到的任务数:" + tasks2.size() + ":" + tasks2);

for (Task task : tasks1) {
	// 完成任务
	taskService.completeTask(task.getId());
}

// 删除用户
testDeleteUser();

System.out.println(builder.toString());
// 启动之后(findGroupTasks):partner4java获取到的任务数:1:[Task(m_task)]helloworld获取到的任务数:1:[Task(m_task)]
// partner4java接受任务之后(findGroupTasks):partner4java获取到的任务数:0:[]helloworld获取到的任务数:0:[]
// partner4java接受任务之后(findPersonalTasks):partner4java获取到的任务数:1:[Task(m_task)]helloworld获取到的任务数:0:[]
// partner4java完成任务之后(findPersonalTasks):partner4java获取到的任务数:1:[Task(n_task)]helloworld获取到的任务数:0:[]
·关于任务变量
任务可以读取、更新流程变量。任务还可以定义任务自由的变量,即任务变量。
一般来说,任务变量的主要作用是作为任务表单的数据容器 -- 任务表单负责展示来自任务和流程的变量数据;
同时用户通过任务表单录入的数据则会被设置为任务变量,任务变量根据需要也可以被输出成为流程变量。

·关于任务提醒邮件
为任务的分配者提供电子邮件提醒,包括:
当一个任务出现在某个任务列表中时立即提醒;
指定时间间隔进行反复提醒。

电子邮件的内容是根据一个模板生成出来的,此模板默认使用jBPM内置的,也可以进行修改,详见jbpm.mail.templates.xml.(后面我们会具体介绍)

任务活动的电子邮件相关元素:
notification 当一个任务被分配的时候立即发送一封提醒邮件。如果没有指定模板,邮件会使用默认的jbpm.mail.templates.xml
reminder 根据指定的时间间隔发送提醒邮件。如果没有指定模板,邮件会使用默认的模板

notification元素的属性:
continue 以同步、同步还是独占模式发送notification提醒邮件(字符串枚举sync、async、exclusive)

reminder元素的属性:
duedate(必须) reminder提醒电子邮件在任务产生后延迟多少时间发送(延迟时间 可包含表达式的字符串)
repeat reminder提醒电子邮件每间隔多少时间再发送一次,直到任务被办理(间隔时间 可包含表达式的字符串)
continue 以同步、同步还是独占模式发送notification提醒邮件(字符串枚举sync、async、exclusive)
<process name="email_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start name="start" g="537,43,80,40">
		<transition to="m_task"/>
	</start>
	<task name="m_task" g="534,157,80,40" assignee="partner4java">
		<!-- 这标识在任务产生后,立即以同步的方式发送提醒邮件 -->
		<notification/>
		<!-- 这标识在任务产生2天后,开始按发送提醒邮件,如果任务得不到处理,每隔1天再提醒一次 -->
		<reminder duedate="2 days" repeat="1 day"/>
		<transition to="end"/>
	</task>
	<end name="end" g="569,306,80,40"/>
</process>

8、sub-process(子流程活动)

当我们的流程复杂到一定程度的时候,就需要按照一定规则把业务拆分成若干子流程,这样业务模块之间才能明晰易于划分。
有时候,为了方便想对独立的流程之间的拼装、重组,划分出子流程管理也是明智的选择。

jBPM4提供了sub-process -- 子流程活动,这允许您在“主干流程”定义中调用其他的流程定义,从而“组装”您的流程定义。
在运行到子流程活动时,工作流引擎将创建一个子流程实例,然后等待直到其完成,当子流程实例完成后,流程就会流向下一步。

sub-process活动的属性:
sub-process-id 流程定义的ID标识。可以通过一个流程的ID去引用此流程定义的指定版本。
sub-process-key 流程key标识。通过key去引用流程定义,意味着引用了该流程定义的最新版本。注意,该流程定义的最新版本会在每次活动室里执行时计算得出。(sub-process-key和sub-process-id,必选其一)
outcome 当子流程活动执行结束时执行的表达式。表达式值用来匹配流出转移中的outcome-value元素值,起到选择sub-process活动下一步流向的作用。

sub-process活动的元素:
parameter-in 子流程输入参数。即声明一个变量,在创建子流程实例时传入。
parameter-out 子流程输出参数。即声明一个变量,在子流程实例结束时,返回父流程实例。

sub-process的parameter-in -- 子流程活动输入元素的属性:
subvar(必需) 被赋值的子流程变量的名称
var 从父流程环境中输入的变量名称
expr 此表达式在父流程环境中解析,结果值会被输入到对应的子流程变量中(var和expr二者必须指定一个)
lang 表达式使用的脚本语言(EL表达式)

sub-process的parameter-out -- 子流程活动输出元素的属性:
var(必需) 输出的目标 -- 父流程中的变量名称
subvar 子流程中需要被输出的变量名称
expr 此表达式在子流程环境中解析,结果会被传入到对应的父流程变量中。(subvar和expr二者必须制定一个)
lang 表达式使用的脚本语言(EL表达式)

sub-process活动的outcome元素必须有与之相呼应的outcome-value元素,这个outcome-value元素被定义在子流程活动的流出转移(transition)中。

sub-process transition的outcome-value元素:
outcome-value 是一个值表达式。子流程活动结束时,如果某转移的outcome-value值与子流程的outcome值匹配,那么,父流程的下一步将会通过此转移。注意:这个outcome-value值是由一个子元素定义的。

sub_process_main_hello.jpdl.xml:

<process name="sub_process_main_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start name="start" g="513,10,80,40">
		<transition to="m_sub_process" />
	</start>

	<!-- 引用标识为sub_process_ch_hello的子流程定义 -->
	<!-- 在这里定义了一个outcome属性,它引用变量result -->
	<sub-process name="m_sub_process" outcome="#{line}"
		sub-process-key="sub_process_ch_hello" g="502,105,80,40">
		<!-- 父流程将变量assignee_id输入子流程,对应的子流程变量名称为userid -->
		<parameter-in subvar="userid" var="assignee_id" />
		<parameter-in subvar="text" var="context" />
		<!-- 子流程将变量result返回父流程,对应的父流程变量名为line -->
		<parameter-out subvar="result" var="line" />
		<!-- 在这个流出转移中,定义了与outcome属性呼应的outcome-value值。 即如果outcome值等于‘line1’,则子流程结束后,父流程会通过名称为‘m_line’的转移,继续执行, 
			而line2转移则会被略过不执行。 -->
		<transition name="m_line" to="m_task1" g="-41,-22">
			<outcome-value>
				<string value="line1" />
			</outcome-value>
		</transition>
		<transition name="line2" to="m_task2" g="-32,-22" />
	</sub-process>
	<task name="m_task1" g="411,227,80,40" assignee="#{assignee_id}">
		<transition to="end" />
	</task>
	<task name="m_task2" g="621,221,80,40" assignee="#{assignee_id}">
		<transition to="end" />
	</task>
	<end name="end" g="538,342,80,40" />
</process>

sub_process_ch_hello.jpdl.xml:
<process name="sub_process_ch_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start name="start" g="280,29,80,40">
		<transition to="to_task" />
	</start>
	<!-- 子流程中的一个任务,分配给userid变量用户。assignee从个人任务列表中拿到任务后,可以从任务中获取子流程变量 -->
	<task name="to_task" g="270,128,80,40" assignee="#{userid}">
		<transition to="end" />
	</task>
	<!-- 子流程在这里结束。将哪些子流程变量返回父流程由调用它的父流程决定 -->
	<end name="end" g="284,262,80,40" />
</process>

执行代码:
// 发布
repositoryService.createDeployment().addResourceFromClasspath("jpdl/sub_process_main_hello.jpdl.xml").deploy();
repositoryService.createDeployment().addResourceFromClasspath("jpdl/sub_process_ch_hello.jpdl.xml").deploy();

// 创建参数
Map<String, Object> variables = new HashMap<String, Object>();
// 用于子流程和主流程的任务所有者定义
variables.put("assignee_id", PARTNER4JAVA);
// 从主流程携带给子流程
variables.put("context", "I'm helloworld!");
ProcessInstance processInstance = executionService.startProcessInstanceByKey("sub_process_main_hello",
		variables);

// 获取用户任务,这里获取的任务已经达到了子任务的to_task,并能从子任务获取到主任务传递过来的数据
List<Task> tasks = taskService.findPersonalTasks(PARTNER4JAVA);
for (Task task : tasks) {
	// 判断是否为指定的子任务名称
	Assert.assertEquals("to_task", task.getName());
	// 判断参数是否携带成功
	Assert.assertEquals("I'm helloworld!", taskService.getVariable(task.getId(), "text"));

	Map<String, Object> mVariables = new HashMap<String, Object>();
	mVariables.put("result", "line1");
	// 完成子任务,并携带参数返回给主任务
	taskService.completeTask(task.getId(), mVariables);
}

// 子任务完成后,返回到主任务,会根据子任务返回的result变量,转化为主任务的line变量,决定下一步的走向
tasks = taskService.findPersonalTasks(PARTNER4JAVA);
for (Task task : tasks) {
	Assert.assertEquals("m_task1", task.getName());
	taskService.completeTask(task.getId());
}

9、自定义活动

在流程执行过程中,只要拿到了流程实例及其上下文对象,再通过某种机制获得流程定义的输入数据、发布输出数据,那么自己实现一些活动就不是什么难事了,因为这些活动独特的活动也就是它们的处理逻辑。
如果有特殊而复杂的业务需求,与其生套jBPM本身提供的流转控制活动,不如自己实现一个自定义的活动来使用。

jBPM4提供了这样的功能,可以通过custom活动完全自定义一套活动行为,调用自己的代码,实现定制的活动逻辑。

处理类需要实现ExternalActivityBehaviour接口,而ExternalActivityBehaviour接口继承自ActivityBehaviour接口。
ActivityBehaviour接口是所有jBPM4内置活动都需要实现的接口。
需要实现两个方法:
·来自ActivityBehaviour的void execute(ActivityExecution execution)
在流程实例进入到此活动时执行此方法,完成主要的活动逻辑,提供ActivityExecution对象作为参数,通过它可以拿到流程实例、执行上下文等您能得到的一切流程运行时对象。
·void signal(ActivityExecution execution, String signalName, Map<String, ?> parameters)
自定义活动需要实现的方法,在流程实例得到执行信号离开此活动时执行此方法,这个接口为您提供了更为全面的流程运行时对象,包括信号的名称signalName。
在实现此方法时需要做的是:离开活动时的业务处理逻辑,以及根据signalName使流程实例通过正确的流出转移走向下一步。

custom_hello.jpdl.xml:

<process name="custom_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start g="486,41,80,40" name="start">
		<transition to="m_custom" />
	</start>

	<!-- 自定义活动:主要是传入了我们定义的class -->
	<custom class="com.partner4java.demo.MyCustom" g="487,203,80,40"
		name="m_custom">
		<transition to="end" />
	</custom>

	<end g="518,371,80,40" name="end" />
</process>

public class MyCustom implements ExternalActivityBehaviour {
	private static final long serialVersionUID = 2919452947822958504L;

	@Override
	public void execute(ActivityExecution execution) throws Exception {
		// 在这里执行自定义的处理逻辑
		System.out.println("execute(ActivityExecution execution)");
		// ....
		// 是流程陷入“等待”状态
		execution.waitForSignal();
		// 当然也可以调用execution.take(signalName)在这里自动发出执行信号,不等待而直接完成这个活动
	}

	@Override
	public void signal(ActivityExecution execution, String signalName, Map<String, ?> parameters) throws Exception {
		System.out.println("signal(ActivityExecution execution, String signalName, Map<String, ?> parameters)");
		// 活动收到执行信号后,进入到这里
		// .....
		// 最后别忘了调用使流程实例进入下一步方法
		execution.take(signalName);
	}

}

10、自动活动

通过前面学习的流程定义,jBPM能完成基本的工作流管理需要,这需要根据流程定义规则,进行人工的流程操作。
同样,jBPM4也能很好支持处理多种自动活动,所谓自动活动就是在执行过程中完全无须人工干预地编排好地程序jBPM4在处理和执行这些自动活动时能把人工活动产生的数据通过流程变量方式与之完美地结合。

jBPM4默认支持的自动活动类型有:
·java - Java程序活动
·script - 脚本活动
·hql - Hibernate查询语言活动
·SQL - 结构化查询语言活动
·mail - 邮件活动

·java(Java程序活动)
java活动可以指定一个Java类的方法(Java Method),当流程执行到此活动时,便会自动执行此Java方法。
java活动的属性:
class Java的全路径。此类的对象在活动执行时被“延迟创建”(需要提供无参构造器),即不随jBPM工作流引擎启动而创建。当这些对象创建后,将作为流程定义的一部分被缓存。
expr 此表达式返回一个Java类对象。(class或expr二者必选其一)
method(必须) 调用的方法名称
var 存储方法结果的流程变量名称

java活动支持的元素:
field 在方法被调用之前给指定的类成员域注入指定的值
arg 给被调用的方法提供参数

<process name="java_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start g="520,105,80,40" name="start">
		<transition to="m_java" />
	</start>
	<!-- 在这里调用java活动,调用类JavaHello对象的hello方法,并将执行结果存储到manu变量中 -->
	<java class="com.partner4java.demo.JavaHello" g="511,225,80,40"
		method="hello" name="m_java" var="manu">
		<!-- 为方法传入参数 -->
		<arg>
			<string value="world" />
		</arg>
		<!-- 为成员变量注入hello值,如果变量为类似private私有,需提供可访问的设置方法 -->
		<field name="name">
			<string value="hello" />
		</field>
		<transition to="m_state" />
	</java>
	<state name="m_state" g="516,340,80,40">
		<transition to="end" />
	</state>
	<end g="538,434,80,40" name="end" />
</process>

repositoryService.createDeployment().addResourceFromClasspath("auto/java_hello.jpdl.xml").deploy();
ProcessInstance processInstance = executionService.startProcessInstanceByKey("java_hello");
Execution execution = processInstance.findActiveExecutionIn("m_state");
System.out.println(executionService.getVariable(execution.getId(), "manu"));
·script(脚本活动)
script -- 脚本活动为我们解决了这个问题,您可以在script活动中定义一段EL表达式脚本,工作流引擎执行此活动时会解析这段脚本。
实际山个,不仅是EL表达式,任何一种复合JSR-223规范的脚本语言都可以在这里使用。
(在配置文件jbpm.default.scriptmanager.xml里定义要使用的脚本语言)

jBPM4默认的脚本语言是juel,即EL表达式。

·hql(Hibernate查询语言活动)
在一些特殊的情况下,我们可能需要直接从持久化层读取流程数据。
因为jBPM4的持久化层是基于Hibernate框架实现的,因此使用hql活动,直接面向数据库执行HQL语句查询,并将返回结果保存到流程变量中,是个不错的办法。

hql活动的属性:
var 存储HQL执行结果的流程变量名
unique 此属性值为true时,在查询结果上调用uniqueResult()方法获得HQL查询的唯一结果集;此属性默认值为false,即查询结果调用list()方法得到HQL查询的结果集列表。

hql活动的元素:
query HQL查询的语句
parameter HQL查询语句的外部参数
<process name="hql_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<start name="start" g="501,8,80,40">
		<transition to="m_hql"/>
	</start>
	<!-- 此HQL活动将查询名字含有m的所有任务名称,并放入参数变量names中 -->
	<hql name="m_hql" var="names" g="491,142,80,40">
		<!-- HQL语句 -->
		<query>
			select task.name from org.jbpm.pvm.internal.task.TaskImpl as task where task.name like :taskName
		</query>
		<!-- HQL替换参数 -->
		<parameters>
			<string name="taskName" value="%m%"/>
		</parameters>
		<transition to="m_task"/>
	</hql>
	<task name="m_task" g="505,292,80,40">
		<transition to="end"/>
	</task>
	<end name="end" g="529,440,80,40"/>
</process>

·sql(结构化查询语句活动)
与HQL活动相似,jBPM4的SQL活动能够支持使用SQL -- 结构化查询语言直接从流程数据库中查询数据,将结果返回到流程变量中。
sql活动与hql活动的属性和元素大致一样。


11、事件

事件(event)用来定位在流程执行过程中特定的“点”,例如“流程实例开始”、“状态活动结束”等,可以在这些“点”注册一系列的监听器(listener)。
当流程的执行通过这些被监听的点时,监听器中设定的逻辑就会被执行。

编写一个时间监听器需要实现EventListener接口:

/** listener to process execution events.
 * 
 * @author Tom Baeyens
 */
public interface EventListener extends Serializable {
  //接口方法提供了流程的执行对象execution,这足够拿到当前流程的任何信息
  /** is invoked when an execution crosses the event on which this listener is registered */
  void notify(EventListenerExecution execution) throws Exception;

}
为了给流程或活动分配一系列的事件监听器,可以使用on元素来为时间监听器分组并制定事件,on元素可以嵌入到process元素或process元素下的任何流程活动中.
on元素的属性:
event(必须) 事件的名称
(event属性目前只支持“start-开始事件”和“end-结束事件”的监听)

对于转移的执行事件(transition take),只需直接在transition元素中嵌入相应的时间监听器即可。

on元素支持的子元素:
event-listener 指定自定义的事件监听器,实现EventListener接口
任何自动活动 使用自动活动(java,script,hql...)作为事件监听器

event-listener的独特属性:
propagation 指定该时间监听器是否支持被传播的事件调用。(enabled、disabled、true、false、on、off)默认值disabled

·事件监听
<process name="event_listener_hello" xmlns="http://jbpm.org/4.4/jpdl">
	<!-- 放在了process里,是监听<start>事件 -->
	<on event="start">
		<event-listener class="com.partner4java.event.MyEventListener">
			<field name="message">
				<string value="process start" />
			</field>
		</event-listener>
	</on>
	<!-- 放在了process里,是监听<end>事件 -->
	<on event="end">
		<event-listener class="com.partner4java.event.MyEventListener">
			<field name="message">
				<string value="process end" />
			</field>
		</event-listener>
	</on>
	<start g="497,76,80,40">
		<transition to="m_start" />
	</start>
	<state name="m_start" g="479,222,80,40">
		<!-- 监听本state的开始事件 -->
		<on event="start">
			<event-listener class="com.partner4java.event.MyEventListener">
				<field name="message">
					<string value="state start" />
				</field>
			</event-listener>
		</on>
		<!-- 监听本state的结束事件 -->
		<on event="end">
			<event-listener class="com.partner4java.event.MyEventListener">
				<field name="message">
					<string value="state end" />
				</field>
			</event-listener>
		</on>
		<transition to="end" />
	</state>
	<end name="end" g="496,382,80,40" />
</process>
·事件传播
jBPM的事件触发是支持从父元素传播到子元素的。
默认情况下,事件监听器(event-listener)只对其当前订阅的元素所触发的时间起作用,即propagation="disable"。
但是指定事件监听器的传播属性propagation="enabled",则该事件监听器可以对其监听元素的所有子元素起作用。
	<on event="start">
		<event-listener class="com.partner4java.event.MyEventListener">
			<field name="message">
				<string value="process start" />
			</field>
		</event-listener>
	</on>
改为:
	<on event="start">
		<event-listener class="com.partner4java.event.MyEventListener" propagation="true">
			<field name="message">
				<string value="process start" />
			</field>
		</event-listener>
	</on>	
state的启动动作也会触发此事件。

你可能感兴趣的:(jbpm4)