@MrXiao
2017-11-23T17:01:35.000000Z
字数 26718
阅读 2135
activiti
工作流(Workflow),就是“业务过程的部分或整体在计算机应用环境下的自动化”,它主要解决的是“使在多个参与者之间按照某种预定义的规则传递文档、信息或任务的过程自动进行,从而实现某个预期的业务目标,或者促使此目标的实现”。
Activiti5是由Alfresco软件在2010年5月17日发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理、工作流、服务协作等领域的一个开源的、灵活的、易扩展的可执行流程语言框架。Activiti基于Apache许可的开源BPM平台,创始人Tom Baeyens是JBoss jBPM的项目架构师,它特色是提供了eclipse插件,开发人员可以通过插件直接绘画出业务流程图。
这是Activiti的核心,负责生成流程运行时的各种实例及数据、监控和管理流程的运行。
业务流程建模与标注(Business Process Model and Notation,BPMN) ,描述流程的基本符号,包括这些图元如何组合成一个业务流程图(Business Process Diagram)
Activiti的后台是有数据库的支持,所有的表都以ACT_开头。第二部分是表示表的用途的两个字母标识。 用途也和服务的API对应。
这四张表很常见,基本的组织机构管理,关于用户认证方面建议还是自己开发一套,组件自带的功能太简单,使用中有很多需求难以满足 。可以使shiro完成用户认证。
Activiti核心配置文件,配置流程引擎创建工具的基本参数和数据库连接池参数。
定义数据库配置参数:
基于JDBC参数配置的数据库连接 会使用默认的MyBatis连接池。 下面的参数可以用来配置连接池(来自MyBatis参数):
示例如下:
<bean name="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="JdbcDriver" value="com.mysql.jdbc.Driver" />
<property name="JdbcUrl" value="jdbc:mysql://localhost:3306/activititest"/>
<property name="JdbcUsername" value="root" />
<property name="JdbcPassword" value="123" />
<!--
databaseSchemaUpdate:设置流程引擎启动和关闭时如何处理数据库表
false(默认):检查数据库表的版本和依赖库的版本,如果不一致就抛出异常
true:构建流程引擎是执行检查,如果需要就更新,如果表不存在就创建
create-drop:构建流程引擎是创建表,关闭时删除表
-->
<property name="databaseSchemaUpdate" value="true" />
</bean>
关于c3p0、dbcp等数据库连接池配置请参看spring(4)——持久层封装
log4j.rootLogger=WARN, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
更详细log4j说明请参看log4j.properties配置详解与实例
activiti可以到Activiti官方网站下载得到。
百度网盘下载地址,5.22.0和6.0.0两个版本,还有eclipse插件。百度网盘下载
注意:安装插件后,在Windows->Preferences->Activiti->Save菜单下勾选保存时自动生成图片。
在activiti-5.22.0→wars目录下是一些示例项目,解压activiti-rest项目,导入activiti-rest目录中WEB-INF\lib下所有jar包到classpath中。
注意还需要数据库连接驱动包,如Mysql还需要添加mysql-connector-java.jar。
@Test
public void createTable() {
//1、创建流程引擎配置对象
ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
//2、配置数据库
processEngineConfiguration.setJdbcDriver("com.mysql.jdbc.Driver");
processEngineConfiguration.setJdbcUrl("jdbc:mysql://localhost:3306/activititest");
processEngineConfiguration.setJdbcUsername("root");
processEngineConfiguration.setJdbcPassword("123");
//3、设置建表策略
processEngineConfiguration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
//4、创建流程引擎对象
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
System.out.println("processEngine"+processEngine);
}
在Activiti中,在创建核心的流程引擎对象时会自动建表。如果程序正常执行,mysql会自动建库,然后创建23张表。
在Actiiti5中定制流程必定会操作到数据库,如果都像上面那样写一大段代码会非常麻烦,所以我们可以把数据库连接配置写入配置文件。
在Activiti5的官方示例中并没有现成的配置文件,所以先得找到activiti-rest\WEB-INF\classes下有:activiti-context.xml:一个类似spring结构的配置文件,清空内容后改名为activiti.cfg.xml,用来做流程引擎的相关配置。
<bean name="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="JdbcDriver" value="com.mysql.jdbc.Driver" />
<property name="JdbcUrl" value="jdbc:mysql://localhost:3306/activititest"/>
<property name="JdbcUsername" value="root" />
<property name="JdbcPassword" value="123" />
<!--
databaseSchemaUpdate:设置流程引擎启动和关闭时如何处理数据库表
false(默认):检查数据库表的版本和依赖库的版本,如果不一致就抛出异常
true:构建流程引擎是执行检查,如果需要就更新,如果表不存在就创建
create-drop:构建流程引擎是创建表,关闭时删除表
-->
<property name="databaseSchemaUpdate" value="true" />
</bean>
Java代码如下:
@Test
public void createTable2() {
ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml");
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
}
产生方式
2.1 方式一:最基础版本,见2.3.2小节
2.2 方式二:配置文件+java代码,见2.3.3小节
@Test
public void createTable3() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
}
这种方式更加简单,会自动加载classpath下名为activiti.cfg.xml的文件。
产生各种service
//流程仓库业务类,管理流程定义、部署
RepositoryService repositoryService = processEngine.getRepositoryService();
//流程运行业务类,管理流程启动、推进、删除等操作
RuntimeService runtimeService = processEngine.getRuntimeService();
//流程任务类,管理具体的任务
TaskService taskService = processEngine.getTaskService();
//流程历史数据类
HistoryService historyService = processEngine.getHistoryService();
//流程用户业务类
IdentityService identityService = processEngine.getIdentityService();
流程仓库业务类,仓库简单理解即流程定义文档的两个文件:bpmn文件和流程图片
产生流程部署配置对象,用来定义流程部署的相关参数
@Test
public void Demo1() {
DeploymentBuilder deploymentBuilder = processEngine.getRepositoryService().createDeployment().name("test");
deploymentBuilder.addClasspathResource("diagrams/HelloWord.bpmn");
deploymentBuilder.addClasspathResource("diagrams/HelloWord.png");
Deployment deployment = deploymentBuilder.deploy();
System.out.println(deployment.getId() + "。。。。"+ deployment.getName());
}
删除流程定义
repositoryService.deleteDeployment(deploymentId);
流程执行服务类。可以从这个类中获取很多关于流程执行相关的信息。
启动流程
//常用的启动流程实例的两种方法
ProcessInstance processInstance = runtimeService.startProcessInstanceById(processInstanceId);
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processInstanceKey);
activiti的任务服务类。可以从这个类中获取任务的信息。
流程定义类。可以从这里获得资源文件等。
每个流程文件唯一对应一个相同key的流程定义,只是流程定义的version不同
流程实例。根据流程定义实例化的具体流程,我们可以利用这个对象来了解当前流程实例的进度等信息。
如:
公司有一个所有人通用的请假流程,这个叫做流程定义。
小明今天请假,填写请假单,就是创建了一个流程实例。
/** Represents one execution of a {@link ProcessDefinition}.
1.
2. @author Tom Baeyens
3. @author Joram Barrez
4. @author Daniel Meyer
5. @author Tijs Rademakers
*/
public interface ProcessInstance extends Execution {
从上面可以看出ProcessInstance就是Execution,但实际有点区别
Activiti用这个对象去描述流程执行的每一个节点。在没有并发的情况下,同ProcessInstance。
在classpath下创建流程文件
编辑流程文件
部署流程定义可以看成是添加流程定义
@Test
public void deploy() {
//创建流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//创建流程部署对象
DeploymentBuilder deploymentBuilder = processEngine.getRepositoryService().createDeployment();
//读取文件部署流程
Deployment deploy = deploymentBuilder.name("测试")
.addClasspathResource("diagrams/HelloWord.bpmn")
.addClasspathResource("diagrams/HelloWord.png")
.deploy();
System.out.println(deploy.getId() + "。。。" + deploy.getName());
}
部署流程会改变3张表
@Test
public void view() {
RepositoryService repositoryService = ProcessEngines.getDefaultProcessEngine().getRepositoryService();
List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
//过滤条件
.processDefinitionKey("HelloWorld")
//.processDefinitionId(processDefinitionId)
//分页
//.listPage(firstResult, maxResults)
//排序
//.orderByProcessDefinitionVersion()
//执行查询
.list();
for (ProcessDefinition processDefinition : list) {
System.out.print("id:" + processDefinition.getId() + ",");//流程Id
System.out.print("key:" + processDefinition.getKey() + ",");//流程Key
System.out.print("name:" + processDefinition.getName() + ",");//流程名字
System.out.print("version:" + processDefinition.getVersion() + ",");//流程版本
System.out.println("deploymentId():" + processDefinition.getDeploymentId());//流程部署对象Id
}
}
查询结果
id:HelloWorld:1:35004,key:HelloWorld,name:请假审批流程,version:1,deploymentId():35001
再部署一次,再查询
id:HelloWorld:1:35004,key:HelloWorld,name:请假审批流程,version:1,deploymentId():35001
id:HelloWorld:2:37504,key:HelloWorld,name:请假审批流程,version:2,deploymentId():37501
说明:
由查询结果可知:
3.1 流程定义的key和name与流程文件的id和name一致
<process id="HelloWorld" name="请假审批流程" isExecutable="true">
3.2 key属性被用来区别不同的流程定义。
3.3 带有特定key的流程定义第一次部署时,version为1。之后每次部署都会在当前最高版本号上加1
3.4 Id的生成规则为:{processDefinitionKey}:{processDefinitionVersion}:{generated-id},这里的generated-id是一个自动生成的唯一的数字
3.5 重复部署一次,deploymentId的值以一定的形式变化
删除部署到activiti中的流程定义。如:删除请假流程定义,则大家再也不能请假了。
@Test
public void delete() {
RepositoryService repositoryService = ProcessEngines.getDefaultProcessEngine().getRepositoryService();
String deploymentId = "7501";
//如果有关联信息,会报错
//repositoryService.deleteDeployment(deploymentId);
//如果有关联信息,会级联删除
repositoryService.deleteDeployment(deploymentId, true);
}
@Test
public void getResource() throws IOException {
RepositoryService repositoryService = ProcessEngines.getDefaultProcessEngine().getRepositoryService();
String deploymentId = "35001";
String resourceName = null;
//资源文件名,即act_re_bytearray表中的name
List<String> names = repositoryService.getDeploymentResourceNames(deploymentId);
for (String str : names) {
//过滤出特定的图片文件
if (str.indexOf("HelloWorld.png") != -1) {
resourceName = str;
}
}
System.out.println(resourceName);
if (resourceName != null) {
//通过部署ID和文件名获得输入流
InputStream inputStream = repositoryService.getResourceAsStream(deploymentId, resourceName);
File file = new File("D:/"+ resourceName);
FileUtils.copyInputStreamToFile(inputStream, file);
}
}
@Test
public void startProcess() {
RuntimeService runtimeService = ProcessEngines.getDefaultProcessEngine().getRuntimeService();
//根据流程Id启动流程
//ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId);
//根据流程key启动流程,会自动找到版本最高的流程定义来创建
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("HelloWorld");
System.out.println(processInstance.getId());//流程实例Id
System.out.println(processInstance.getActivityId());//流程当前节点Id
}
启动流程实例有很多重载方法,根据实际需要选用。
在Activiti中,任务主要分为两类:
@Test
public void taskQuery() {
TaskService taskService = ProcessEngines.getDefaultProcessEngine().getTaskService();
List<Task> list = taskService.createTaskQuery()
//查询指定用户的私人任务
//.taskAssignee("指定用户")
//查询某人可接的共有任务
//.taskCandidateUser("候选用户")
//排序
.orderByTaskCreateTime().desc()
//执行查询
.list();
for (Task task : list) {
System.out.println("id:" + task.getId());
System.out.println("name:" + task.getName());
System.out.println("assignee:" + task.getAssignee());
System.out.println("createTime:" + task.getCreateTime());
System.out.println("ProcessInstanceId:" + task.getProcessInstanceId());
System.out.println("ProcessDefinitionId:" + task.getProcessDefinitionId());
}
}
taskService.claim(taskId, userId);
将任务变为用户的私人任务
//根据任务Id办理任务
taskService.complete(taskId);
//根据任务Id办理任务,并设置任务变量
taskService.complete(taskId, variables);
variables是Map,当设置变量时,会在act_ru_variables表中添加相应条数的数据,变量可以用来执行gateway的判断,后面会讲到gateway。
在流程执行过程中,创建的流程实例ID在整个过程都不会变,当流程结束后,流程实例将被删除。
@Test
public String checkStatus() {
String processInstanceId = "1213";
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
//从运行库查询该流程实例是否存在
ProcessInstance instance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
//实例不存在,可能未开始或者已结束
if (instance == null) {
//从历史库查
HistoryService historyService = processEngine.getHistoryService();
HistoricProcessInstance singleResult = historyService.createHistoricProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
if (singleResult != null && singleResult.getEndTime() != null) {
//流程已结束
return "COMPLETE";
} else {
//流程未开始
return "NOT_START";
}
}
return instance.getActivityId();//当前任务节点
}
在前一个的例子中,大家可能会流程执行完毕后,究竟去了哪里有些疑问。虽然已完成的任务在act_ru_task和act_ru_execution表中都已被删除,但是这些数据还存在activiti的数据库中,作为历史改由HistoryService来管理。
历史是一个组件,它可以捕获发生在进程执行中的信息并永久的保存,与运行时数据不同的是,当流程实例运行完成之后它还会存在于数据库中。
在流程引擎配置对象中可以设置历史记录规则:
<bean name="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="databaseSchemaUpdate" value="true" />
<!--
Activiti提供了四种历史级别:
none:不保存任何历史,可以提高性能
activiti:保存所有流程实例、任务、活动信息
audit:默认级别,保存所有流程实例、任务、活动、表单
full:保存所有信息,如流程变量
-->
<property name="history" value="activiti"/>
由于数据库中保存着历史信息以及正在运行的流程实例信息,在实际项目中对已完成任务的查看频率远不及对代办和可接任务的查看,所以在activiti采用分开管理,把正在运行的交给runtimeService管理,而历史数据交给HistoryService来管理。
对已成为历史的数据主要进行查询操作,我们主要关心两种类型的历史数据:
@Test
public void queryHisInstance() {
//流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//流程定义ID
String processDefinitionId = "HelloWorld:2:37504";
//查询历史流程实例
List<HistoricProcessInstance> list = processEngine.getHistoryService().createHistoricProcessInstanceQuery()
.processDefinitionId(processDefinitionId)
.finished()
.orderByProcessInstanceStartTime().desc()
.list();
for (HistoricProcessInstance hp : list) {
System.out.println("ID:"+hp.getId());
System.out.println("startTime:"+hp.getStartTime());
System.out.println("endTime:"+hp.getEndTime());
}
}
1.通常查询历史流程实例都需要指定一个过滤条件,指定 processDefinitionId查看具体某一次部署所开启的流程或者指定 processDefinitionKey查看某个规则下不限版本的所有流程
2.可以选择性添加finished方法控制是否查询未完成的流程实例。在流 程开启时,activiti同时在act_ru_execution表和act_hi_procinst表中 创建了一条记录,在流程完成之前act_hi_procinst表中实例的结束时间为空
@Test
public void queryHisActiviti() {
//流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//流程实例ID
String processInstanceId = "HelloWorld:2:37504";
//查询历史流程活动
List<HistoricActivityInstance> list = processEngine.getHistoryService()
//创建历史流程活动实例查询对象
.createHistoricActivityInstanceQuery()
//过滤条件
.processInstanceId(processInstanceId)
//排序
.orderByHistoricActivityInstanceStartTime().desc()
//执行查询
.list();
for (HistoricActivityInstance ha : list) {
System.out.println("ID:"+ha.getActivityId());
System.out.println("name:"+ha.getActivityName());
System.out.println("startTime:"+ha.getStartTime());
System.out.println("endTime:"+ha.getEndTime());
}
}
通常查询历史流程活动都需要指定一个过滤条件,指定processInstanceId查看具体某一次流程执行过程中所经历的步奏
流程变量在整个工作流中扮演很重要的作用。例如:请假流程中有请假天数、请假原因等一些参数都为流程变量的范围。
流程变量的作用域范围是流程实例。也就是说各个流程实例的流程变量是不相互影响的。
流程实例结束完成以后流程变量是否保存在数据库中取决于存储策略,见第六章。
启动流程实例时添加
@Test
public void addVariables() {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
String processDefinitionKey = "HelloWorld";
Map<String,Object> variables = new HashMap<>();
variables.put("请假天数", 3);
variables.put("请假原因", "约会");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
}
说明:
1) 在启动流程实例时,通过重载startProcessInstanceByKey的方法可以加载流程变量。
2) 第二个参数要求是Map类型,意味着可以添加多个流程变量。
3) 当这段代码执行完以后,会在数据库表act_ru_variable中添加两行记录。
完成任务时添加
//办理过程中
taskService.setVariables(taskId, variables);
//提交时
taskService.complete(taskId, variables);
执行流程实例时添加
//执行流程实例时
runtimeService.setVariables(executionId, variables);
//流程实例提交时
runtimeService.signal(executionId, processVariables);//signal用于接受任务
//某个流程的变量
runtimeService.getVariables(executionId);
//某个任务的变量
taskService.getVariables(taskId);
说明:这些流程变量是从act_ru_variable这个表中读出来的。
从图中可以看出包括了大部分封装类型和Date、String和实现了Serializable接口的类的类型。
bpmn文件一个流程的根元素。一个流程就代表一个工作流。
顺序流是连接两个流程节点的连线,代表一个节点的出口。流程执行完一个节点后,会沿着节点的所有外出顺序流继续执行。 就是说,BPMN 2.0默认的行为就是并发的: 两个外出顺序流会创造两个单独的,并发流程分支。
顺序流主要由4个属性组成:
开始事件用来指明流程在哪里开始。开始事件的类型(流程在接收事件时启动, 还是在指定时间启动,等等),定义了流程如何启动, 这通过事件中不同的小图表来展示。 在XML中,这些类型是通过声明不同的子元素来区分的。
空开始事件
空开始事件技术上意味着没有指定启动流程实例的触发条件。最常用的一种开始,意味着流程的启动需要手动触发,通过调用api的startProcessInstanceByXXX方法。
ProcessInstance processInstance = runtimeService.startProcessInstanceByXXX()
图形标记:空开始事件显示成一个圆圈,没有内部图表(没有触发类型)
XML结构如下:
<startEvent id="startevent" name="Start"></startEvent>
定时开始事件
定时开始事件用来在指定的时间创建流程实例。 它可以同时用于只启动一次的流程 和应该在特定时间间隔启动多次的流程。
注意:
1.子流程不能使用定时开始事件。
2.定时开始事件在流程发布后就会开始计算时间。不需要调用startProcessInstanceByXXX,虽然也而已调用启动流程的方法,但是那会导致调用startProcessInstanceByXXX时启动过多的流程。
3.当包含定时开始事件的新版本流程部署时,对应的上一个定时器就会被删除。这是因为通常不希望自动启动旧版本流程的流程实例。
图形标记:定时开始事件显示为一个圆圈,内部是一个表。
XML内容:定时开始事件的XML内容是普通开始事件的声明,包含一个定时定义子元素。
示例:流程会启动4次,每次间隔5分钟,从2013年9月18日,12:10开始计时。
<startEvent id="theStart">
<timerEventDefinition>
<timeCycle>R4/2017-11-22T11:04/PT5M</timeCycle>
</timerEventDefinition>
</startEvent>
示例:流程会根据选中的时间启动一次。
<startEvent id="theStart">
<timerEventDefinition>
<timeDate>2017-12-31T23:59:59</timeDate>
</timerEventDefinition>
</startEvent>
结束事件表示(子)流程(分支)的结束。结束事件都是触发事件。这是说当流程达结束事件,会触发一个结果。 结果的类型是通过事件的内部黑色图标表示的。
空结束事件
空结束事件意味着到达事件时不会指定抛出的结果。这样,引擎会直接结束当前执行的分支,不会做其他事情。
图形标记:空结束事件是一个粗边圆圈,内部没有小图表(无结果类型)
XML内容:空结束事件的XML内容是普通结束事件定义,不包含子元素(其他结束事件类型都会包含声明类型的子元素)。
<endEvent id="end" name="my end event"/>
用户任务节点
用户任务用来设置必须由人员完成的工作。当流程执行到用户任务,会创建一个新任务,并把这个新任务加入到分配人或群组的任务列表中。
图形标记:用户任务显示成一个普通任务(圆角矩形),左上角有一个小用户图标。
XML内容:XML中的用户任务定义如下。id属性是必须的。 name属性是可选的。
<userTask id="theTask" name="Important task"/>
用户任务也可以设置描述(实际上所有BPMN 2.0元素都可以设置描述)。 添加documentation元素可以定义描述。
<userTask id="theTask" name="Schedule meeting">
<documentation>
Schedule an engineering meeting for next week with the new hire.
</documentation>
在实际应用中,用户接到任务后可以参照任务描述来办理任务,描述文本可以通过标准的java方法来获得:
task.getDescription()
私人任务
私有任务即有直接分配给指定用户的任务。只有一个用户可以成为任务的执行者。 在activiti中,用户叫做执行者。 拥有执行者的用户任务(即私有任务)对其他用户是不可见的。只能出现执行者的个人任务列表中.
直接把用户任务分配给指定用户使用assignee属性,XML代码如下:
<userTask id="theTask" name="my task" activiti:assignee="username"/>
Assignee属性对应的值为一个用户的ID。
直接分配给用户的任务可以通过TaskService像下面这样办理:
List<Task>tasks =taskService.createTaskQuery().taskAssignee("sirius").list();
Task task = tasks.get(0);// 假设任务集合的第一条是要办理的任务
taskService.complete(task.getId());
公有任务
有的用户任务在指派时无法确定具体的办理者,这时任务也可以加入到 人员的候选任务列表中,然后让这些人员选择性认领和办理任务。
公有任务的分配可以分为指定候选用户和候选组两种。
<userTaskid="theTask"name="my task"activiti:candidateUsers="sirius,kermit"/>
candidateUsers属性内为用户的ID,多个用户ID之间使用(半角)逗号间隔。
<userTask id="theTask" name="my task" activiti:candidateGroups="testGroup,developGroup"/>
间接分配给用户的任务,可以通过TaskService像下面这样操作:
List<Task>tasks =taskService.createTaskQuery()
.taskCandidateUser("sirius").list();
Task task = tasks.get(0);// 假设任务集合的第一条是要办理的任务
String taskId = task.getId();
taskService.claim(taskId ,“sirius”); //认领任务,让用户成为任务的执行者
taskService.complete(taskId );
说明:
如果上面的方式还不够灵活,那么我们也可以自定义一个任务分配处理器,通过代码的方式来动态设置任务的属性。XML代码如下:
<userTask id="task1" name="My task">
<extensionElements>
<activiti:taskListener event="create" class="org.activiti.MyAssignmentHandler"/>
</extensionElements>
</userTask>
DelegateTask会传递给TaskListener的实现,通过它可以设置执行人,候选人和候选组:
Public class MyAssignmentHandler implements TaskListener {
Public void notify(DelegateTask delegateTask){
// 执行用户搜索相关代码
...
// 然后把获取到的用户通过下面方法,设置给当前触发事件的任务
delegateTask.setAssignee("sirius");
//delegateTask.addCandidateUser("kermit");
//delegateTask.addCandidateGroup("testGroup");
...
}
}
接收任务节点(receiveTask)
接收任务是一个简单任务,它会等待对应消息的到达。当前,官方只实现了这个任务的java语义。 当流程达到接收任务,流程状态会保存到数据库中。在任务创建后,意味着流程会进入等待状态,直到引擎接收了一个特定的消息,这会触发流程穿过接收任务继续执行。
图形标记:接收任务显示为一个任务(圆角矩形),右上角有一个消息小标记。 消息是白色的(黑色图标表示发送语义)。
XML内容:
<receiveTask id="waitState" name="wait"/>
当前任务(一般指机器自动完成,但需要耗费一定时间的工作)完成后,向后推移流程,可以调用runtimeService.signal(executionId),传递接收任务上流程的id。
ProcessInstance pi = runtimeService.startProcessInstanceByKey("receiveTask");
Execution execution = runtimeService.createExecutionQuery()
.processInstanceId(pi.getId())
.activityId("waitState")
.singleResult();
runtimeService.signal(execution.getId());
述两个虽然都可以统称为任务节点,但是还是有本质区别:
排他网关
排他网关(也叫异或(XOR)网关,或更技术性的叫法基于数据的排他网关),用来在流程中实现决策。
图形标记:排他网关显示成一个普通网关(比如,菱形图形),内部是一个“X”图标,表示异或(XOR)语义。注意,没有内部图标的网关,默认为排他网关。BPMN2.0规范不允许在同一个流程定义中同时使用没有X和有X的菱形图形。
XML内容:排他网关的XML内容是很直接的:用一行定义了网关,条件表达式定义在外出顺序流中。参考条件顺序流 获得这些表达式的可用配置。
<exclusiveGatewayid="exclusiveGw"name="Exclusive Gateway"/>
<sequenceFlow id="flow2" sourceRef="exclusiveGw" targetRef="theTask1">
<conditionExpressionxsi:type="tFormalExpression">${input == 1}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow3" sourceRef="exclusiveGw" targetRef="theTask2">
<conditionExpressionxsi:type="tFormalExpression">${input == 2}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4" sourceRef="exclusiveGw" targetRef="theTask3">
<conditionExpressionxsi:type="tFormalExpression">${input == 3}</conditionExpression>
</sequenceFlow>
说明:
1.一个排他网关对应一个以上的顺序流
2.由排他网关流出的顺序流都有个conditionExpression元素,在内部维护返回boolean类型的决策结果。
3.决策网关只会返回一条结果。当流程执行到排他网关时,流程引擎会自动检索网关出口,从上到下检索如果发现第一条决策结果为true或者没有设置条件的(默认为成立),则流出。
4.如果没有任何一个出口符合条件则抛出异常。
并行网关
网关也可以表示流程中的并行情况。最简单的并行网关是parallelGateWay,它允许将流程分成多条分支,也可以把多条分支汇聚到一起。
图形标记:并行网关显示成一个普通网关(菱形)内部是一个“加号”图标,表示“与(AND)”语义。
XML内容:定义并行网关只需要一行
<parallelGateway id="myParallelGateway"/>
实际发生的行为(分支,聚合,同时分支聚合),要根据并行网关的顺序流来决定。
<startEvent id="theStart"/>
<sequenceFlow id="flow1" sourceRef="theStart" targetRef="fork"/>
<parallelGateway id="fork"/>
<sequenceFlow sourceRef="fork" targetRef="receivePayment"/>
<sequenceFlow sourceRef="fork" targetRef="shipOrder"/>
<userTaskid="receivePayment"name="Receive Payment"/>
<sequenceFlow sourceRef="receivePayment" targetRef="join"/>
<userTaskid="shipOrder"name="Ship Order"/>
<sequenceFlow sourceRef="shipOrder" targetRef="join"/>
<parallelGateway id="join"/>
<sequenceFlow sourceRef="join" targetRef="archiveOrder"/>
<userTask id="archiveOrder"name="Archive Order"/>
<sequenceFlow sourceRef="archiveOrder" targetRef="theEnd"/>
<endEventid="theEnd"/>
上面例子中,流程启动之后,会创建两个任务:
ProcessInstancepi =runtimeService.startProcessInstanceByKey("forkJoin");
List<Task> tasks = taskService.createTaskQuery().list();
Task task1 =tasks.get(0);
System.out.println("Receive Payment"+task1.getName());
Task task2 =tasks.get(1);
System.out.println("Ship Order"+task2.getName());
当两个任务都完成时,第二个并行网关会汇聚两个分支,因为它只有一条外出连线,不会创建并行分支,只会创建归档订单任务。
说明:
1.并行网关的功能是基于进入和外出的顺序流的:
分支(fork):并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
汇聚(join):所有到达并行网关,在此等待的进入分支,直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。
2.并行网关的进入和外出都是使用相同节点标示
3.如果同一个并行网关有多个进入和多个外出顺序流,它就同时具有分支和汇聚功能。这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。
4.并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。
5.并行网关不需要是“平衡的”(比如,对应并行网关的进入和外出节点数目相等)。如图中标示是合法的:
在流程中我们有时会对整个流程或者一个节点的某种状态做出相应的处理。这时就会用到监听器。
在Activiti中流程的监听主要分为两大类,执行监听器和任务监听器。
执行监听器可以执行外部java代码或执行表达式,当流程定义中发生了某个事件。 可以捕获的事件有:
现在有这样一个简单流程,只包含开始、结束、接收任务和用户任务4个节点:
配置监听器,XML代码如下
<extensionElements>
<activiti:executionListener event="start" class="test.ProcessStartListener"></activiti:executionListener>
</extensionElements>
说明:
任务监听器支持以下属性:
public class ProcessStartListener implements ExecutionListener {
private static final long serialVersionUID = 1L;
@Override
public void notify(DelegateExecution execution) throws Exception {
System.out.println("ID:"+execution.getId()+", Name:"+execution.getCurrentActivityName());
}
}
执行监听器配置可以放在以下三个地方,如图
a) 监听整个流程的启动和结束状态,配置为process节点的子元素,如①
b) 监听一个节点的启动和结束状态,配置为一个节点的子元素,如②和③
c) 监听一条连线的执行,配置在sequenceFlow节点的内部,只有task一种事件,如④
启动流程测试代码如下:
@Test
public void Demo1() {
//部署流程
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("diagrams/Listener.bpmn")
.addClasspathResource("diagrams/Listener.png")
.deploy();
//启动
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("ListenerTest");
//推动
String processInstanceId = processInstance.getId();
runtimeService.signal(processInstanceId);
String id = taskService.createTaskQuery().processInstanceId(processInstanceId).singleResult().getId();
taskService.complete(id);
}
测试结果如下:
ID:60005, Name:开始
ID:60005, Name:接收任务
ID:60005, Name:用户任务
任务监听器可以在发生对应的任务相关事件时执行自定义java逻辑 或表达式。
任务监听器只能添加到流程定义中的用户任务中。在之前任务节点上添加任务监听:
任务监听器支持以下属性
新添加的任务监听包裹在executionListener监听的内部,顺序为:execution Start--> task Assignment-->task Create-->task Complete-->execution End-->execution take。
虽然前面的例子中我们可以自己手动来创建相应的API实例,但是在一个项目中这些API都应该以单例形式存在的。和Spring的集成主要就是把Activiti的主要对象交给Spring容器管理。
<!-- 流程引擎 -->
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
<property name="processEngineConfiguration" ref="processEngineConfiguration"></property>
</bean>
<!-- 流程引擎配置对象 -->
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="transactionManager" />
<!-- activiti表数据更新策略 false true(默认) create-drop-->
<property name="databaseSchemaUpdate" value="true" />
<!-- activiti历史数据保存策略 none activity audit(默认) full-->
<property name="history" value="audit" />
<!-- 管理线程计时器和异步消息,默认开启,但有ManagementService替代,避免冲突所以关闭 -->
<property name="jobExecutorActivate" value="false" />
<!-- 加载资源文件 -->
<property name="deploymentResources" value="classpath:/activiti-process/*.bpmn" />
</bean>
<!-- 核心业务类 -->
<bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService" />
<bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService" />
<bean id="taskService" factory-bean="processEngine" factory-method="getTaskService" />
<bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService" />
<bean id="managementService" factory-bean="processEngine" factory-method="getManagementService" />
注意:
1. ProcessEngineConfiguration
单独使用和整合Spring时不是同一个class
独立使用:org.activiti.engine.ProcessEngineConfiguration
整合Spring:org.activiti.spring.SpringProcessEngineConfiguration
2. 项目部署后会自动在数据库中创建相关流程定义