流程变量
流程变量在整个工作流中扮演很重要的作用。例如:请假流程中有请假天数、请假原因等一些参数都为流程变量的范围。流程变量的作用域范围是流程实例。也就是说各个流程实例的流程变量是不相互影响的。流程实例结束完成以后流程变量还保存在数据库中(存放到流程变量的历史表中)。
2.2.4. 设置流程变量
@Test
public void setVarTest() throws Exception {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
String processInstanceId="2501";
String assignee="小莉";
//通过任务服务获取这个流程实例中 小莉的唯一的一个任务
Task task = taskService.createTaskQuery()
.processInstanceId(processInstanceId)//在一次流程实例中一个人的任务是唯一的
.taskAssignee(assignee)
.singleResult();
System.out.println(task);
//设置一个流程变量
String taskId = task.getId();
taskService.setVariable(taskId, "请假人", assignee);
Map<String,Object> variables=new HashMap<>();
variables.put("请假原因","想男朋友了");//设置String
variables.put("请假天数", 3);//设置Integer
variables.put("是否扣工资",true);//设置boolean:存long型:1true;0false
variables.put("请假开始时间", new Date());//设置Date类型
variables.put("扣多少钱", 666.666d);//设置Double
//设置多个流程变量:
taskService.setVariables(taskId, variables);
//完成任务的时候设置:
Map<String,Object> variables1=new HashMap<>();
variables1.put("叫老板", "亲爱的老板,我真的要回去找男朋友,很急的。。。。。");
taskService.complete(taskId, variables1);
}
说明:
1)流程变量的作用域就是流程实例,所以只要设置就行了,不用管在哪个阶段设置
2)基本类型设置流程变量,在taskService中使用任务ID,定义流程变量的名称,设置流程变量的值。
3)Javabean类型设置流程变量,需要这个javabean实现了Serializable接口
4)设置流程变量的时候,向act_ru_variable这个表添加数据
2. 流程定义语言(BPMN)
2.1. 什么是BPMN
业务流程建模与标注(Business Process Model and Notation,BPMN) ,描述流程的基本符号,包括这些图元如何组合成一个业务流程图(Business Process Diagram)
Eclispse画出流程,有两个文件bpmn文件和png文件,其中bpmn文件又可以叫做流程定义文件,它需要遵循BPMN语言规范.png:就是一个单纯的图片,没有任何作用.
2.2. 顺序流(sequenceFlow )
2.21. 什么是顺序流
顺序流是连接两个流程节点的连线,代表一个节点的出口。流程执行完一个节点后,会沿着节点的所有外出顺序流继续执行。 就是说,BPMN 2.0默认的行为就是并发的: 两个外出顺序流会创造两个单独的,并发流程分支。
顺序流主要由4个属性组成:
Id: 唯一标示,用来区分不同的顺序流
sourceRef:连线的源头节点ID
targetRef:连线的目标节点ID
name(可选):连线的名称,不涉及业务,主要用于显示。多出口原则要设置。
说明:
1)结束节点没有出口
其他节点有一个或多个出口。如果有一个出口,则代表是一个单线流程;
2.2.2. 分支流程-流程图
3.3. 节点
开始节点
结束节点:加一个执行监听器,修改流程的状态
任务节点
网关节点
监听器节点
3.3.1. 开始事件节点(startEvent)
开始事件对应节点
3.3.2. 结束事件节点(endEvent)
结束事件对应节点
3.3.3. 任务节点 (Task)
接收任务节点 (receiveTask)
接收任务是一个简单任务,它会等待对应消息的到达。 当前,官方只实现了这个任务的java语义。 当流程达到接收任务,流程状态会保存到数据库中。
在任务创建后,意味着流程会进入等待状态, 直到引擎接收了一个特定的消息, 这会触发流程穿过接收任务继续执行。(短跑比赛一样,预备,信号触发跑)
图形标记:
接收任务显示为一个任务(圆角矩形),右上角有一个消息小标记。 消息是白色的(黑色图标表示发送语义):
1)流程图
销售经理统计当前的营业额,然后短信发送给老板
用户任务节点 (userTask)
用户任务用来设置必须由人员完成的工作。 当流程执行到用户任务,会创建一个新任务, 并把这个新任务加入到分配人或群组的任务列表中。
图形标记
用户任务显示成一个普通任务(圆角矩形),左上角有一个小用户图标。
+
任务分配
用户任务的办理都需要人工的参与。用户任务可以分为两大类。私有任务(待办任务)和公有任务(可接任务)。
1)私有任务
私有任务即有直接分配给指定用户的任务。只有一个用户可以成为任务 的执行者。 在activiti中,用户叫做执行者。 拥有执行者的用户任务 (即私有任务)对用户是不可见的。
办理者指定分三种方式:
- 写死
- 流程变量
- 监听器
公共代码增加:
/**- 通过流程定义key启动流程并且设置流程变量
- @param processDefinitionKey
- @return
*/
protected ProcessInstance startProcess(String processDefinitionKey,Map<String, Object> variables) {
return runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
}
/**
- 私有任务测试
-
办理者指定:
-
1)写死,设计流程时写死办理者(不用)
-
缺点:这个流程只能由写死哪个人申请
-
2)流程变量设置--第一个节点
-
①设计流程时要从流程变量中获取办理者
-
②流程执行时要设置对应流程变量-启动时设置
-
问题:如果第二个-n个节点通过在启动流程时设置变量来指定办理者,
-
第一个办理长时间没有办理,在这期间后面节点办理者离职了,走下去再离职人员手上还会挂任务.
-
方案1:下一个任务办理时再指定下一个任务办理者.不好,业务逻辑耦合
-
方案2:使用监听器设置下一个节点启动时指定人,不需要耦合.(采纳)
-
3)监听器--第二个-n个节点
-
①写一个类实现监听器类-TaskListener
-
②把写好类绑定到对应节点
-
③实现监听器逻辑
- @author Administrator
*/
public class PersonalTaskTest extends BaseBpmnTest {
//写死
@Test
public void test1() {
//部署
deploy("私有任务测试1", "PersonalTaskTest");
//启动
String processDefinitionKey = "PersonalTaskTest";
ProcessInstance pi = startProcess(processDefinitionKey );
//获取写死办理的任务
String assignee = "小鑫鑫";
Task task = queryPersonalTask(pi.getId(), assignee );
System.out.println("id:"+task.getId()
+",name:"+task.getName()+",assignee:"+task.getAssignee());
}
@Test
public void test2() {
//部署
//deploy("私有任务测试2", "PersonalTaskTest2");
//启动时要设置第一个节点的办理者
String processDefinitionKey = "PersonalTaskTest2";
Map<String, Object> variables = new HashMap<>();
//第一个节点办理者
String assignee = "晓鑫鑫";//当前登录用户名称
variables.put("apply", assignee);
ProcessInstance pi = startProcess(processDefinitionKey,variables );
System.out.println("pid:"+pi.getId());
//获取写死办理的任务
Task task = queryPersonalTask(pi.getId(), assignee );
System.out.println("id:"+task.getId()
+",name:"+task.getName()+",assignee:"+task.getAssignee());
}
//要把第一个节点走下去
/**
* 使用流程变量设置
* @throws Exception
*/
@Test
public void test3Pre() throws Exception {
//部署
//deploy("私有任务测试3", "PersonalTaskTest3");
//启动时要设置第一个节点的办理者
String processDefinitionKey = "PersonalTaskTest3";
Map<String, Object> variables = new HashMap<>();
//第一个节点办理者
String assignee = "逗比鑫";//当前登录用户名称
variables.put("apply", assignee);
ProcessInstance pi = startProcess(processDefinitionKey,variables );
System.out.println("pid:"+pi.getId());
//获取写死办理的任务
Task task = queryPersonalTask(pi.getId(), assignee );
System.out.println("id:"+task.getId()
+",name:"+task.getName()+",assignee:"+task.getAssignee());
taskService.complete(task.getId());
}
//上一个任务完成设置下一个任务的办理者,没法做,没有办法直接获取下一个任务并设置
//当前任务创建时,再指定办理者
@Test
public void test3() throws Exception {
String assignee = "逗比鑫"+"审批人";
String processInstanceId = "5001";
Task task = queryPersonalTask(processInstanceId, assignee );
System.out.println("id:"+task.getId()
+",name:"+task.getName()+",assignee:"+task.getAssignee());
//完成第二个任务
taskService.complete(task.getId());
//判断流程是否结束
ProcessInstance pi = queryProcessInstanceById(processInstanceId);
if (pi == null) {
System.out.println("流程结束");
}
}
}
在下一个节点创建时,增加这个监听器:
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
/**
- ①写一个类实现监听器类-TaskListener
- ②把写好类绑定到对应节点
- ③实现监听器逻辑
- @author Administrator
*/
public class ManagerSettingListener implements TaskListener{
@Override
public void notify(DelegateTask delegateTask) {
System.out.println(delegateTask.getName());
//1 获取申请人
String apply = delegateTask.getVariable("apply", String.class);
//2 通过申请人获取审批人 通过申请人获取部门经理
String manager = apply+"审批人";
//3 设置办理者
delegateTask.setAssignee(manager);
//delegateTask.addCandidateUser(userId);
//delegateTask.addCandidateGroup(groupId);
}
}
2)公共任务
有的用户任务在指派时无法确定具体的办理者,这时任务也可以加入到人员的候选任务列表中,然后让这些人员选择性认领和办理任务。
公有任务的分配可以分为指定候选用户和候选组两种。
每一种都要三种指定方式
- 写死(不用的)
- 流程变量指定第一个节点
- 任务监听器指定第2-n节点
a) 把任务添加到一批用户的候选任务列表中,使用candidateUsers 属 性,XML内容如下:
<userTaskid="theTask"name="my task"activiti:candidateUsers=“sirius,kermit”/>
candidateUsers属性内为用户的ID,多个用户ID之间使用(半角)逗号间隔。
b)把任务添加到一个或多个候选组下,这时任务对组下的所有用户可 见,首先得保证每个组下面有用户,通过IdentityService对象创建用户 和组,然后把用户添加到对应的组下,java代码如下:
,
上述两个虽然都可以统称为任务节点,但是还是有本质区别:
1.receiveTask主要代表机器自动执行的,userTask代表人工干预的。
2.receiveTask任务产生后会在act_ru_execution表中新增一条记录, 而userTask产生后会在act_ru_execution和act_ru_task(主要记录任 务的发布时间,办理人等信息)中各产生一条记录。
receiveTask任务提交方式使用RuntimeService的signal方法提交, userTask任务提交方式使用TaskService的complete方法提交。
3.4.4. 网关节点
3.4.5. 监听事件节点
见3.6
3.5. 网关节点
网关用来控制流程的流向。
网关显示成菱形图形,内部有有一个小图标。 图标表示网关的类型。
3.5.1. 排他网关(ExclusiveGateWay)
简单理解有多个分支,但是只能走一个分支。
排他网关(也叫异或(XOR)网关,或更技术性的叫法 基于数据的排他网关), 用来在流程中实现决策。
图形标记
排他网关显示成一个普通网关(比如,菱形图形), 内部是一个“X”图标,表示异或(XOR)语义。 注意,没有内部图标的网关,默认为排他网关。 BPMN 2.0规范不允许在同一个流程定义中同时使用没有X和有X的菱形图形。
XML内容
排他网关的XML内容是很直接的:用一行定义了网关, 条件表达式定义在外出顺序流中。 参考条件顺序流 获得这些表达式的可用配置。
它对应的XML内容如下:、
<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway" default="财务"></exclusiveGateway>
<sequenceFlow id="财务" name="小于1000" sourceRef="exclusivegateway1" targetRef="usertask2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${price<1000}]]></conditionExpression>
</sequenceFlow>
<userTask id="usertask3" name="部门经理审批"></userTask>
<sequenceFlow id="部门经理" name="大于1000小于10000" sourceRef="exclusivegateway1" targetRef="usertask3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${price>1000&&price<10000}]]></conditionExpression>
</sequenceFlow>
<userTask id="usertask4" name="老板审批"></userTask>
<sequenceFlow id="老板" name="大于10000" sourceRef="exclusivegateway1" targetRef="usertask4">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${price>10000}]]></conditionExpression>
</sequenceFlow>
说明:
1.一个排他网关对应一个以上的顺序流
2.由排他网关流出的顺序流都有个conditionExpression元素,在内部维护返回boolean类型的决策结果。
3.决策网关只会返回一条结果。当流程执行到排他网关时,流程引擎会自动检索网关出口,从上到下检索如果发现第一条决策结果为true或者没有设置条件的(默认为成立),则流出。
4.如果没有任何一个出口符合条件则抛出异常。
3.5.2. 并行网关(parallelGateWay)
多个分支要一起执行完,才算完成(会签)。
网关也可以表示流程中的并行情况。最简单的并行网关是parallelGateWay,它允许将流程 分成多条分支,也可以把多条分支 汇聚到一起。
图形标记
并行网关显示成一个普通网关(菱形)内部是一个“加号”图标, 表示“与(AND)”语义。
:
XML内容
定义并行网关只需要一行XML:
实际发生的行为(分支,聚合,同时分支聚合), 要根据并行网关的顺序流来决定。
参考如下代码:
<startEvent id="theStart"/>
<sequenceFlow id="flow1"sourceRef="theStart"targetRef="fork"/> <parallelGatewayid="fork"/> <sequenceFlowsourceRef="fork"targetRef="receivePayment"/> <sequenceFlowsourceRef="fork"targetRef="shipOrder"/>
<userTaskid="receivePayment"name="Receive Payment"/> <sequenceFlowsourceRef="receivePayment"targetRef="join"/>
<userTaskid="shipOrder"name="Ship Order"/> <sequenceFlowsourceRef="shipOrder"targetRef="join"/> <parallelGatewayid="join"/> <sequenceFlowsourceRef="join"targetRef="archiveOrder"/>
<userTaskid="archiveOrder"name="Archive Order"/> <sequenceFlowsourceRef="archiveOrder"targetRef="theEnd"/>
<endEventid="theEnd"/>
上面例子中,流程启动之后,会创建两个任务:
ProcessInstancepi =runtimeService.startProcessInstanceByKey("forkJoin");
TaskQueryquery=taskService.createTaskQuery() .processInstanceId(pi.getId()) .orderByTaskName().asc();
List<Task>tasks =query.list();
assertEquals(2,tasks.size()); Task task1 =tasks.get(0);
assertEquals("Receive Payment",task1.getName());Tasktask2 =tasks.get(1);
assertEquals("Ship Order",task2.getName());
当两个任务都完成时,第二个并行网关会汇聚两个分支,因为它只有一条外出连线, 不会创建并行分支, 只会创建归档订单任务。
说明:
1.并行网关的功能是基于进入和外出的顺序流的:
分支(fork): 并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
汇聚(join): 所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。
2.并行网关的进入和外出都是使用相同节点标示
3.如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。
4.并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。
并行网关不需要是“平衡的”(比如, 对应并行网关的进入和外出节点数目相等)。如图中标示是合法的:
;
3.6. 监听器(Listener)
在流程中我们有时会对整个流程或者一个节点的某种状态做出相应的处理。这时就会用到监听器。
在Activiti中流程的监听主要分为两大类,执行监听器和任务监听器。
任务监听器:只能绑定到用户任务节点。 可用于用户节点办理者的动态设置,操作日志等
执行监听器:可以在任何节点绑定。修改流程结束状态(在结束节点绑定执行监听器执行修改),操作日志等
3.6.1. 执行监听器(ExecutionListener)
执行监听器可以执行外部java代码或执行表达式,当流程定义中发生了某个事件。 可以捕获的事件有:
流程实例的启动和结束。
选中一条连线。
节点的开始和结束。
网关的开始和结束。
中间事件的开始和结束。
开始时间结束或结束事件开始。
现在有这样一个简单流程,只包含开始、结束、接收任务和用户任务4个节点:
<
配置监听器,XML代码如下:
说明:
1.任务监听器支持以下属性:
event(必选):任务监听器会被调用的任务类型。 可能的类型为:
start:流程节点创建后触发。
end:当任务完成,并尚未从运行数据中删除时触发。
take:任务完成后,流程流出时触发
class:必须调用的代理类。 这个类必须实现org.activiti.engine.delegate.
ExecutionListener接口。实现类代码如下:
执行监听器配置可以放在以下三个地方,如图:
?
a)监听整个流程的启动和结束状态,配置为process节点的子元素,如图①
b)监听一个节点的启动和结束状态,配置为一个节点的子元素,如图②和③
c)监听一条连线的执行,配置在sequenceFlow节点的内部,只有task一种事件,如图④
启动流程测试代码如下:
@
结果如下:
A
3.6.2. 任务监听器(TaskListener)
任务监听器可以在发生对应的任务相关事件时执行自定义java逻辑 或表达式。
任务监听器只能添加到流程定义中的用户任务中。在之前任务节点上添加任务监听:
B
任务监听器可以在发生对应的任务相关事件时执行自定义java逻辑 或表达式。
任务监听器只能添加到流程定义中的用户任务中。在之前任务节点上添加任务监听:
说明:
1.任务监听器支持以下属性:
event(必选):任务监听器会被调用的任务类型。 可能的类型为:
create:任务创建并设置所有属性后触发。
assignment:任务分配给一些人时触发。 当流程到达userTask,assignment事件 会在create事件之前发生。 这样的顺序似乎不自然,但是原因很简单:当获得create时间时, 我们想获得任务的所有属性,包括执行人。
complete:当任务完成,并尚未从运行数据中删除时触发。
其实对于流程变量来说记住以下几点就应该没什么问题了:
设置流程变量:4种
①:启动流程的时候设置
②:任务完成的时候设置
③:runtimeService设置
④:taskService设置
获取流程变量:2种
①:runtimeService获取变量
②:taskService获取变量
注意:
1.流程变量应该可以被所有的执行对象获取(只要流程实例不结束,流程变量可以在任何地方获取)
2.流程变量是可以覆盖的
3.如果存放domain实体类的话就需要这个实体类实现序列化接口 Serializable