目录
Activiti流程任务
任务是流程的核心元素之一,任务表示在流程中需要完成的工作。BPMN2.0中定义了多种任务,每种任务都有不同的属性,完成不同的工作。
1、BPMN2.0任务
BPMN2.0中定义了多种不同的任务,每种任务都有其特定的行为。BPMN2.0中定义的任务有Service Task、Send Task、Receive Task、User Task、Manual Task、Business Rule Task和Script Task。Activiti支持BPMN2.0中定义的大部分任务,并且对这些任务进行了相应的扩
展,例如Service Task在Activiti中可以体现为Java Service Task、Web Service Task等。
1.1、任务的继承
BPMN2.0为任务在流程文件中的定义提供了规范,遵守BPMN2.0规范的流程引擎都需要按照其提供的XML约束来定义流程,也可以根据流程引擎自身的需要添加额外的XML元素或者属性。BPMN2.0在定义流程文件的XML约束时,根据不同流程元素的特点,定义了整套流程元素的继承机制,关于任务的继承关系如图所示。
流程文件中全部的XML元素均直接或者间接继承于BaseElement,其中FlowElement下有两种子元素:FlowNode和SequenceFlow,即流程节点和顺序流,而FlowNode下有三类子元素:Activity(行为)、Event(事件)和Gateway(网关)。Activity表示流程中行为流程节点,流程中表示行为的元素有三类:SubProcess(嵌入子流程)、CallActivity(调用子流程)和Task(任务)。
1.2、XML约束
对于流程文件中的每个XML元素,BPMN2.0均提供了XML约束,任务元素的ML约束如代码所示。
<xsd:element name="baseElement" type="tBaseElement"/>
<xsd:complexType name="tBaseElement" abstract="true">
<xsd:sequence>
<xsd:element ref="documentation" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="extensionElements" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="optional"/>
<xsd:anyAttribute namespace="##other" processContents="lax"/>
</xsd:complexType>
<xsd:element name="flowElement" type="tFlowElement"/>
<xsd:complexType name="tFlowElement" abstract="true">
<xsd:complexContent>
<xsd:extension base="tBaseElement">
<xsd:sequence>
<xsd:element ref="auditing" minOccurs="0" maxOccurs="1"/>
<xsd:element ref="monitoring" minOccurs="0" maxOccurs="1"/>
<xsd:element name="categoryValueRef" type="xsd:QName" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
...
1.3、任务的类型
BPMN2.0有以下类型的任务。
- Service Task:服务任务可以用于调用Web Service或者自动执行程序,Activiti中的Java Service Task、Web Service Task、Shell Task为BPMN2.0中定义的Service Task,对应的XML元素为serviceTask。
- Send Task:发送任务表示处理向外部的流程参与人发送消息的工作,根据这个定义,Activiti中的Email Task属于这种任务,但是Activiti的官方文档却使用serviceTask元
素来配置,而笔者在查看Activiti的源代码时发现,Activiti的这两种任务,既可以使用serviceTask元素进行配置,也可以使用sendTask元素进行配置,该任务对应的XML元素为sendTask。 - Receive Task:接收任务是一种等待外部流程参与者发送消息的任务,换言之,当流程到达该任务时,需要外界告诉该任务接收到消息,它才会继续执行。对于该类任务,目前Activiti只对Java进行了实现,因此Activiti中只有Java Receive Task,Receive Task对应的XML元素为receiveTask。
- User Task:用户任务是典型的流程元素之一,它表示需要有人参与的任务,对应Activiti中的User Task任务,XML元素为userTask。
- Manual Task:手动任务并不需要任务的流程引擎或者应用的驱动,它会自动执行,在工作流中,它表示一种工作己经完成,工作流引擎不需要关心它是如何完成的。该任务对应的XML元素为manualTasl。
- Business Rule Task:业务规则任务,主要用于向规则引擎发送请求参数,让其按照既定的业务规则进行运算并返回结果,当前Activiti只对JBoss的Drools规则引擎提供支
持,对应的XML元素为businessRuleTask。 - Script Task:脚本任务用于执行定义好的脚本程序,流程到达该任务后,这些脚本程序被执行,执行完成后任务结束,对应XML元素为scriptTask。
2、用户任务
一般的业务流程大多都会有人的参与,因此用户任务是最常用的任务,当流程到达用户任务时,用户任务会被分配到特定用户或者用户组。
在流程文件中,可以使用userTask元素定义一个用户任务:
<process id="process1" name="process1">
<userTask id="usertask1">
<documentation>task doc</documentation>
</userTask>
<startEvent id="startevent1" name="Start"></startEvent>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" name="" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
</process>
在代码中定义了一个id为“usertask1”的用户任务,在userTask元素下定义了documentation子元素,该子元素是BaseElement定义的子元素,因此流程文件中的全部元素(除根元素外)均可以使用该子元素来配置描述信息。为userTask配置了描述信息,当流程启动并到达这个用户任务时,会将这里所配置的任务描述信息写入任务数据表
(ACT_RU_TASK)的DESCRIPTION字段中。如果需要在代码中获取这些信息,可以使用task的getDescription方法。
2.1、分配任务候选人
任务的候选人是指有权限对该任务进行操作的潜在用户群体,这个用户群体有权限处理(处理、完成)该任务。设置这种权限可以使用TaskService的addCandidateUser方法或者
addCandidateGroup方法(见8.2节),也可以通过XML配置的方式为任务分配候选人。
<process id="process1" name="process1">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="Task1">
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>user(angus), group(management), boss
</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" name="" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
</process>
用户可以作为多种角色被分配到流程活动中,BPMN2.0提供了humanPerformer和potentialOwner元素来实现角色的分配,代码使用了potentialOwner元素,该元素继承于resourceRole,resourceRole可以作为子元素被配置在Activity元素下,而Task就是Activity的子元素,那么就可以使用potentialOwner。
在formalExpression中,定义了用户“anugs”和用户组“management’”、“boss”为该任务的候选人,该配置的效果等同于在流程运行时使用TaskService的
addCandidateGroup方法和addCandidateUser方法,如果需要指定某个用户为该任务的候选人,则需要使用user(userId)这样的表达式,用户组的话,可以使用group(groupld)来指定。如果不使用user或者group而直接使用字符串,则会被直接视作用户组的ID,效果等同于
group(groupId)。
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
// 部署流程文件
repositoryService.createDeployment().addClasspathResource("demo12/Candidate.bpmn").deploy();
// 启动流程
runtimeService.startProcessInstanceByKey("process1");
// 根据用户组查询任务
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("boss").list();
System.out.println("分配到boss用户组下的任务数量:" + tasks.size());
// 根据用户查询任务
tasks = taskService.createTaskQuery().taskCandidateUser("angus").list();
System.out.println("用户angus下的任务数量为:" + tasks.size());
分配到boss用户组下的任务数量:1
用户angus下的任务数量为:1
2.2、分配任务代理人
可以为一个任务分配多个候选人,而一个任务只允许有一个代理人。可以使用TaskService的setAssignee方法设置任务的代理人,设置了任务代理人后,ACT_RU_TASK表的
ASSIGNEE字段会被设置为相应的值。除了使用setAssignee方法外,还可以使用XML配置的方式分配任务代理人。
<process id="process1" name="process1">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="Task 1">
<humanPerformer>
<resourceAssignmentExpression>
<formalExpression>user1</formalExpression>
</resourceAssignmentExpression>
</humanPerformer>
</userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" name="" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
</process>
2.3、权限分配扩展
可以使用Activiti的扩展属性来实现这个功能。BPMN2.0规范允许各个流程引擎对规范进行扩展,可以为规范中定义的流程元素添加个性化的属性,前提是不能与BPMN
2.0规范相违背。
<process id="process1" name="process1">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="Assignee" activiti:assignee="user1"></userTask>
<userTask id="usertask2" name="Candidate User"
activiti:candidateUsers="user1, user2"></userTask>
<userTask id="usertask3" name="Candidate Group"
activiti:candidateGroups="group1,group2"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" name="" sourceRef="usertask1"
targetRef="usertask2"></sequenceFlow>
<sequenceFlow id="flow3" name="" sourceRef="usertask2"
targetRef="usertask3"></sequenceFlow>
<sequenceFlow id="flow4" name="" sourceRef="usertask3"
targetRef="endevent1"></sequenceFlow>
</process>
代码中使用activiti:assignee属性分配任务代理人,使用activiti:candidateUsers属性来分配任务候选人,使用activiti::candidateGroups来分配任务的候选用户组。
2.4、使用任务监听器进行权限分配
除了可以使用BPMN2.0的ML元素和Activiti的扩展属性来分配任务候选人和任务代理人外,还可以编写自定义的任务监听器,在监听器的实现中使用编码方式进行权限分配。在般的应用系统中,用户组和用户均有可能发生变化,将用户和用户组写死到流程文件中显然是不合适的,因此可以使用任务监听器进行动态权限分配。
public class UserTaskListener implements TaskListener {
public void notify(DelegateTask delegateTask) {
System.out.println("使用任务监听器设置任务权限");
delegateTask.setAssignee("user1");
delegateTask.addCandidateGroup("group1");
delegateTask.addCandidateUser("user1");
}
}
<process id="process1" name="process1">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="Assignee">
<extensionElements>
<activiti:taskListener event="create"
class="pers.zhang.listener.UserTaskListener"></activiti:taskListener>
</extensionElements>
</userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" name="" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
</process>
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
// 部署流程文件
repositoryService.createDeployment().addClasspathResource("demo12/TaskListener.bpmn").deploy();
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1");
// 进行任务查询
List<Task> tasks = taskService.createTaskQuery().taskAssignee("user1").list();
System.out.println(tasks.size());
使用任务监听器设置任务权限
1
2.5、使用JUEL分配权限
Activiti默认对JUEL表达式提供支持,因此在进行任务权限分配时,也可以使用JUEL表达式来指定。使用JUEL可以直接调用自定义JavaBean里面的方法,例如可以使用
${object..method()}
或者${object.field}
来调用方法或者获取属性值,但是前提是该对象需要被设置到流程参数中。
<process id="process1" name="process1" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask2" name="Task 1" activiti:assignee="${authService.getUserAssignee()}"></userTask>
<userTask id="usertask3" name="Task 2" activiti:candidateUsers="${authService.getCandidateUsers()}"></userTask>
<userTask id="usertask4" name="Task 3" activiti:candidateGroups="${authService.getCandidateGroups()}"></userTask>
<userTask id="usertask5" name="Task 4" activiti:assignee="${authService.lastUser}"></userTask>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask2"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="usertask3" targetRef="usertask4"></sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow4" sourceRef="usertask4" targetRef="usertask5"></sequenceFlow>
<sequenceFlow id="flow5" sourceRef="usertask5" targetRef="endevent1"></sequenceFlow>
</process>
public class AuthService implements Serializable {
private String lastUser = "angus";
public String getLastUser() {
return this.lastUser;
}
public AuthService() {
System.out.println("create AuthService");
}
//使用方法为任务指定代理人
public String getUserAssignee() {
return "crazyit";
}
//使用方法为任务指定候选人
public List<String> getCandidateUsers() {
List<String> result = new ArrayList<String>();
result.add("user1");
result.add("user2");
return result;
}
//使用方法为任务指定候选用户组
public List<String> getCandidateGroups() {
List<String> result = new ArrayList<String>();
result.add("group1");
result.add("group2");
return result;
}
}
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
// 部署流程文件
repositoryService.createDeployment().addClasspathResource("demo12/JUELAuth.bpmn").deploy();
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("authService", new AuthService());
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1", vars);
// 查询第一个任务
Task task = taskService.createTaskQuery().processInstanceId(pi.getProcessInstanceId()).singleResult();
System.out.println("第一个任务代理人:" + task.getAssignee());
//完成第一个任务
taskService.complete(task.getId());
// 查询第二个任务
task = taskService.createTaskQuery().processInstanceId(pi.getProcessInstanceId()).singleResult();
// 查询任务与用户的关联
List<IdentityLink> links = taskService.getIdentityLinksForTask(task.getId());
System.out.println("第二个任务的候选用户:");//结果2
for (IdentityLink link : links) {
System.out.println(" " + link.getUserId());
}
//完成第二个任务
taskService.complete(task.getId());
// 查询第三个任务
task = taskService.createTaskQuery().singleResult();
links = taskService.getIdentityLinksForTask(task.getId());
System.out.println("第三个任务的候选用户组:");//结果2
for (IdentityLink link : links) {
System.out.println(" " + link.getGroupId());
}
// 完成第三个任务
taskService.complete(task.getId());
// 查找第四个任务
task = taskService.createTaskQuery().singleResult();
System.out.println("第四个用户的代理人:" + task.getAssignee())
3、脚本任务
脚本任务由流程引擎执行,在定义脚本任务时,需要为流程引擎提供它可以解析并执行的脚本语言,流程到达脚本任务时,流程引擎会执行定义好的脚本,任务将会在脚本执行后完成。
使用以下配置片断可定义一个Script Task:
<scriptTask id="scripttaskl"name="Script Task"scriptFormat="juel">
<script></script>
</scriptTask>
定义一个脚本任务,需要指定脚本的格式,如果不指定,Activiti会认为提供的是UEL表达式。JUEL是统一表达式语言(Unified Expression Language)的Java实现。Activiti的流程文件中许多地方都可以使用JUEL表达式,例如User Task、Service Task、Script Task和网关等。
3.1、脚本任务
脚本任务支持多种脚本语言,前提是提供的语言与JSR-223规范兼容。随着PHP、Ruby、JavaScript等脚本语言的广泛使用,为了能在Java中使用这些脚本语言,从Java6开始,Java提供了JSR-223规范,该规范定义了Java对这些脚本语言进行解析与执行的标准,从而为Java执行这些脚本语言提供了可能。在Java6中,集成了Rhino作为默认的JavaScript引擎,而在Java8中将Rhino替换为Oracle Nashorn。
//创建脚本引擎管理对象
ScriptEngineManager manager = new ScriptEngineManager();
//获取JavaScript的脚本引擎
ScriptEngine engine = manager.getEngineByName("javascript");
//执行一段脚本
engine.eval("for (var i = 0; i < 5; i++) {print(i);}");
3.2、JavaScript脚本
Java默认支持执行JavaScript脚本,因此可以在Script Task中直接将JavaScript作为任务的脚本。
<process id="process1" name="process1">
<startEvent id="startevent1" name="Start"></startEvent>
<scriptTask id="scripttask1" name="Script Task"
scriptFormat="javascript">
<script><![CDATA[
var myVar = "angus";
execution.setVariable("user", myVar);
]]></script>
</scriptTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1"
targetRef="scripttask1"></sequenceFlow>
<userTask id="usertask1" name="End Task"></userTask>
<sequenceFlow id="flow2" name="" sourceRef="scripttask1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow3" name="" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
</process>
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
repositoryService.createDeployment().addClasspathResource("demo12/JavaScriptTask.bpmn").deploy();
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1");
// 获取在JavaScript中设置的参数
String user = (String)runtimeService.getVariable(pi.getId(), "user");
System.out.println("获取用JavaScript设置的参数:" + user);
获取用JavaScript设置的参数:angus
3.3、Groovy脚本
Groovy是基于JVM的一种动态语言,它结合了SmallTalk、Ruby等语言的特性,使用Groovy可以很好地与Java进行结合。Groovy官方提供的groovy-jsr223-2.4.8包中已经包含了JSR-223的实现,因此可以在脚本任务中指定使用Groovy脚本。
<process id="process1" name="process1">
<startEvent id="startevent1" name="Start"></startEvent>
<scriptTask id="scripttask1" name="Script Task" scriptFormat="groovy">
<script>
org.crazyit.activiti.GroovyScriptTask.print(execution);
</script>
</scriptTask>
<userTask id="usertask1" name="End Task"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1"
targetRef="scripttask1"></sequenceFlow>
<sequenceFlow id="flow2" name="" sourceRef="scripttask1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow3" name="" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
</process>
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
repositoryService.createDeployment().addClasspathResource("demo12/GroovyScriptTask.bpmn").deploy();
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1");
3.4、设置返回值
在任务脚本中,可以得到execution变量,并且可以使用execution的方法,代码中就使用了execution的setVariable方法设置流程参数,除此之外,直接在脚本中定义的变量,也会被设置为流程参数,例如以下的JavaScript脚本:
<script>
var user1 = "a";
var user2 = "b";
</script>
那么在获取参数时,可以根据“user1”来获取参数值“a”,也可以根据“user2”来获取参数值“b”,而并不需要使用execution.setVariable方法。对于Groovy脚本,使用以下代码可以设置流程参数:
<script>
user1 = "a";
user2 = "b";
</script>
同样,也可以根据“user1”和“user2”来获取相应的参数值,但是如果使用以下Groovy脚本,就不能设置流程参数:
<script>
def user1 = "a";
def user2 = "b";
</script>
如果在脚本中想将某些值作为参数设置到流程中,并且想显式指定变量名称,则可以为scriptTask元素加上activiti:resultVariable属性,属性值为参数的变量名称。
<process id="process1" name="process1">
<startEvent id="startevent1" name="Start"></startEvent>
<scriptTask id="scripttask1" name="Script Task"
scriptFormat="groovy" activiti:resultVariable="user">
<script>
execution.id
</script>
</scriptTask>
<userTask id="usertask1" name="End Task"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1"
targetRef="scripttask1"></sequenceFlow>
<sequenceFlow id="flow2" name="" sourceRef="scripttask1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow3" name="" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
</process>
3.5、JUEL脚本
除了可以使用支持JSR223规范的脚本语言外,默认情况下,还可以使用JUEL表达式,使用JUEL表达式可以进行值的输出和Java Bean的调用。在脚本任务中,同样支持使用UEL表达式。
<process id="process1" name="process1">
<startEvent id="startevent1" name="Start"></startEvent>
<scriptTask id="scripttask1" name="Script Task" scriptFormat="juel">
<script>
${myBean.print(execution)}
</script>
</scriptTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1"
targetRef="scripttask1"></sequenceFlow>
<sequenceFlow id="flow2" name="" sourceRef="scripttask1"
targetRef="endevent1"></sequenceFlow>
</process>
public class MyBean implements Serializable {
public void print(Execution exe) {
System.out.println("执行Java Bean的方法,流程ID为:" + exe.getId());
}
}
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
repositoryService.createDeployment().addClasspathResource("demo12/JUELScript.bpmn").deploy();
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("myBean", new MyBean());
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1", vars);
执行Java Bean的方法,流程ID为:15009