一、流程

    在JPDL中process元素是每个流程定义的顶级元素,即任何流程定义都必须以如下形式开始和结束


...

process元素拥有的属性:


属性 类型 默认值 是否必须 描述
name 文本 必须 展示给用户
key
如省略,则根据name生成
标识不同流程
version 整型 从1开始
同一流程的不同版本

它下的子元素有:description、activities



二、流转控制活动

  • start——开始活动

  • state——状态活动

  • decision——判断活动

  • fork--join——分支/聚合活动

  • end——结束活动

  • task——人工任务活动

  • sup-process——子流程活动

  • custom——自定义活动

1.start

    即流程的入口,一个流程中必须拥有一个start,必须有一个流出转移(transition),这个转移会在流程中通过start活动的时候执行。


2.state(状态活动)

当业务流程受到某些特定的外部干预后再继续运行,而在这之前流程处于一个中断等待的状态,这个时候就是state活动。

示例:

wKiom1kmw-uyj9D-AAARU0HDYG4339.jpg

对应的JPDL如下:


   
   
      
   
   
   
      
   
   
   
      
   
   
   

根据此流程定义发起实例:

ProcessInstance processInstance = 
		executionService.startProcessInstanceByKey("stateSequence");
//在没有任何外部触发的情况下,此流程会在a state一直等待,调用singalXx会触发流程进入下一步
Execution executionInA = processInstance.findActiveExecutionIn("a");
//断言流程实例在a state等待
assertNotNull(executionInA);
//发出触发执行的信号
processInstance = executionService.signalExecutionById(executionInA.getId());
Execution executionInB = processInstance.findActiveExecutionIn("b");
//断言流程实例走向下一步b state
assertNotNull(executionInB);
//继续触发
processInstance = executionService.signalExecutionById(executionInB.getId());
//...c

在state活动里可以定义多个transition元素,通过触发指定转移路径的名称,可以选择其中的一个transition通过。

示例:

JBPM(六)——掌握JBPM流程定义语言_第1张图片

对应的JPDL如下:


   
   
   		
   
   
   
   		
   		
   
   
   
   
   

要想该流程运行起来,则需要:

ProcessInstance processInstance = 
		executionService.startProcessInstanceByKey("stateChoice");
//假设该流程到达了wait for response,则该实例会一直等待外部触发的出现
//获得流程实例的ID
String executionId = processInstance.findActiveExecutionIn("wait for response").getId();
//触发accept信号
processInstance = executionService.signalExecutionById(executionId,"accept");
//断言流程实例流向了预期的活动
assertTrue(processInstance.isActive("submit doc"));
		
//...同理适用于reject transition


3.decision(判断活动)

    根据条件在多个流转路径中选择其一通过,也就是做一个决定性的判断,这时候使用decision。

    decision可以拥有多个流出转移,当流程实例到达decision活动时,会根据最先匹配成功的一个条件自动地通过响应的流出转移。

    decision流向哪个转移,有3种方式。

1>使用decision活动的condition元素

当一个transition的condition值为true或者一个没有设置condition的transition,那么该流程就立刻流向这个transition。

JBPM(六)——掌握JBPM流程定义语言_第2张图片

对应的JPDL:


   
   		
   
   
   	
   	
   		
   		
   	
   	
   		
   		
   	
   	
   	
   
   
   
   
   

测试代码如下:

Map variables = new HashMap<>();
	variables.put("content", "good");
//发起流程实例并传入流程变量
ProcessInstance processInstance = executionService
		.startProcessInstanceById("decisionCondition", variables);
//断言
assertTrue(processInstance.isActive("submit doc"));

2>使用decision活动的expr属性

expr属性来判断流程的转向,需要指定流转的路径

JBPM(六)——掌握JBPM流程定义语言_第3张图片

对应的JPDL如下:


   
   	
   
   
   
       
       
       
       
       
       
   
   
   
   
   

单元测试如下(同上):

Map variables = new HashMap<>();
variables.put("content", "good");
//发起流程实例并传入流程变量
ProcessInstance processInstance = executionService
		.startProcessInstanceById("decisionCondition", variables);
//断言
assertTrue(processInstance.isActive("submit doc"));

3>使用decision活动的handler元素

当判断流转时计算大量、复杂的业务逻辑时,可以实现DecisionHandler接口

DecisionHandler接口

public interface DecisionHandler extends Serializable {

  //提供流程实例的执行上下文(execution)作为参数,返回字符串类型的转移名称
  String decide(OpenExecution execution);
}

此时handler需要作为decision活动的子元素进行配置。

示例:

JBPM(六)——掌握JBPM流程定义语言_第4张图片

对应的jPDL如下:


   
      
   
   
      
      
      
      
      
   
   
   
   

对应的TestHandler如下:

public class TestHandler implements DecisionHandler {

	@Override
	public String decide(OpenExecution execution) {
		//获得流程变量content
		String content = (String)execution.getVariable("content");
		if ("great".equals(content)) {
			return "good";
		}
		if ("improve".equals(content)) {
			return "bad";
		}
		return "ugly";
	}

}

对应的单元测试如下:

Map variables = new HashMap<>();
variables.put("content", "great");
//发起流程实例并传入流程变量
ProcessInstance processInstance = executionService
		.startProcessInstanceById("decisionHandler", variables);
//断言
assertTrue(processInstance.isActive("submit doc"));

注意:decision与state的区别!

decision活动定义的流转条件没有任何一个得到满足,那么流畅实例将无法进行下去会抛出异常。

state活动有多个流出转移,且同样没有任何一个得到满足,那么会从定义的第一条流转出去。

结论:decision具有更加严格的判断,如不定义默认路径,当无法满足条件时则报错


4.fork-join(分支/聚合活动)

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

JBPM(六)——掌握JBPM流程定义语言_第5张图片

对应的jPDL:


   
      
   
   
   
      
      
      
   
   
      
   
   
      
   
   
      
   
   
   
      
   
   
   	  
   
   
   
   	  
   
   

单元测试执行上述流程定义:

//发起流程实例
ProcessInstance processInstance = executionService
		.startProcessInstanceByKey("concurrency");
String pid = processInstance.getId();
//构造一个活动集合以验证分支
Set activitys = new HashSet<>();
activitys.add("send invoice");
activitys.add("load truck");
activitys.add("print doc");
//断言当前活动即为产生的3个分支
assertEquals(activitys, processInstance.findActiveActivityNames());
//发出执行信号通过"send invoice"活动,这个时候流程会在final join等待其他分支
String sendInvoice = processInstance.findActiveExecutionIn("send invoice").getId();
processInstance = executionService
		.signalExecutionById(sendInvoice);
		
//...在活动名称集合中排除"send invoice"活动
activitys.remove("send invoice");
//此时,仍然可以断言另外2个分支还在等待
assertNotNull(processInstance.findActiveExecutionIn("load truck"));
assertNotNull(processInstance.findActiveExecutionIn("print doc"));
//发出执行信号通过剩下的第1个分支——load truck活动
String loadTruck = processInstance.findActiveExecutionIn("load truck").getId();
processInstance = executionService.signalExecutionById(loadTruck);
		
//....在活动中排除"load truck"
activitys.remove("load truck");
//发出执行信号——print doc活动
String printDoc = processInstance
	.findActiveExecutionIn("print doc").getId();
processInstance = executionService.signalExecutionById(printDoc);

//...在活动中排除"print doc"
activitys.remove("print doc");
//断言通过第一个活动shipping join,到达了drive truck
activitys.add("drive truck");
assertEquals(activitys, processInstance.findActiveActivityNames());
assertNotNull(processInstance.findActiveExecutionIn("drive truck"));
//发出执行信号通过"drive truck"
String driveTruck = processInstance.findActiveExecutionIn("drive truck").getId();
processInstance = executionService.signalExecutionById(driveTruck);
		
//最终聚合"final join"
//因此断言此流程已经不存在了
assertNull("流程:"+pid+"不存在,"+executionService.findExecutionById(pid));


5.end(结束活动)

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

简单流程定义:

JBPM(六)——掌握JBPM流程定义语言_第6张图片

对应的jPDL:


   
      
   
   

单元测试如下:

//发起流程实例
ProcessInstance processInstance = executionService
		.startProcessInstanceByKey("concurrency");
//创建流程实例后,直接断言其结束
assertTrue(processInstance.isEnded());


复杂一些的end活动

一个流程定义可以有多个end活动,以便通过事件机制触发不同的结束方式。


   
      
   
   
   
      
      
      
   
   
   
   

测试:

//发起流程实例
ProcessInstance processInstance = executionService
		.startProcessInstanceByKey("concurrency");
String executionId = processInstance.getId();
//指定转移名称:400
processInstance = executionService.signalExecutionById(executionId,"400");
//断言流程实例结束
assertTrue(processInstance.isEnded());

在实际应用中,为了表明流程实例的结束状态,可以利用end活动的state属性标识 或者利用jBPM4提供的特殊end活动:end-cancel活动end-error活动

例如:

JBPM(六)——掌握JBPM流程定义语言_第7张图片

对应的jPDL:


   
      
   
   
   
   
      
      
      
   
   
   
   
   
   
   

单元测试:测200的转移实例

//发起流程实例
ProcessInstance processInstance = executionService
		.startProcessInstanceByKey("concurrency");
String executionId = processInstance.getId();
//指定转移名称:400
processInstance = executionService.signalExecutionById(executionId,"200");
//断言流程实例的状态与预期符合,state属性值为completed
assertEquals("completed", processInstance.getState());
assertTrue(processInstance.isEnded());


6.task(人工任务活动)

用来处理涉及人机交互的活动。

1>关于任务的分配者

assignee属性将一个任务分配给指定的用户。

JBPM(六)——掌握JBPM流程定义语言_第8张图片

对应的jPDL:


   
      
   
   
   
      
   
   

注意:assignee属性引用了一个用户,即负责完成任务的人;assignee属性默认会作为EL表达式来执行,#{order.owner}意味着使用order这个名称在任务对应的流程变量中查找一个对象,然后通过order对象的getOwner方法获得用户ID。

public class Order implements Serializable{

	//owner成员域保存用户的ID
	String owner;
	public Order(String owner){
		this.owner = owner;
	}
	public String getOwner() {
		return owner;
	}
	public void setOwner(String owner) {
		this.owner = owner;
	}
}

基于此定义进行测试:

Map vars = new HashMap<>();
vars.put("order", new Order("alex"));
//发起流程实例
ProcessInstance processInstance = executionService
		.startProcessInstanceByKey("concurrency",vars);
//用户alex可通过findPersonalTasks获得
List taskList = taskService.findPersonalTasks("alex");


2.关于任务的候选者

jBPM支持将任务分配给一组候选用户,组内的一个用户可以接受这个任务并完成。

JBPM(六)——掌握JBPM流程定义语言_第9张图片

对应的jPDL如下:


   
      
   
   
   
      
   
   

流程实例发起后,任务review会被创建,但是不会显示在任何人的个人任务列表中,因为还没有创建sales-dept组。可以通过taskService.findGroupTasks来获取。

//首先创建sales-dept组
identityService.createGroup("sales-dept");
//创建用户alex
identityService.createUser("alex","alex", "Alex", "Miller");
//将alex加入sales-dept组
identityService.createMembership("alex", "sales-dept");
//创建用户joes
identityService.createUser("joes", "joes", "Joe","Smoe");
//将joes加入sales-dept组
identityService.createMembership("joes", "sales-dept");

此任务将会有2个后选择——alex和joes,候选者在处理任务之前,必须先接受任务,这时两个候选者将同时看到任务。

//接受任务

taskService.taskTask(task.getId(),"alex");

此时alex接受了任务后,就会由任务的候选者变为任务的分配者,同时此任务会从所有候选者的任务列表中消失,它会出现在alex的已分配任务列表中。


3>.关于任务分配处理器

任务分配处理器需要实现AssignmentHandler接口

任务分配处理器作为任务活动的一个子元素,名称为assignment-handler。它指向用户代码实现的AssignmentHandler接口。

JBPM(六)——掌握JBPM流程定义语言_第10张图片

对应的jPDL如下:


   
      
   
   
   
   	  
   	  
   	  	
   	  	
   	  		
   	  	
   	  
      
   
   

对应的任务分配处理器:

public class AssignTask implements AssignmentHandler {

	String assignee;
	@Override
	public void assign(Assignable assignable, OpenExecution execution)
			throws Exception {
		//设置任务的分配者
		assignable.setAssignee(assignee);
	}

}

测试代码如下:

//发起流程实例
ProcessInstance processInstance = executionService
		.startProcessInstanceByKey("concurrency");
//alex是通过定义注入的任务分配者
List taskList = taskService.findPersonalTasks("alex");
//断言alex有一个任务
assertEquals(1, taskList.size());
Task task = taskList.get(0);
//断言任务名称
assertEquals("review", task.getName());
//断言任务的分配者
assertEquals("alex", task.getAssignee());

此流程运行后到任务活动review,当review任务被创建时,AssignTask任务分配处理器被调用,此时已设置alex用户为此任务的分配者,所以alex将在他的个人任务列表中找到这个任务。


4>.关于任务泳道

在实际业务中,流程定义中的多个任务需要被分配或候选给同一个群用户,这个时候可以将"同一群用户"定义为"一个泳道"。

例如:

JBPM(六)——掌握JBPM流程定义语言_第11张图片

在此图中,有三个泳道——申请人主管财务部

JBPM(六)——掌握JBPM流程定义语言_第12张图片

对应的jPDL文件如下:


   
   
   
      
   
   
   
      
   
   

在xml中的泳道"sales"引用了一个用户组sales-dept。在流程运行前这个用户组就得需要被创建出来

identityService.createGroup("sales-dept");
//创建用户alex并加入sales-dept组
identityService.createUser("alex", "alex", "Alex","Miller");
identityService.createMembership("alex","sales-dept");

在发起流程后,alex将成为order data的唯一候选者(因为组里只有他一个用户)

//接受任务
taskService.takeTask(taskId, "alex");

接受任务后alex将成为任务的分配者,alex可以通过completeTask API完成任务

//完成任务
taskService.completeTask(taskId);

完成任务后,流程实例会流转到下一个任务"quote",这个任务也引用了泳道sales,因此,任务会直接分配给alex。

验证代码如下:

List taskList = taskService.findPersonalTasks("alex");
//断言alex直接拿到了任务
assertEquals(1, taskList.size());
Task task = taskList.get(0);
//断言是否为预期的任务和分配者
assertEquals("quote", task.getName());
assertEquals("alex", task.getAssignee());


5>.关于任务变量

任务可以读取、更新流程变量还可以定义任务自由的变量,主要作用是作为任务表单的数据容器——任务表单负责展现来自任务和流程的变量数据

6>.关于任务提醒邮件

jBPM4支持使用电子邮件进行任务提醒,邮件里的内容是根据一个模板生成出来的,默认使用jBPM内置的,可以在process-engine-context指定自定义的模板。


7.sub-process(子流程活动)