JAVAWEB开发之工作流详解——Activiti核心API的使用(流程定义和流程实例的管理流程变量监听器...)以及与Spring的集成

Posted LIUXUN1993728

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVAWEB开发之工作流详解——Activiti核心API的使用(流程定义和流程实例的管理流程变量监听器...)以及与Spring的集成相关的知识,希望对你有一定的参考价值。

管理流程定义

设计流程定义文档


bpmn文件

设置方式可以直接使用插件图形化界面进行设置

为某任务节点指定任务执行者


保存后的BPMN文件可以使用XML编辑器打开 BPMN 2.0根节点是definitions节点。 这个元素中,可以定义多个流程定义(不过我们建议每个文件只包含一个流程定义, 可以简化开发过程中的维护难度)。 一个空的流程定义看起来像下面这样。注意,definitions元素 最少也要包含xmlns 和 targetNamespace的声明。 targetNamespace可以是任意值,它用来对流程实例进行分类。
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://activiti.liuxun/test">
  <!-- 流程定义部分 -->  
  <process id="myProcess" name="My First process" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <userTask id="usertask1" name="员工申请请假" activiti:assignee="员工"></userTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"></sequenceFlow>
    <userTask id="usertask2" name="部门经理审批" activiti:assignee="经理"></userTask>
    <sequenceFlow id="flow2" name="提交申请" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
    <userTask id="usertask3" name="总经理审批" activiti:assignee="总经理"></userTask>
    <sequenceFlow id="flow3" name="经理审批" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow4" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
  </process>
 <!-- BPMN绘图规范定义部分(用来描述节点图标的大小和坐标) -->
  <bpmndi:BPMNDiagram id="BPMNDiagram_myProcess">
    <bpmndi:BPMNPlane bpmnElement="myProcess" id="BPMNPlane_myProcess">
      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="40.0" y="138.0"></omgdc:Bounds>
        ......
    </bpmndi:BPMNPlane>
       ......
  </bpmndi:BPMNDiagram>
</definitions>
说明:流程定义文档由两部分组成 (1) bpmn文件 流程规则文件。在部署后,每次系统启动时都会被解析,把内容封装成流程定义放入项目缓存中。Activiti框架结合XML文件自动管理流程。 (2) 展示流程图的图片 在系统里需要展示流程进展的图片

部署流程定义

部署流程定义也可以认为是增加流程定义
/**
 * 1.部署流程定义
 */
@Test
public void deploy() 
	// 创建流程引擎
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	// 创建部署环境配置对象
	DeploymentBuilder deploymentBuilder = processEngine.getRepositoryService().createDeployment();
	// 部署流程
	
	// 方式一:读取单个的流程定义文件
	Deployment deployment = deploymentBuilder
			.name("测试") //设置部署流程的名称
			.addClasspathResource("activiti/lx/processdefination/MyProcess.bpmn") //设置流程文件
			.addClasspathResource("activiti/lx/processdefination/MyProcess.png") //设置流程文件
			.deploy(); // 部署
	System.out.println("部署ID: "+deployment.getId());
说明 (1) 通过getDefaultProcessEngine() 首先获得默认的流程引擎,在创建时会自动加载classpath下的activiti.cfg.xml或activiti-context.xml (2) 通过流程引擎获取一个RepositoryService对象(仓库服务对象)
(3) 由仓库的服务对象可以获取一个部署环境的配置对象,用来封装部署环境的相关配置。
(4) 部署环境的配置对象(deploymentBuilder) 支持链式编程,在部署配置对象中设置显示名,上传规则文件相对的classpath的地址
(5) 部署,也是往数据库中存储流程定义的过程。
(6) 部署流程定义在数据库中主要操作三张表 a) ACT_RE_DEPLOYMENT 存放流程定义的显示名和部署时间,每部署一次增加一条记录。
b) ACT_RE_PROCDEF 存放流程定义的属性信息,部署每个新的流程定义都会在这张表中插入一条记录。
c) ACT_RE_BYTEARRAY  存放流程定义相关的部署信息。即流程定义文档的存放地。每部署一次就会增加两条记录,一条是关于bpmn规则文件的,另一条是图片的(如果部署时只指定了bpmn文件,activiti会在部署时解析bpmn文件内容自动生成流程图)。两个文件不是很大,都是以二进制的形式存储在数据库中。
此种方式只适合部署单个的流程,可以以Zip格式进行部署(将所有的流程规则文件与图片压缩成ZIP格式文件)
/**
 * 1.部署流程定义(部署zip格式文件)
 */
@Test
public void deploy2() 
	// 创建流程引擎
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	// 创建部署环境配置对象
	DeploymentBuilder deploymentBuilder = processEngine.getRepositoryService().createDeployment();
	// 部署流程
	
	ZipInputStream zipInputStream = new ZipInputStream(this.getClass().getClassLoader().getResourceAsStream("activiti/lx/processdefination/process.zip"));
	// 方式二:读取zip压缩文件
	Deployment deployment = deploymentBuilder
			.name("测试zip部署") //设置部署流程的名称
			.addZipInputStream(zipInputStream )
			.deploy();
	System.out.println("部署ID: "+deployment.getId());

查看流程定义

/**
 * 2. 查看流程规则的信息(流程定义)
 */
@Test
public void view() 
	// 创建流程引擎对象
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	// 获取流程定义信息
	RepositoryService repositoryService = processEngine.getRepositoryService();
	ProcessDefinitionQuery definitionQuery = repositoryService.createProcessDefinitionQuery();
	// 添加过滤条件
	// definitionQuery.processDefinitionName("");
	 definitionQuery.processDefinitionKey("myProcess");
	// 添加分页条件
	// definitionQuery.listPage(0, 10);
	// 添加排序条件
	// definitionQuery.orderByProcessDefinitionId();
	
	List<ProcessDefinition> list = definitionQuery.list();
	// 迭代效果查看流程定义
	for (ProcessDefinition pd : list) 
		System.out.print("id= "+pd.getId()+", ");
		System.out.print("name= "+pd.getName()+", ");
		System.out.print("key= "+pd.getKey()+", ");
		System.out.print("version= "+pd.getVersion()+", ");
		System.out.println("deploymentId= "+pd.getDeploymentId()+", ");
	
	
结果:
再部署一次的运行结果为:

说明: (1) 首先获取流程引擎对象 (2) 然后通过流程引擎对象获取RepositoryService (3)通过RepositoryService实例可以获取查询对象实例 (4) 根据查询实例可以设置过滤参数,设置查询以及排序条件,获得符合条件的流程定义列表。 (5)由运行结果可以看出: a) Key和Name的值为:bpmn文件process节点的id和name的属性值
  <process id="myProcess" name="My First process" isExecutable="true">
b) key属性被用来区别不同的流程定义。
c) 带有特定key的流程定义第一次部署时,version为1。之后每次部署都会在当前最高版本号上加1
d) Id的值的生成规则为:processDefinitionKey:processDefinitionVersion:generated-id, 这里的generated-id是一个自动生成的唯一的数字
e) 重复部署一次,deploymentId的值以一定的形式变化
f) 流程定义(ProcessDefinition)在数据库中没有相应的表对应,只是从act_ge_bytearray表中取出相应的bpmn和png图片,并进行解析。

删除流程定义

删除部署到activiti中的流程定义
/**
 *3.删除流程定义
 */
@Test
public void  delDeploy() 
	// 创建流程引擎对象
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	// 设置要删除的部署的ID
	String deploymentId = "101";
	// 删除指定的部署信息,如果有关联信息则报错(例如启动了流程定义相关的流程实例)
	// processEngine.getRepositoryService().deleteDeployment(deploymentId );
	// 删除指定的部署信息,如果有关联信息则级联删除
	// 第二个参数cascade,代表是否级联删除
	processEngine.getRepositoryService().deleteDeployment(deploymentId, true);
说明: (1) 因为删除的是流程定义,而流程定义的部署是属于仓库服务的,所以应该首先获取RespositoryService (2) 如果该流程定义在没有正在运行的情况下,可以使用普通的删除方式。如果有关联的信息,可以使用第二种级联强制删除(删除所有关联的信息)。由于级联删除涉及的数据比较多,一般只开放给超级管理员使用。

获取流程定义文档的资源

查询流程定义文档。主要查看的是图片,用于显示流程使用。
/**
 * 4.获取流程定义中的资源文件(查看流程图片)
 * 
 * @throws IOException
 */
@Test
public void getResource() throws IOException 
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	String deploymentId = "1";
	// 获取指定ID流程定义下的所有资源文件的名称列表
	List<String> names = processEngine.getRepositoryService().getDeploymentResourceNames(deploymentId);
	String resourceName = null;
	// 遍历资源文件名称列表
	for (String string : names) 
		// 获取'.png'结尾名称为流程图片名称
		if (string.endsWith(".png")) 
			resourceName = string;
		
	
	// 如果流程图片存在
	if (resourceName != null) 
		InputStream in = processEngine.getRepositoryService().getResourceAsStream(deploymentId, resourceName);
		// 指定拷贝目录
		File file = new File("/Users/liuxun/Downloads/" + resourceName);
		// 原始方式
		// OutputStream out = new FileOutputStream(file);
		// byte[] b = new byte[1024];
		// int len = 0;
		// while((len=in.read(b))!=-1) 
		// out.write(b, 0, len);
		// 
		// out.close();
		// 使用FileUtils文件操作工具类,将流程图片拷贝到指定目录下
		FileUtils.copyInputStreamToFile(in, file);
	

查看ACT_GE_BYTERRAY
运行结果:

1)deploymentId为流程部署ID
2)resourceName为act_ge_bytearray表中NAME_列的值
3)使用repositoryService的getDeploymentResourceNames方法可以获取指定部署下得所有文件的名称
4)使用repositoryService的getResourceAsStream方法传入部署ID和文件名称可以获取部署下指定名称文件的输入流
5)最后的有关IO流的操作,使用FileUtils工具的copyInputStreamToFile方法完成流程流程到文件的拷贝

查询最新版本的流程定义

/**
 * 获取最新版本的流程定义
 */
@Test
public void getLatestVersion() 
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	ProcessDefinitionQuery query = processEngine.getRepositoryService().createProcessDefinitionQuery();
	query.orderByProcessDefinitionId().asc();
	List<ProcessDefinition> list = query.list();
	HashMap<String, ProcessDefinition> map = new HashMap<String, ProcessDefinition>();
	for (ProcessDefinition pd : list) 
		map.put(pd.getKey(), pd);
	
	ArrayList<ProcessDefinition> lastList = new ArrayList<>(map.values());
	for (ProcessDefinition processDefinition : lastList) 
		System.out.println(processDefinition.getName()+" "+processDefinition.getVersion());
	

流程实例管理

启动流程实例

在完成流程定义部署后,就可以启动流程实例了
// 启动流程实例
@Test
public void startProcess() 
	// 创建流程引擎
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	// 获取流程引擎服务对象
	RuntimeService runtimeService = processEngine.getRuntimeService();
	// 启动流程,返回流程实例对象
	ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess");
	// 显示相关信息
	System.out.print("pid= "+processInstance.getId()); //流程实例ID
    System.out.println("activityId= "+processInstance.getActivityId());		
说明: (1) 结果为: (2) 操作数据库的ACT_RU_EXECUTION表,如果是用户任务节点,同时也会在ACT_RU_TASK中添加一条记录 注意:按照key启动流程 会默认根据最新版本的流程定义规则创建流程实例 ACT_RU_EXECUTION
ACT_RU_TASK

查询任务

在activiti任务中,主要分为两大类:
1.确切指定了办理者的任务,这个任务将成为指定者的私有任务
2.无法指定具体的某一个人来办理的任务,可以把任务分配给几个人或者一到多个小组,让这个范围内的用户可以选择性(如有空余时间时)来办理这类任务。

查询指定用户的代办任务

对指定用户的未完成的个人任务执行查询(由某一个人负责办理 在任务列表中通过assignee字段指定)
/**
 * 2.1 查看指定用户的代办任务
 */
@Test
public void findUnfinishedTask() 
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	// 获取个人的代办信息
	TaskService taskService = processEngine.getTaskService();
	// 创建流程任务查询对象
	TaskQuery taskQuery = taskService.createTaskQuery();
	String assignee = "员工";
	// 添加过滤条件
	taskQuery.taskAssignee(assignee);
	// 添加分页条件
	taskQuery.listPage(0, 10);
	// 添加过滤条件
	taskQuery.orderByTaskCreateTime().desc();
	// 执行查询
	List<Task> list = taskQuery.list();
	System.out.println("===============【"+assignee+"】的个人任务列表===============");
	// 迭代结果,查看个人任务
	for (Task task : list) 
		System.out.print("id="+task.getId()+","); //获取任务的ID
		System.out.print("name="+task.getName()+",");//获取任务的名称
		System.out.print("assign="+task.getAssignee()+",");//查询任务的代办人
		System.out.print("createTime="+task.getCreateTime()+",");//查询任务的创建时间
		System.out.println("executionId="+task.getExecutionId());//获取流程执行对象的ID
	
运行结果:
说明:
1)因为是任务查询,所以从processEngine中应该得到TaskService
2)使用TaskService获取到任务查询对象TaskQuery
3)为查询对象添加查询过滤条件,使用taskAssignee指定任务的候选者(即查询指定用户的代办任务),添加分页排序等过滤条件
4)调用list方法执行查询,返回办理者为指定用户的任务列表
5)任务ID、名称、办理人、创建时间可以从act_ru_task表中查到。
6)Execution与ProcessInstance的关系与分支有关。在这种单线流程中,ProcessInstance相当于Execution
7)如果assignee属性为部门经理,结果为空。因为现在流程只到了”填写请假申请”阶段,后面的任务还没有执行,即在数据库中没有部门经理可以办理的任务,所以查询不到。
8)一个Task节点和Execution节点是1对1的情况,在task对象中使用Execution_来标示他们之间的关系
9)任务ID在数据库表act_ru_task中对应“ID_”列

查询指定用户的可接任务(公共任务)

对指定用户的可接收的公共任务执行查询
/**
 * 2.2 查看指定用户的可接任务(公共任务)
 */
@Test
public void findCanTakeTask() 
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	// 获取个人的代办信息
	TaskService taskService = processEngine.getTaskService();
	// 创建流程任务查询对象
	TaskQuery taskQuery = taskService.createTaskQuery();
	String candidateUser = "员工1";
	// 添加过滤条件
	taskQuery.taskCandidateUser(candidateUser );
	// 添加分页条件
	taskQuery.listPage(0, 10);
	// 添加过滤条件
	taskQuery.orderByTaskCreateTime().desc();
	// 执行查询
	List<Task> list = taskQuery.list();
	System.out.println("===============【"+candidateUser+"】的可接收任务列表===============");
	// 迭代结果,查看个人任务
	for (Task task : list) 
		System.out.print("id="+task.getId()+","); //获取任务的ID
		System.out.print("name="+task.getName()+",");//获取任务的名称
		System.out.print("assign="+task.getAssignee()+",");//查询任务的代办人
		System.out.print("createTime="+task.getCreateTime()+",");//查询任务的创建时间
		System.out.println("executionId="+task.getExecutionId());//获取流程执行对象的ID
	
1.前面步骤类似,查询任务首先使用TaskService创建TaskQuery对象
2.在查询对象上,添加taskCandidateUser过滤条件,代表过滤任务候 选者为自己的任务
3.调用list方法返回指定用户的可接任务列表
4.所有公共任务的assignee属性为空

认领任务

通常一个公共任务都有一个以上的候选者,用户想要办理它应该先进行认领任务操作,即把自己变成任务的拥有者
/**
 * 3.认领任务
 */
@Test
public void takeTask() 
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	// 接手的任务ID
	String taskId = "304";
	// 接手任务的员工ID
	String userId = "员工1";
	// 让"员工1"认领ID为"304"的任务
	processEngine.getTaskService().claim(taskId, userId);
说明: (1) 任务相关操作,首先获取TaskService (2) 确定被认领的任务ID和认领人的ID (3) 调用taskService的claim(认领)方法,把公共任务变成指定用户的私有任务

退回任务

退回任务是针对公共任务来说的,在公共任务被某个候选人领取后变成了私人任务,这时如果处于某种原因,候选人可以将领取到的任务退回变成公共任务。
/**
 * 3.2退回任务(将个人任务变成公共任务)
 * 
 */
@Test
public void backTask() 
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    String taskId = "304";
    String userId = "员工1";
	processEngine.getTaskService().setAssignee(taskId, userId);		

办理任务

指定任务ID,完成该任务
/**
 * 4。办理任务(完成任务后,让流程往后移)
 */
@Test
public void completeTask() 
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	// 指定任务ID
	String taskId = "104";
	// 完成任务
	processEngine.getTaskService().complete(taskId );
依次执行 查看任务表


流程执行完毕后,任务表里与该流程实例相关的记录就会被删除 说明: 1)是办理任务,所以从ProcessEngine得到的是TaskService。
2)当执行完这段代码,再以员工的身份去执行查询的时候,会发现这个时候已经没有数据了。
3)对于执行完的任务,activiti将从act_ru_task表中删除该任务,下一个任务会被插入进来。
4)以”经理”的身份进行查询,可以查到结果。因为流程执行到经理审批这个节点了。
5)再执行办理任务代码,执行完以后以”经理”身份进行查询,没有结果。
6)重复第3和4步直到流程执行完。

验证流程已经结束

在流程指定的过程中,创建的流程实例的ID在整个过程中都不会变,当流程结束后,流程实例将会被删除。
/**
 * 5. 验证流程是否结束
 */
@Test
public void checkProcessEnded() 
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	RuntimeService runtimeService = processEngine.getRuntimeService();
	ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
	// 通过流程定义ID获取到流程定义实例对象
	String procId = "101";
	ProcessInstance processInstance = processInstanceQuery.processDefinitionId(procId).singleResult();
	// 查看流程定义是否已经完成
	if (processInstance!= null) 
		System.out.println("当前活动ID为:"+processInstance.getActivityId());
	 else 
        System.out.println("ID为【"+procId+"】的流程实例已经结束");
	
说明: (1) 因为是流程实例查询,所以先获取runtimeService (2) 创建流程查询对象,设置实例ID过滤参数 (3)由于一个流程实例ID只对应一个实例,使用singleResult执行查询返回一个唯一的结果,如果结果数量大于1,则抛出异常
(4)判断指定ID的实例是否存在,如果结果为空,则代表流程结束,实例已被删除

流程历史

虽然已完成的任务在act_ru_task和act_ru_execution表中都已被删除,但是这些数据还存在activiti的数据库中,作为历史改由HistoryService来管理。
历史是一个组件,它可以捕获发生在进程执行中的信息并永久的保存,与运行时数据不同的是,当流程实例运行完成之后它还会存在于数据库中。
在流程引擎配置对象中可以设置历史记录规则:
<!-- 配置流程引擎配置对象 -->
	<bean id="processEngineConfiguration"
		class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
		<property name="jdbcDriver" value="com.mysql.jdbc.Driver" />
		<property name="jdbcUrl" value="jdbc:mysql:///activiti" />
		<property name="jdbcUsername" value="root" />
		<property name="jdbcPassword" value="root" />
		<property name="databaseSchemaUpdate" value="true" />
		<!-- 
		 Activiti默认提供了4种历史流程
			 none:不保存任何历史记录,可以提高系统性能
			 activity:保存所有的流程实例、任务、活动信息
			 audit:也是activiti的默认级别,保存所有的流程实例、任务、活动、表单属性
			 full:最完整的记录,除了包含audit级别的信息之外还能保存流程变量等信息
		 -->
		 <property name="history" value="activity"/>
	</bean>
由于数据库中保存着历史信息以及正在运行的流程实例信息,在实际项目中对已完成任务的查看频率远不及对代办和可接任务的查看,所以在activiti采用分开管理,把正在运行的交给runtimeService管理,而历史数据交给HistoryService来管理。
对已成为历史的数据主要进行查询操作,我们主要关心两种类型的历史数据:
HistoricProcessInstance 包含当前和已经结束的流程实例信息。
HistoricActivityInstance 包含一个活动(流程上的节点)的执行信息 。

HistoricTaskInstance 包含一个任务的相关信息
刚才运行完一个流程实例 查看历史表中有关的数据 如下图 ACT_HI_PROCINST
ACT_HI_ACTINST

ACT_HI_TASKINST

查看历史流程实例

查看用户按照某个流程规则执行了多少次流程
/**
 * 1. 查看历史流程实例
 * 查看系统一共按照某个规则执行了多少次流程
 */
@Test
public void queryHistoryInstance() 
	// 创建流程引擎
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	HistoryService historyService = processEngine.getHistoryService();
	// 创建流程实例查询对象
	HistoricProcessInstanceQuery instanceQuery = historyService.createHistoricProcessInstanceQuery();
	// 指定流程定义ID
	String processDefinitionId = "myProcess:1:4";
	// 添加过滤条件
	instanceQuery.processDefinitionId(processDefinitionId);
	// 排序条件
	instanceQuery.orderByProcessInstanceStartTime().desc();
	// 查询已经完成的实例
	instanceQuery.finished();
	// 分页查询
	instanceQuery.listPage(0, 10);
	// 执行查询
	List<HistoricProcessInstance> list = instanceQuery.list();
	for (HistoricProcessInstance hp : list) 
		System.out.print("ID: "+hp.getProcessDefinitionId()+", ");
		System.out.print("开始活动ID: "+hp.getStartActivityId()+", ");
		System.out.print("开始时间: "+hp.getStartTime()+", ");
		System.out.println("结束时间: "+hp.getEndTime()+", ");
	

说明: (1) 通常查询历史流程实例都需要指定一个过滤条件,指定processDefinitionId查看具体某一次部署所开启的流程或者指定processDefinitionKey查看某个规则下不限版本的所有流程
(2) 可以选择性添加finished方法控制是否查询未完成的流程实例。在流程开启时,activiti同时在act_ru_execution表和act_hi_procinst表中创建了一条记录,在流程完成之前act_hi_procinst表中实例的结束时间为空

查看历史流程活动

查看某次流程执行过程中所经历的步骤
/**
 * 2.查看历史流程活动
 * 查看某次流程执行过程中所经历的步骤
 */
@Test
public void queryHistoryActiviti() 
	// 获取流程引擎
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	HistoryService historyService = processEngine.getHistoryService();
	// 查看历史流程实例活动对象
	HistoricActivityInstanceQuery activityInstanceQuery = historyService.createHistoricActivityInstanceQuery();
	// 添加过滤条件
	String processInstanceId = "101"; // 指定流程实例ID
	activityInstanceQuery.processInstanceId(processInstanceId);
	// 执行查询
	List<HistoricActivityInstance> list = activityInstanceQuery.list();
	for (HistoricActivityInstance ha : list) 
		System.out.print("流程定义ID: "+ha.getProcessDefinitionId()+", ");
		System.out.print("开始活动ID: "+ha.getActivityId()+", ");
		System.out.print("开始活动名称: "+ha.getActivityName()+", ");
		System.out.print("开始时间: "+ha.getStartTime()+", ");
		System.out.println("结束时间: "+ha.getEndTime()+", ");
	
	

说明:通常查询历史流程活动都需要指定一个过滤条件,指定processInstanceId查看具体某一次流程执行过程中所经历的步骤

查看历史任务

/**
 * 3.查看历史任务数据
 * 任务与活动的区别:活动是一个动作,从A->B节点 是一个活动
 * 任务是执行某个节点
 */
@Test
public void queryHistorTask() 
	// 获取流程引擎
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	HistoryService historyService = processEngine.getHistoryService();
	// 查看历史流程实例任务
	HistoricTaskInstanceQuery taskInstanceQuery = historyService.createHistoricTaskInstanceQuery();
	// 添加过滤条件
	String processInstanceId = "101"; // 指定流程实例ID
	taskInstanceQuery.processInstanceId(processInstanceId);
	// 执行查询
	List<HistoricTaskInstance> list = taskInstanceQuery.list();
	for (HistoricTaskInstance taskInstance : list) 
		System.out.print("任务名称:"+taskInstance.getName()+", ");
		System.out.print("执行者:"+taskInstance.getAssignee()+", ");
		System.out.println("任务开始时间:"+taskInstance.getStartTime());
	
	

流程变量

流程变量在整个工作流中扮演很重要的作用。例如:请假流程中有请假天数、请假原因等一些参数都为流程变量的范围。流程变量的作用域范围是流程实例。也就是说各个流程实例的流程变量是不相互影响的。

添加流程变量

在启动流程实例时

在启动流程实例时,可以添加流程变量。这是添加流程变量的一种时机。
/**
 * 1. 在流程启动时添加流程变量
 */
@Test
public void startFlow() 
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	RuntimeService runtimeService = processEngine.getRuntimeService();

	String processDefinitionKey = "QJLC";
	Map<String, Object> variables = new HashMap<>();
	variables.put("请假天数", "3");
	variables.put("请假原因", "家里有事");
	ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
	System.out.println("pid: "+processInstance.getId());

说明: (1) 在启动流程实例时,通过重载startProcessInstanceByKey方法 可以加载流程变量 (2) 此方法的第二个参数要求是Map<String,Object>类型,意味着可以添加多个流程变量 (3) 当这段代码执行完毕后,会在数据库表ACT_RU_VARIABLE中添加两行记录

在办理任务或任务提交时

在办理任务时或者有时候任务办理完成以后,需要传递一些信息到系统中,这时可以使用TaskService类来添加流程变量。
/**
 * 1.2 在办理任务和任务提交时添加流程变量
 */
@Test
public void completeTask_setVar() 
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	String taskId = "606";
	Map<String, Object> variables1 = new HashMap<>() ;
	variables1.put("请假日期", "明天");
	/**
	 * 在任务办理的过程中提交流程变量
	 */
	processEngine.getTaskService().setVariables(taskId, variables1);
	// 一次只能设置一个变量
    processEngine.getTaskService().setVariable(taskId, "请假送礼", "两盒红塔山");
    
	/**
     * 在任务提交时,设置流程变量
     */
    Map<String, Object> variables2 = new HashMap<>();
    variables2.put("批注", "请领导批准");
    processEngine.getTaskService().complete(taskId, variables2);
查看ACT_RU_VARIABLE表

说明: (1) 利用setVariables方法在任务办理的过程中可以添加多个流程变量 (2) 利用setVariable方法在任务办理过程中可以添加一个流程变量 (3) TaskService有一个重载complete的方法 可以在提交任务时提交流程变量

执行任务实例时

因为流程变量的作用域就是流程实例,所以可以为流程实例设置流程变量,为流程实例设置了流程变量,在该流程的任何阶段都可以获取得到。
/**
 * 1.3 为指定的流程实例设置流程变量
 */
@Test
public void execution_setVar() 
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	RuntimeService runtimeService = processEngine.getRuntimeService();
	String executionId = "601";
	Map<String, Object> variables = new HashMap<>();
	variables.put("value", "流程变量");
	// 在流程实例的办理过程中设置流程变量
	runtimeService.setVariables(executionId, variables);
	// runtimeService.setVariable(executionId, variableName, value);

	// 在流程实例提交时设置流程变量
	// runtimeService.signal(executionId, variables);
查看ACT_RU_VARIABLE表

获取流程变量

通过runtimeService获取

/**
 * 2.1 获取流程变量(使用RuntimeService)
 */
@Test
public void runtimeServ_getVar() 
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	RuntimeService runtimeService = processEngine.getRuntimeService();
	System.out.println("=======指定单个属性获取变量=========");
	String executionId = "601";
	String variableName = "请假原因";
	String value = (String) runtimeService.getVariableLocal(executionId, variableName);
	System.out.println(variableName+"="+value);
	
	System.out.println("=======指定多个属性获取变量=========");
	Collection<String> variableNames = new ArrayList<>();
	variableNames.add("请假天数");
	variableNames.add("请假日期");
	Map<String, Object> variables = runtimeService.getVariables(executionId, variableNames );
	for (String key : variables.keySet()) 
		System.out.println(key+"="+variables.get(key));
	
	
	System.out.println("=======指定全部属性获取变量=========");
	Map<String, Object> variables2 = runtimeService.getVariables(executionId);
	for (String key : variables2.keySet()) 
		System.out.println(key+"="+variables2.get(key));
	

通过TaskService获取

/**
 * 2.2 通过TaskService 获取
 */
@Test
public void taskSer_getVar() 
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	String taskId = "705";
	Map<String, Object> variables = processEngine.getTaskService().getVariables(taskId );
	for (String key : variables.keySet()) 
		System.out.println(key+"="+variables.get(key));
	

流程变量范围

官方给出的流程变量类型如下:
从图中可以看出包括了大部分封装类型和Date、String都实现了Serializable接口的类的类型。

JavaBean类型流程变量

(1) 加入一个JavaBean,此JavaBean必须实现Serializable接口
package activiti.lx.variable;

import java.io.Serializable;

public class User implements Serializable 
	private Long id;
	private String name;

	public User() 
	

	public User(Long id, String name) 
		this.id = id;
		this.name = name;
	

	public Long getId() 
		return id;
	

	public void setId(Long id) 
		this.id = id;
	

	public String getName() 
		return name;
	

	public void setName(String name) 
		this.name = name;
	


(2) 设置JavaBean类型流程变量
/**
 * 3.1 设置JavaBean流程变量
 */
@Test
public void setJavaBeanVar() 
	String executionId = "601";
	Map<String, Object> variables = new HashMap<>();
	variables.put("user", new User(1L, "小明"));
	ProcessEngines.getDefaultProcessEngine().getRuntimeService().setVariables(executionId, variables);

(3) 获取JavaBean流程变量
/**
 * 3.2 获取JavaBean流程变量
 */
@Test
public void getJavaBeanVar() 
	String executionId = "601";
	Map<String, Object> variables = ProcessEngines.getDefaultProcessEngine().getRuntimeService()
			.getVariables(executionId);
    for (Entry<String, Object> entry : variables.entrySet()) 
		if (entry.getKey().equals("user")) 
			User user = (User) entry.getValue();
			System.out.println(user.getId());
			System.out.println(user.getName());
		
	

流程定义语言

流程(process)

process是bpmn文件中一个流程的根元素,一个流程代表一个工作流

顺序流(sequenceFlow)

顺序流是连接两个流程节点的连线,代表一个节点的出口。流程执行完一个节点后,会沿着节点的所有外出顺序流继续执行。也就是说,BPMN2.0默认的行为就是并发的:两个外出的顺序流会创造两个单独的并发流程分支。 顺序流(sequenceFolw)是process的子节点


顺序流主要由4个属性组成: id:唯一标识,用来区分不同的顺序流。 sourceRef:连接线的源头节点id。 targetRef:连接线的目标节点id。 name(可选):连线的名称,不涉及业务,主要用于显示。 说明: (1) 结束节点没有出口 (2) 其他节点有一个或多个出口。如果有一个出口,则代表是一个单线流程;如果有多个出口,则代表的是开启并发流程。

节点

主要有开始节点、结束节点、任务节点、网关节点、事件节点

开始事件节点(startEvent)

开始事件节点用来指明流程在哪里开始。开始事件的类型(流程在接收事件时启动还是在指定的时间启动等等),这通过事件中不同的小图表来展示。在XML中,这些类型是通过声明不通话的子元素来区分的。

空开始事件

空开始事件在技术上意味着没有指定启动流程实例的触发条件。是最常用的一种开始节点,意味着流程启动需要手动触发,通过API的startProcessInstanceByXXX方法。
ProcessInstance processInstance = runtimeService.startProcessInstanceByXXX();
图形标记:空开始事件显示成一个圆圈,没有内部图表(没有触发类型)
XML结构如下:
    <startEvent id="startevent1" name="Start"></startEvent>

定时开始事件

定时开始事件用来在指定的时间创建流程实例。它可以用于只启动一次的流程和应该在特定的时间间隔启动多次的流程。 注意: 1.子流程不能使用定时开始事件 2. 定时开始事件在流程发布后就会开始计算时间,不需要再手动调用startProcessInstanceByXXX 否则会导致启动过多的流程 3. 当包含定时开始事件的新版本流程部署时,对应的上一个定时器就会被删除。这是因为通常不希望自动启动旧版本流程的流程实例 图形标记 定时开始事件显示为一个圆圈,内部是一个表

配置界面:

Time duration:每隔多长时间执行一次(无限循环) Time date:在指定的时间执行一次 Time Cycle:按照一定格式配置,从某时间点开始计时,一共执行次数,执行的时间间隔 注意:只能选取使用一种配置 XML内容: 定时开始事件的XML内容是普通开始事件的声明,包含一个关于定时定义的子元素。 示例1:
表示:流程一共会执行4次,每次时间间隔为5分钟,从2013年9月18日,12:13分开始计时
<startEvent id="timerstartevent1" name="Timer start">
  <timerEventDefinition>
    <timeCycle>R4/2013-09-18T12:13/PT5M</timeCycle>
  </timerEventDefinition>
</startEvent>
示例2:
表示流程根据配置的时间启动一次
<startEvent id="timerstartevent1" name="Timer start">
  <timerEventDefinition>
    <timeDate>2013-10-31T23:59:24</timeDate>
  </timerEventDefinition>
</startEvent>

结束事件节点

结束事件表示(子)流程(分支)的结束。结束事件都是触发事件。这是说当流程达到结束时间,会触发一个结果。结果的类型是通过事件的内部黑色图标表示的。

空结束事件

空结束事件意味着到达事件时不会指定抛出的结果。 这样,引擎会直接结束当前执行的分支,不会做其他事情。
图形标记:
空结束事件是一个粗边圆圈,内部没有小图表(无结果类型)
 
XML内容:
空结束事件的XML内容是普通结束事件定义,不包含子元素 (其他结束事件类型都会包含声明类型的子元素)。
   <endEvent id="endevent4" name="End"></endEvent>

任务节点(TASK)

接收任务节点(receiveTask)

接收任务是一个简单任务,它会等待对应消息的到达。当前,官方只实现了这个任务的语义.当流程达到接收任务,流程状态会保存到数据库中。 在任务创建后,意味着流程会进入等待状态,直到引擎接收了一个特定的消息,才能触发流程穿过接收任务继续执行。 图形标记: 接收任务显示为一个任务(圆角矩形),右上角有一个消息小标记。 消息是白色的(黑色图标表示发送语义)
     
XML内容
<receiveTask id="receivetask1" name="Receive Task"></receiveTask>
当前任务(一般指机器自动完成,但需要耗费一定时间的工作)完成后,向后推移流程,可以调用runtimeService.signal(executionId),传递接收任务上流程的id 演示如下: (1) 绘制流程图
为了方便测试,删除数据库中所有的ACT开头的表 执行的关键代码如下: (2)初始化环境,加载新的流程图(流程定义规则)
// 初始化测试环境
@Test
public void deploy() 
	ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
	RepositoryService repositoryService = processEngine.getRepositoryService();
	DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
	deploymentBuilder.addClasspathResource("activiti/lx/task/receive/receiveTask.bpmn")
			.addClasspathResource("activiti/lx/task/receive/receiveTask.png").name("部署测试接收任务");
	Deployment deploy = deploymentBuilder.deploy();
	System.out.println("部署ID:" + deploy.getId() + "\\t部署名称" + deploy.getName());


(3) 启动流程实例
// 启动流程实例
@Test
public void startProcessInst() 
	String processDefinitionKey = "receivePro

以上是关于JAVAWEB开发之工作流详解——Activiti核心API的使用(流程定义和流程实例的管理流程变量监听器...)以及与Spring的集成的主要内容,如果未能解决你的问题,请参考以下文章

JavaWeb开发之详解Servlet及Servlet容器

详解工作流框架Activiti的服务架构和组件

工作流引擎使用详解!工作流框架Activiti的详细配置以及安装和使用

详解工作流框架Activiti的服务架构和组件

原Spring activiti 环境搭建之数据库创建

工作流引擎详解!工作流开源框架ACtiviti的详细配置以及安装和使用