Starter
依赖包
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>${activiti-version}</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>${activiti-version}</version>
</dependency>
根据需要,加入DB,例如:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.21</version>
</dependency>
<dependency>
<groupId>oracle</groupId>
<artifactId>oracle</artifactId>
<version>11.2</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.168</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.4-1201-jdbc41</version>
</dependency>
流程设计工具
- 使用Activiti Modeler(网页流程设计器)
- 一般适用于业务设计人员
- Signavio 授权的基于Web的开源BPMN设计器
- BPMN 2.0规范
- 获取后置于tomcat中启用,获取方式:
- 方式一:
svn checkout http://signavio-core-components.googlecode.com/svn/trunk
- 方式二:activiti包下的
activiti-explorer.war
(内置了Activiti Modeler组件)
- 方式一:
- 还可将此组件加入到自己项目中,为项目提供在线流程设计功能:
<dependency> <groupId>org.activiti</groupId> <artifactId>activiti-modeler</artifactId> <version>${activiti-version}</version> </dependency>
- 使用Activiti Designer(Eclipse插件)
- 一般适用于开发人员
ProcessEngine(流程引擎)
ProcessEngineConfiguration:
org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration
: 单独运行的流程引擎。- Activiti会自己处理事务
- 默认
- 数据库只在引擎启动时检测 (如果没有Activiti的表或者表结构不正确就会抛出异常)
org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration
: 单元测试时的辅助类。- Activiti会自己控制事务。
- 默认使用H2内存数据库。
- 数据库表会在引擎启动时创建,关闭时删除。
- 使用它时,不需要其他配置(除非使用job执行器或邮件功能)
org.activiti.spring.SpringProcessEngineConfiguration
: 在Spring环境下使用流程引擎org.activiti.engine.impl.cfg.JtaProcessEngineConfiguration
: 单独运行流程引擎,并使用JTA事务。
创建ProcessEngine:
方式一:
/*
* 不使用配置文件,创建ProcessEngine对象
*/
@Test
public void envTest1(){
//1.创建流程引擎配置对象ProcessEngineConfiguration
ProcessEngineConfiguration configuration=ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
//2.配置
//2.1)完善数据库相关配置--这里使用MySQL(默认为H2)
configuration.setJdbcDriver("com.mysql.jdbc.Driver");
configuration.setJdbcUrl("jdbc:mysql://localhost:3306/activitiTest?createDatabaseIfNotExist=true");
configuration.setJdbcUsername("xxxx");
configuration.setJdbcPassword("xxxxxx");
//2.2)配置数据库建表策略,默认为false
configuration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
//3.创建ProcessEngine
ProcessEngine processEngine=configuration.buildProcessEngine();
System.out.println(processEngine.getName());
}
方式二:
/*
* 使用配置文件,加载创建ProcessEngine对象
* 默认加载classpath下的activiti.cfg.xml文件
*/
@Test
public void envTest2(){
//最简单获取ProcessEngine对象的方式
ProcessEngine processEngine=ProcessEngines.getDefaultProcessEngine();
System.out.println(processEngine.getName());
}
activiti.cfg.xml:
<bean id="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?createDatabaseIfNotExist=true"/>
<property name="jdbcUsername" value="xxxx"/>
<property name="jdbcPassword" value="xxxxxx"/>
<property name="databaseSchemaUpdate" value="true"/>
</bean>
ProcessDefinition(流程定义)
使用设计工具生成流程定义文件:
- bpmn流程规则文件(xml格式)
- png流程图片(自动生成)
- 例如:HelloWorld.bpmn,HelloWorld.png
通过RepositoryService
对ProcessDefinition
进行操作
ProcessEngine processEngine=ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService=this.processEngine.getRepositoryService();
发布流程定义
- 方式一:逐个添加资源文件
- 方式二:添加zip包 (Activiti框架会自动解压解析,依次添加到
act_ge_bytearray
表中)
@Test
public void deployProcess()
{
DeploymentBuilder builder=repositoryService.createDeployment();
//方式一:
/*InputStream bpmnInput=this.getClass().getClassLoader().getResourceAsStream("bpmn/HelloWorld.bpmn");
InputStream pngInput=this.getClass().getClassLoader().getResourceAsStream("bpmn/HelloWorld.png");
builder.name("Hello World!")
.addInputStream("HelloWorld.bpmn", bpmnInput)
.addInputStream("HelloWorld.png", pngInput)
.deploy();*/
//方式二:
InputStream in=this.getClass().getClassLoader().getResourceAsStream("bpmn/HelloWorld.zip");
ZipInputStream zipInputStream =new ZipInputStream(in);
builder.name("Hello World!")
.addZipInputStream(zipInputStream)
.deploy();
}
发布成功后,会在3张表中添加数据:
act_re_deployment
: 1条记录:name
:Process的显示别名deploy_time
:发布时间
act_ge_bytearray
: 一般为2条记录(通过deployment_id
关联act_re_deployment
表):- 1条bpmn文件内容
- 1条png文件内容
act_re_procdef
: 1条记录(Process定义的相关信息,通过deployment_id
关联act_re_deployment
表)id
:{key}:{version}:{random}version
:发布时计算(同key的maxVersion+1,默认取1)name
:bpmn文件中定义的process节点的name属性key
:bpmn文件中定义的process节点的id属性deployment_id
:关联act_re_deployment表
PS:一定要有bpmn流程规则文件,png流程图片可选,也可加入其它文件,比如video,txt等
查看流程定义信息
Process概括信息:ProcessDefinition
- 流程定义对象:流程规则的整体信息
- 记录在
act_re_procdef
表中
Process内部子节点(即活动)等详细信息:
ActivityImpl
- 活动对象:流程规则的活动信息
- 记录在bmpn文件中(运行时加载解析bmpn文件到内存中)
注意:
- 通过
ProcessDefinitionQuery
对象获取的是最简单的ProcessDefinition对象
(从act_re_procdef
表中查询) - 要获取Process内部的活动定义信息,需通过
RepositoryService
的getProcessDefinition(id)
方法(从内存中获取存放的活动Activity定义信息)
- 通过
@Test
public void queryProcessDefinition()
{
String key="helloWorld";
List<ProcessDefinition> pds=repositoryService.createProcessDefinitionQuery().processDefinitionKey(key).list();
for(ProcessDefinition pd:pds)
{
System.out.println("id:"+pd.getId()+",name:"+pd.getName()+",key:"+pd.getKey()+",version:"+pd.getVersion());
//System.out.println(((ProcessDefinitionImpl)pd).getActivities()); //这样无法获得Activity信息 (这个ProcessDefinition是从DB中查询得到的)
ProcessDefinitionImpl pdImpl=(ProcessDefinitionImpl)repositoryService.getProcessDefinition(pd.getId());
//这个ProcessDefinition是对应的bpmn文件解析构建的,即内存中(实际是ProcessDefinitionEntity对象)
System.out.println(pdImpl.getActivities()); //activity中包含Transitions等信息 (Actitity即节点)
}
}
查看流程附件
查看流程附件(例如:流程图片)
- 存放在表act_ge_bytearray的Bytes字段中(type为BLOB)
- 通过 deployment_id & 文件名name 查找
@Test
public void queryProcessImage() throws IOException
{
//eg1.获取附件,例如png图片,也可获取其他resource文件:
String deploymentId="201";
List<String> resources=repositoryService.getDeploymentResourceNames(deploymentId); //通过发布ID,获取此发布的所有资源文件名
String imgName=null;
for(String res:resources) //文件名过滤
{
System.out.println(res);
if(res.endsWith(".png")){
imgName=res;
break;
}
}
if(imgName!=null && imgName.length()!=0){
//查找指定资源,以流的形式读出
InputStream in=repositoryService.getResourceAsStream(deploymentId, imgName);
FileUtils.copyInputStreamToFile(in, new File("D:/hello.png"));
}
//eg2.直接获取流程图:
String processDefinitionId="helloWorld:2:304";
InputStream in=repositoryService.getProcessDiagram(processDefinitionId);
FileUtils.copyInputStreamToFile(in, new File("D:/hello2.png"));
}
删除流程定义
删除发布的流程 (act_re_deployment
):
方式一:普通删除
repositoryService.deleteDeployment(deploymentId);
- 等价于:
repositoryService.deleteDeployment(deploymentId, false);
- 只删除发布流程相关的信息
- 若当前
ProcessDefinition
有正在执行的ProcessInstance
,则删除失败(保护),因为act_ru_*
中有对ProcessDefinition id的引用 - 删除下表记录:
act_re_deployment
act_ge_bytearray
act_re_procdef
- 不会删除history(
act_hi_*
)
- 等价于:
方式二:级联删除
repositoryService.deleteDeployment(deploymentId, true);
- 会删除
ProcessDefinition
和ProcessInstance
,包括history
(暴力) - 删除下表记录:
act_re_deployment
act_ge_bytearray
act_re_procdef
act_ru_*
act_hi_*
- 会删除
@Test
public void deleteDeployment()
{
String key="myProcess";
List<Deployment> deployments=repositoryService.createDeploymentQuery().processDefinitionKey(key).list();
for(Deployment deployment:deployments)
{
//方式一:
repositoryService.deleteDeployment(deployment.getId());
//方式二:
//repositoryService.deleteDeployment(deployment.getId(), true);
}
}
ProcessInstance(流程实例)
ProcessInstance特点:
- 一个Process只有一个ProcessInstance
- ID不变
- 可自定义ID规则,eg:{对象名称}-{对象ID}
- 流程实例ProcessInstance是一个特殊的Execution对象(
ProcessInstance extends Execution
)- 代表一次执行,永远指向当前所在的活动节点(类似遍历链式结构的指针,指向当前节点)
- 对于单线流程,描述每个节点的Execution对象就是ProcessInstance;
- 对于分支流程,会在分支的最顶部创建一个Execution对象作为ProcessInstance来描述这个分支( 每个分支下产生的Execution对象都会挂在此ProcessInstance下)
启动流程
启动流程(生成ProcessInstance):runtimeService.startProcessInstanceByXxx
- 方式一:通过processDefinition的Id启动流程
- 方式二:通过ProcessDefinition的key启动流程(系统会使用此key下版本最高的ProcessDefinition)
@Test
public void startProcess(){
//RuntimeService 负责流程的启动,流转等操作
RuntimeService runtimeService=this.processEngine.getRuntimeService();
//方式一:
/*String id="helloWorld:1:204";
ProcessInstance processInstance=runtimeService.startProcessInstanceById(id);*/
//方式二:
String key="helloWorld";
ProcessInstance processInstance=runtimeService.startProcessInstanceByKey(key);
System.out.println("id:"+processInstance.getId()+",activityId:"+processInstance.getActivityId());
}
启动成功后:
act_ru_execution
表: 记录当前活动(只要流程到达某一个节点,即具体活动,都会在这张表中添加数据)act_ru_task
表: 记录人工参与的任务信息 (主要记录:任务办理者assignee,任务分配时间create_Time)
查看流程状态
查看流程状态,使用ProcessInstanceQuery
(通过runtimeService.createProcessInstanceQuery()
获取)
@Test
public void queryProcessState()
{
String pid="1001";
ProcessInstance processInstance=this.runtimeService.createProcessInstanceQuery()
.processInstanceId(pid)
.singleResult();
if(processInstance!=null)
System.out.println("id:"+processInstance.getId()+",activityId:"+processInstance.getActivityId());
}
Task(任务)
查看任务
查看任务,使用TaskQuery
(通过taskService.createTaskQuery()
获取),主要查询表act_ru_task
/**
* 查看任务——公有任务candidate (candicateUser,candicateGroup -- act_ru_identitylink)
* taskService.createTaskQuery().taskCandidateXxx
* */
@Test
public void queryCandidateTask()
{
String candidateGroup="ROLE_User";
List<Task> tasks=this.taskService.createTaskQuery().taskCandidateGroup(candidateGroup).list();
System.out.println("-----------["+candidateGroup+"]可认领任务列表");
for(Task task:tasks)
System.out.println("id:"+task.getId()+",name:"+task.getName()+",assignee:"+task.getAssignee()+",createTime:"+task.getCreateTime());
String candidateUser="Lucy";
tasks=this.taskService.createTaskQuery().taskCandidateUser(candidateUser).list();
System.out.println("-----------["+candidateUser+"]可认领任务列表");
for(Task task:tasks)
System.out.println("id:"+task.getId()+",name:"+task.getName()+",assignee:"+task.getAssignee()+",createTime:"+task.getCreateTime());
}
/**
* 查看任务——私有任务assignee
* taskService.createTaskQuery().taskAssignee
* */
@Test
public void queryAssigneeTask()
{
String assignee="CEO Tom";
List<Task> tasks=this.taskService.createTaskQuery()
.taskAssignee(assignee)
.orderByTaskCreateTime().desc()
.list();
System.out.println("-----------["+assignee+"]私有任务列表");
for(Task task:tasks)
System.out.println("id:"+task.getId()+",name:"+task.getName()+",assignee:"+task.getAssignee()+",createTime:"+task.getCreateTime());
}
办理任务
公有任务需被认领(使用taskService.claim
)后才可执行
@Test
public void claimTask(){
String taskId="1202";
String userId="Lucy";
this.taskService.claim(taskId, userId);
}
办理任务(使用taskService.complete(taskId)
)
@Test
public void doTask()
{
String taskId="1302";
this.taskService.complete(taskId);
}
完成task后会:
- 会在
act_hi_*
表中添加记录:act_hi_actinst
,act_hi_taskinst
,act_hi_proinst
- 删除
act_ru_*
表中的相关记录::act_ru_execution
,act_ru_task
监听器
TaskListener
:任务监听器,监听事件:assignment
-- 给指定人分配task时触发- 在bpmn文件中指定;
- 认领任务后;
- create时setAssignee后;
- 注意:直接
task.setAssignee(...)
并不会触发
create
-- 启动task时触发complete
-- 完成task时触发delete
-- 结束后删除task时触发
ExecutionListener
:执行节点监听器,一般监听事件:start
end
- 使用:
- 使用expression设置
- 使用delegateExpression设置
- 使用class
- 。。。
使用举例1(TaskListener,delegateExpression): 1) xxx.bpmn.xml:
<userTask id="usertask1" name="User Task1">
<extensionElements>
<activiti:taskListener event="create" delegateExpression="${springTaskListenerBean}"></activiti:taskListener>
</extensionElements>
</userTask>
2) SpringTaskListenerBean.java:
@Component
public class SpringTaskListenerBean implements TaskListener {
@Override
public void notify(DelegateTask delegateTask) {
delegateTask.setVariable("status", delegateTask.getEventName());
System.out.println(delegateTask.getEventName()+" : "+delegateTask.getName()
+" ( status: "+delegateTask.getVariable("status")+")");
}
}
使用举例2(TaskListener,expression): 1) xxx.bpmn.xml
<userTask id="reqApprove" name="TPM Manager 审批" activiti:assignee="${approver}">
<extensionElements>
<activiti:taskListener event="assignment" expression="${processService.doProcApproverAssignment(task)}"></activiti:taskListener>
</extensionElements>
</userTask>
2) ProcessService.java
@Service("processService")
public class ProcessService{
public void doProcApproverAssignment(DelegateTask delegateTask){
System.out.println("Do Proc Approver Assignement...");
String reqId=(String)delegateTask.getVariable("reqId");
System.out.println(delegateTask.getAssignee());
...
}
}
使用举例3(executionListener,delegateExpression):
<startEvent id="basicSingleStart" name="Start" activiti:initiator="requesterUser">
<extensionElements>
<activiti:executionListener event="end" delegateExpression="${procStartListener}"></activiti:executionListener>
</extensionElements>
</startEvent>
public class ProcStartListener implements TaskListener
{
@Override
public void notify(DelegateTask delegateTask)
{
System.out.println("Process Start:"+delegateTask.getName());
String reqId=(String)delegateTask.getVariable(ProcessVariable.reqId);
if(reqId!=null)
...
}
}
使用举例3(executionListener,expression): 1) xxx.bpmn.xml
<startEvent id="basicSingleStart" name="Start">
<extensionElements>
<activiti:executionListener event="end" expression="${processService.setProcBusinessKey(execution)}"></activiti:executionListener>
</extensionElements>
</startEvent>
2) ProcessService.java
@Service("processService")
public class ProcessService{
public void setProcBusinessKey(DelegateExecution execution){
System.out.println("Set Proc BusinessKey...");
String key=execution.getProcessBusinessKey();
if(key==null){
key=(String)execution.getVariable(ProcessVariable.reqId);
System.out.println("Set Basic Process BusinessKey:"+key);
if(execution instanceof ExecutionEntity)
((ExecutionEntity)execution).setBusinessKey(key);
else if(execution instanceof ExecutionImpl)
((ExecutionImpl)execution).updateProcessBusinessKey(key);
}
}
}
PS:
- execution:
DelegateExecution
提供外出执行的额外信息。 - task:
DelegateTask
提供当前任务的额外信息。注意,只对任务监听器的表达式有效。
系统任务
ServcieTask 系统任务,自动执行
1) xxx.bpmn.xml:
<serviceTask id="servicetask" name="service Task" activiti:delegateExpression="myJavaDelegate"></serviceTask>
2) MyJavaDelegate.java
public class MyJavaDelegate implements JavaDelegate
{
@Override
public void execute(DelegateExecution execution) throws Exception
{
System.out.println("Execution Service Task...");
execution.setVariable("serviceVar", "Hello");
}
}
Gateway 网关
排他网关
排他网关(ExclusiveGateway):
- 在flow上(线上)设置条件condition,跟jbpm不同
- 根据condition结果,执行其中一条分支
<sequenceFlow id="flow2" name="input == 1" sourceRef="exclusivegateway" targetRef="usertask1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${input == 1}]]></conditionExpression>
</sequenceFlow>
public void testExclusiveGateway(){
Map<String,Object> variables=new HashMap<String,Object>();
variables.put("input", 2);
runtimeService.startProcessInstanceByKey("exclusiveGatewayTest",variables);
Task task=taskService.createTaskQuery().singleResult();
assertTrue("User Task2".equals(task.getName()));
}
并行网关
并行网关(ParallelGateway):
- 无需设置condition
- 等分支都执行完毕再继续向下执行
例如:
需UserTask1
和UserTask2
都完成后UserTask3
才可被执行
包含网关
包含网关(InclusiveGateway)
- 排他网关ExclusiveGateway和并行网关PallelGateway的结合
- 符合Condition的分支会并行执行
例如:
若input=2
,则UserTask1不会被执行,且只有UserTask2
和UserTask3
都被执行完成后,流程才会再往下
与Spring整合
<aop:aspectj-autoproxy proxy-target-class="true"/>
<context:annotation-config/>
<context:component-scan base-package="xxxx" ></context:component-scan>
<context:property-placeholder location="classpath:jdbc-mine.properties"/>
<!-- DataSource -->
<bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="initialSize" value="1"/>
<property name="minIdle" value="2"/>
<property name="maxActive" value="200"/>
<property name="maxIdle" value="30"/>
<property name="maxWait" value="10000"/>
<property name="removeAbandoned" value="true"/>
<!-- <property name="logAbandoned" value="true"/> -->
<property name="validationQuery" value="select 1 from dual"/>
<property name="testOnBorrow" value="true"/>
</bean>
<!-- 声明式事务 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager" >
<tx:attributes>
<tx:method name="list*" propagation="REQUIRED" read-only="true"/>
<tx:method name="get*" propagation="REQUIRED" read-only="true"/>
<tx:method name="detail*" propagation="REQUIRED" read-only="true" />
<tx:method name="test*" propagation="REQUIRED" read-only="true" />
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution(* com.cj..service.*.*(..))" id="myShuttleService"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="myShuttleService" order="2"/>
</aop:config>
<!-- Activiti: processEngineConfiguration -->
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
<property name="dataSource" ref="dataSource" />
<property name="transactionManager" ref="txManager" />
<property name="databaseSchemaUpdate" value="true" />
<property name="jobExecutorActivate" value="true" />
<property name="history" value="full" />
<property name="activityFontName" value="宋体" />
<property name="labelFontName" value="宋体" />
</bean>
<!-- Activiti: processEngine -->
<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean" destroy-method="destroy">
<property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>
<!-- Activiti: Service -->
<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" />
<bean id="identityService" factory-bean="processEngine" factory-method="getIdentityService" />
<!-- 根据需要注入:-->
<!-- Activiti: javaDelegate -->
<bean id="myJavaDelegate" class="com.cj.test.javaDelegate.MyJavaDelegate"></bean>
<!-- Activiti: taskListener -->
<bean id="myTaskListener" class="com.cj.test.taskListener.MyTaskListener"></bean>
测试
PluggableActivitiTestCase
,ResourceActivitiTestCase
,SpringActivitiTestCase
extends AbstractActivitiTestCase
- 定义了processEngine和所有的API的Servcie
- 使用test开头的方法作为单元测试方法:
testXxx(...)
- 可使用
@Deployment
注解deploy测试流程
使用:
- 方式一:
extends PluggableActivitiTestCase
- 使用默认配置文件:
activiti.cfg.xml
- 使用默认配置文件:
- 方式二:
extends ResourceActivitiTestCase
- 可指定配置文件,在构造函数中配置调用:
super("xxx")
;public class GatewayTest extends ResourceActivitiTestCase{ public GatewayTest(){ super("activiti-test.cfg.xml"); } @Deployment(resources = "bpmn/gateway/ExclusiveGateway.bpmn") public void testExclusiveGateway(){ Map<String,Object> variables=new HashMap<String,Object>(); variables.put("input", 2); runtimeService.startProcessInstanceByKey("exclusiveGatewayTest",variables); Task task=taskService.createTaskQuery().singleResult(); assertTrue("User Task2".equals(task.getName())); } }
- 可指定配置文件,在构造函数中配置调用:
方式三:
extends SpringActivitiTestCase
- 整合Spring测试(ProcessEngine,Service由Spring管理)
beans-test.xml
:<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource"> <property name="driverClass" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:advice id="txAdvice" transaction-manager="txManager" > <tx:attributes> <tx:method name="list*" propagation="REQUIRED" read-only="true"/> <tx:method name="get*" propagation="REQUIRED" read-only="true"/> <tx:method name="detail*" propagation="REQUIRED" read-only="true" /> <tx:method name="test*" propagation="REQUIRED" read-only="true" /> <tx:method name="*"/> </tx:attributes> </tx:advice> <bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration"> <property name="dataSource" ref="dataSource" /> <property name="transactionManager" ref="txManager" /> <property name="databaseSchemaUpdate" value="true" /> <property name="jobExecutorActivate" value="true" /> <!-- <property name="history" value="full" /> --> <property name="activityFontName" value="微软雅黑" /> <property name="labelFontName" value="微软雅黑" /> </bean> <bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean" destroy-method="destroy"> <property name="processEngineConfiguration" ref="processEngineConfiguration" /> </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" /> <bean id="identityService" factory-bean="processEngine" factory-method="getIdentityService" />
Test Class:
@ContextConfiguration("classpath:beans-test.xml") public class TaskSpringTest extends SpringActivitiTestCase { @Deployment(resources="bpmn/task/ServiceTask-spring.bpmn") public void testServiceTask(){ ProcessInstance procInst=runtimeService.startProcessInstanceByKey("serviceTaskSpringTest"); //User Task1 Task task=taskService.createTaskQuery().singleResult(); assertTrue("User Task1".equals(task.getName())); taskService.complete(task.getId()); //Service Task -- auto execute! //User Task2 task=taskService.createTaskQuery().singleResult(); assertTrue("User Task2".equals(task.getName())); String var=(String)taskService.getVariable(task.getId(), "serviceVar"); assertTrue("Hello".equals(var)); taskService.complete(task.getId()); assertProcessEnded(procInst.getId()); } }
功能组件介绍
Service
引擎API:
RepositoryService:流程资源服务的接口,主要用于对流程定义的部署、查询和删除操作
repositoryService.createProcessDefinitionQuery()
repositoryService.getBpmnModel(procDefId)
repositoryService.createDeployment()
repositoryService.getDeploymentResourceNames(deploymentId)
repositoryService.getResourceAsStream(...)
repositoryService.getProcessDiagram(processDefinitionId)
- ...
RuntimeService:运行时服务接口,主要对流程实例,变量,活动等的操作
runtimeService.startProcessInstanceByXxx(...)
runtimeService.createExecutionQuery()
runtimeService.getActiveActivityIds(procInstId);
- ...
TaskService:任务服务接口
taskService.createTaskQuery()
taskService.claim(taskId, userId);
taskService.complete(taskId);
taskService.createAttachment(...)
taskService.addComment(...)
- ...
HistoryService:流程历史的服务接口
historyService.createHistoricVariableInstanceQuery()
historyService.createHistoricProcessInstanceQuery()
historyService.createHistoricTaskInstanceQuery()
- ...
IdentityService:用户、组管理服务接口,用于管理Group、User、Membership
identityService.setAuthenticatedUserId(userId);
identityService.createUserQuery()
identityService.createGroupQuery()
- newUser,saveUser,deleteUser,...
- newGroup,saveGroup,deleteGroup,...
- newMembership,saveMembership,deleteMemberShip,...
- ...
FormService:表单服务接口,用于访问表单数据,渲染表单等操作
formService.getXxxFormData(...)
formService.getRenderedXxxFormData(...)
formService.submitXxxFormData(...)
- ...
ManagementService:提供流程管理和控制操作的接口服务
String jobId=managementService.createJobQuery() .processInstanceId("2401") .singleResult() .getId(); managementService.executeJob(jobId);
Tables
Activiti 数据库中表的命名都是以 ACT_
开头
ACT_RE_*
: Repository。存储静态信息,如:流程定义、流程的资源(图片、规则)。ACT_RU_*
: Runtime。运行时的表- Activiti 只存储流程实例执行期间的运行时数据
- 如:流程变量、用户任务、变量、作业等
- 当流程实例结束时,将删除这些记录
ACT_ID_*
:Identity。记录标识信息,如:用户、用户组等ACT_HI_*
:History。记录历史的相关数据,如:结束的流程实例、变量、任务等ACT_GE_*
:General。记录通用数据
Var
- 运行时变量,存放在
act_ru_variable
表中- 全局变量
- 本地变量
- 历史变量,涉及表
act_hi_detail
,act_hi_varinst
- 有4个历史级别:
- none: 不保存任何历史记录,可以提高系统性能;
- activity:保存所有的流程实例、任务、活动信息;
- audit:也是Activiti的默认级别,保存所有的流程实例、任务、活动、表单属性;
- full: 最完整的历史记录,除了包含audit级别的信息之外还能保存详细,例如:流程变量
- 可配置:
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration"> <property name="history" value="full"> </property></bean>
- 查询变量:
List<historicvariableinstance> list = historyService.createHistoricVariableInstanceQuery() .processInstanceId(processInstanceId) .list(); for (HistoricVariableInstance variable : list) { System.out.println("variable: " + variable.getVariableName() + " = " + variable.getValue()); }
- 只查询表单字段:
List<historicdetail> formProperties = historyService.createHistoricDetailQuery() .processInstanceId(processInstance.getId()) .formProperties() .list(); for (HistoricDetail historicDetail : formProperties) { HistoricFormProperty field = (HistoricFormProperty) historicDetail; System.out.println("field id: " + field.getPropertyId() + ", value: " + field.getPropertyValue()); }
- 有4个历史级别:
User
统一身份管理:
- Activiti用户管理系统
- 使用
identityService
管理User,Group,Membership - 关联的表:
act_id_user
,act_id_info
,act_id_group
,act_id_membership
- 在流程定义中可直接使用定义的User,Group,例如
activiti:candidateGroups="admin,manager"
activiti:candidateUsers="Tom,Jerry"
- 使用
- 业务系统的用户管理系统,比如
- 使用LDAP
- 使用部门,角色,权限等更为复杂的组织系统
- 结合方式:
- 业务系统操作时同步管理Activiti用户数据(调用identityService)
- 自定义SessionFactory,非侵入式替换接口实现
- 用视图覆盖同名的
ACT_ID_*
系列表(需在引擎配置中设置属性dbIdentityUsed
为false)
Form
Activiti管理表单
- 配置在流程定义中(可加在StartEvent,Task节点上)
- 表单支持变量自动替换(UEL语法)
- 表单内容以key-value形式保存在表
act_hi_detail
中 使用方式一:在流程定义文件中用
activiti:formProperty
属性定义- 表单没有布局,所有的表单元素会顺序逐行输出在页面
<startevent id="startevent1" name="Start"> <extensionelements> <activiti:formproperty id="name" name="Name" type="string"></activiti:formproperty> </extensionelements> </startevent> <usertask id="usertask1" name="First Step"> <extensionelements> <activiti:formproperty id="setInFirstStep" name="SetInFirstStep" type="date"></activiti:formproperty> <activiti:formproperty id="remark" name="Remark" type="string"></activiti:formproperty> </extensionelements> </usertask>
- 通过
formService.getXxxForm
获取表单:formService.getStartFormData(processDefinitionId)
,返回StartFormData
formService.getTaskFormData(taskId)
,返回TaskFormData
- 提交表单:
formService.submitXxxFormData(...)
- 表单没有布局,所有的表单元素会顺序逐行输出在页面
使用方式二:在流程定义文件中用
activiti:formkey
属性引用- 流程运行时从部署的表单文件中读取,原样输出显示
- 注意:表单内容写好保存到
.form
文件,需同流程定义文件一同部署<process id="FormKey" name="FormKey"> <startevent id="startevent1" name="Start" activiti:formkey="form/start.form"></startevent> … </process>
- 通过
formService.getRenderedXxxForm
获取表单:formService.getRenderedStartForm(processDefinitionId)
formService.getRenderedTaskForm(taskId)
- 注意: return Object (返回内容是经FormEngine处理过的)
- 默认使用的表单引擎为JuelFormEngine,可自定义进行扩展
- 提交表单:
formService.submitXxxFormData(...)
非Activiti管理表单
- 表单呈现自定义
- 表单内容置于自定义的Table中,通过
businessKey
传入与流程关联
常见应用
Attachment & Comment
都是存放在历史记录中的,即act_hi_attachment
,act_hi_comment
Attachment:
- 涉及到的table:
act_hi_attachment
,act_ge_bytearray
,act_hi_comment
- 添加attachment,两种方式:
- 方式一:使用 inputStream
- inputStream 的实际内容存放在
act_ge_bytearray
中,通过contentId关联 taskService.createAttachment(attachmentType, taskId, processInstanceId, attachmentName, attachmentDescription, content)
- inputStream 的实际内容存放在
- 方式二:使用 url
taskService.createAttachment(attachmentType, taskId, processInstanceId, attachmentName, attachmentDescription, url)
- 方式一:使用 inputStream
- 会在act_hi_comment表中插入数据 (type:event,action:AddAttachment,message:attachmentName,full_msg:null)
- 获取Attachment(search
act_hi_attachment
):- 方式一:
List<Attachment> taskService.getTaskAttachments(taskId);
- 方式二:
List<Attachment> taskService.getProcessInstanceAttachments(procInstId);
- 方式一:
Comment:
- 涉及到的table:act_hi_comment
- 添加comment:
taskService.addComment(taskId, processInstanceId, message)
taskService.addComment(taskId, processInstanceId, type, message)
- 会在act_hi_comment表中插入数据 (type:comment,action:AddComment,full_msg:msg)
- 获取Comment(search act_hi_comment):
- 方式一:
List<Comment> taskService.getTaskComments(taskId);
- 方式二:
List<Comment> taskService.getProcessInstanceComments(procInstId);
(会查出因为createAttachment而添加的comment记录)
- 方式一:
多实例
<userTask id="miTasks" name="My Task" activiti:assignee="${user}">
<multiInstanceLoopCharacteristics isSequential="false"
activiti:collection="${myService.resolveUsersForTask()}" activiti:elementVariable="user" >
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6 }</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
isSequential="true"
顺序执行(多级串行)isSequential="false"
并行执行(并行会签)- 每个上级流程为每个实例创建分支时都要提供如下变量:
nrOfInstances
:实例总数nrOfActiveInstances
:当前活动的实例数量 (对于顺序执行的多实例,值一直为1)nrOfCompletedInstances
:已经完成实例的数目- 可以通过
execution.getVariable(x)
方法获得这些变量
- 每个创建的分支都会有分支级别的本地变量(临时)
- 其他实例不可见, 不会保存到流程实例级别
- 比如:
loopCounter
(表示特定实例的在循环的索引值)
CallActivity & SubProcess
CallActivity
调用外部定义的流程
- New ProcessInstance
- Business Key is null
- for transfer: set inputParamater & outParamater
- search by some unique var
SubProcess
子流程 (内嵌在当前流程定义中)
- has Main Process information
(包含SubProcess和CallActivity) (被Call的外部流程定义)
Transaction 事务
Case1: serviceTask[处理成功申请] 同步,即设置
activiti:async="false"
(默认)<serviceTask id="service1" name="处理成功申请" activiti:class="my.custom.Delegate" activiti:async="false"
Case2: serviceTask[处理成功申请] 异步,即设置
activiti:async="true"
,打开jobExecutorActivate
(processEngineConfiguration中)<serviceTask id="service1" name="处理成功申请" activiti:class="my.custom.Delegate" activiti:async="true"
(后续执行失败后回滚到此异步task)
异步执行说明:
- 异步只是指:执行方式异步
- 即启动新的线程(相对于主线程),但在流程控制上,若流程未完结,不会提前执行下一个节点
- 异步执行是一个新的执行单元(Transaction原子)
- 所以设置异步执行,流程不会提前结束,会继续执行下一个节点直到流程结束
异步节点:TimerCatchEvent
- Timer无异步设置,本身就是异步执行的,执行失败(Timer或后面同步节点异常)会停留在TimerCatchEvent节点
- 在流程中设置Timer,会等到Timer执行完成后流程才能结束
- Timer Event: start (流程执行到此Timer节点时触发),end(此异步Timer完成到达设置的执行时间时触发)
- trigger startEvent
- create async thread (Sleep)
- at setup time(Weakup)
- trigger endEvent
- next Activity
PS:
一般情况下,执行一个task为一个原子,执行taskService.complete(taskId)
时,若其中发生异常,将回滚 (这个task中发生的所有操作都会回滚,不用担心业务数据和流程数据不同步的问题)
流程跟踪
- 方式一:Activiti流程引擎根据流程定义文件(.xml)生成图片
- 方便,不灵活,易错位
- 可直接生成Highlight Activity的图片
- 弊端:坐标错位不一致,中文乱码,Flow上文字不显示
- 方式二:直接获取部署时上传的对应流程图
- 灵活,复杂
- 读取图片后通过前端Javascript和Css配合跟踪(Highlight Activity)
- 可利用静态服务提升读取Activiti流程图的性能
- 弊端:同方法一
- 方式三:使用Diagram Viewer流程图跟踪组件
- 灵活,方便
- 根据读取的数据,在前端绘制流程图
使用流程定义文件(.xml)
- 生成流程图
BpmnModel bpmnModel=repositoryService.getBpmnModel(procInst.getProcessDefinitionId()); InputStream in=ProcessDiagramGenerator.generatePngDiagram(bpmnModel);
- 生成活动节点标记的流程图(activeActivity)
List<String> activityIds=runtimeService.getActiveActivityIds(procInst.getId()); InputStream in=ProcessDiagramGenerator.generateDiagram(bpmnModel, "png", activityIds);
- 生成带活动踪迹的流程图(activeActivity+historyFlow)
List<String> highLightedFlows = getHighLightedFlows(processDefinition , processInstanceId); //具体实现参考ProcessInstanceHighlightsResource.java InputStream in=ProcessDiagramGenerator.generateDiagram(bpmnModel, "png", activityIds,highLightedFlows);
使用部署上传的流程图
这种方式在优势灵活性很大,可以显示你要展示的所有信息; 因为完全就是一堆信息点汇合起来的跟踪功能,需要后端提供数据给前端分析; 例如:有哪些Activity及其坐标、高度、宽度、是否当前节点等信息
后端:
- 获取流程图
- 方法一:
InputStream in=repositoryService.getProcessDiagram(processDefinitionId);
- 方法二:
String resourceName = procDef.getDiagramResourceName(); InputStream in = repositoryService.getResourceAsStream(processDefinitionId, resourceName);
- 方法一:
- 获取流程定义的所有节点信息(All Activities Info)
- Process内部节点(即活动)等详细信息:ActivityImpl
- 记录在bmpn文件中(运行时加载解析bmpn文件到内存中)
- 通过RepositoryService的
getProcessDefinition(id)
方法从内存中获取Activity定义信息ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService) .getDeployedProcessDefinition(procInst.getProcessDefinitionId()); List<ActivityImpl> activitiList = processDefinition.getActivities();//获得当前流程定义的所有节点
获取当前活动节点信息(Active Activity Info)
注意:示例中是返回所有节点信息(包括标注是否为当前活动节点)
ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService ) .getDeployedProcessDefinition(procInst.getProcessDefinitionId()); List<String> activityIds=runtimeService.getActiveActivityIds(procInst.getId()); List<Map<String, Object>> activityInfos= new ArrayList<Map<String, Object>>(); for(String id:activityIds){ ActivityImpl activity=processDefinition.findActivity(id); Map<String,Object> activityImageInfo=new HaspMap<String,Object>(); activityImageInfo.put( "x", activity.getX()); activityImageInfo.put( "y", activity.getY()); activityImageInfo.put( "width", activity.getWidth()); activityImageInfo.put( "height", activity.getHeight()); activityInfos.add(activityImageInfo); }
前端:
- 用js和css配合动态标记当前节点(可参考kft-activiti-demo项目的workflow.js代码)
使用Diagram Viewer组件
- 这是官方在5.12版本中提供的一个 Javascript跟踪工具,使用方便,
- 简单配置就可以使用,兼容各种浏览器,并且可以自己扩展
- 以Raphaël为基础库,用REST方式获取JSON数据,生成流程图并把流程的处理过程用不同的颜色加以标注
后端:
- 添加依赖包
<dependency> <groupId>org.activiti</groupId> <artifactId>activiti-diagram-rest</artifactId> <version>${activiti.version}</version> </dependency>
配置一个REST拦截器
- 可使用自定义也可使用activiti自带的DiagramRestApplication
web.xml
<!-- Activiti: Restlet adapter - used to expose modeler functionality through REST --> <servlet> <servlet-name>MyActivitiRestletServlet</servlet-name> <servlet-class>org.restlet.ext.servlet.ServerServlet</servlet-class> <init-param> <!-- Application class name --> <param-name>org.restlet.application</param-name> <!-- <param-value>org.activiti.rest.diagram.application.DiagramRestApplication</param-value> --> <param-value>com.cj.xxx.MyActivitiRestApplication</param-value> </init-param> </servlet> <!-- Catch all service requests --> <servlet-mapping> <servlet-name>MyActivitiRestletServlet</servlet-name> <url-pattern>/service/*</url-pattern> </servlet-mapping>
MyActivitiRestApplication.java
public class MyActivitiRestApplication extends ActivitiRestApplication{ public MyActivitiRestApplication(){ super(); } /** * Creates a root Restlet that will receive all incoming calls. */ @Override public synchronized Restlet createInboundRoot(){ Router router = new Router(getContext()); router.attachDefault(DefaultResource.class); //DiagramServicesInit.attachResources(router); router.attach("/process-instance/{processInstanceId}/highlights", ProcessInstanceHighlightsResource.class); router.attach("/process-instance/{processInstanceId}/diagram-layout", ProcessDefinitionDiagramLayoutResource.class); router.attach("/process-definition/{processDefinitionId}/diagram-layout", MyProcessDefinitionDiagramLayoutResource.class); JsonpFilter jsonpFilter = new JsonpFilter(getContext()); jsonpFilter.setNext(router); return jsonpFilter; } }
前端使用:
复制Diagram Viewer组件到项目
- 从官网包
activiti-explorer.war
中找到diagram-viewer
拷贝到项目 - 根据需要编写修改js文件,在页面中应用
测试:
获取完整效果,eg:
- trigger:
baseUrl+/resources/diagram-viewer/index.html?processDefinitionId=leave:1:4&processInstanceId=5
- 响应的HTML上面绘制了对应的流程追踪图
- trigger:
获取流程定义所有元素信息,eg:
- trigger:
baseUrl + "/service/process-definition/{processDefinitionId}/diagram-layout?callback=?"
- or trigger:
baseUrl + "/service/process-definition/{processDefinitionKey}/diagram-layout?callback=?"
- result:
{"processDefinition":{"id":"leave:1:20","name":"请假流程","key":"leave","version":1,"deploymentId":"1","isGraphicNotationDefined":true}, "activities":[ {"activityId":"deptLeaderAudit","properties":{"name":"部门领导审批","type":"userTask"},"x":90,"y":80,"width":105,"height":55}, {"activityId":"exclusivegateway5","properties":{"name":"Exclusive Gateway","type":"exclusiveGateway"},"x":250,"y":87,"width":40,"height":40}, {"activityId":"modifyApply","properties":{"name":"调整申请","type":"userTask"},"x":218,"y":190,"width":105,"height":55}, {"activityId":"hrAudit","properties":{"name":"人事审批","type":"userTask"},"x":358,"y":80,"width":105,"height":55}, {"activityId":"exclusivegateway6","properties":{"name":"Exclusive Gateway","type":"exclusiveGateway"},"x":495,"y":87,"width":40,"height":40}, {"activityId":"reportBack","properties":{"name":"销假","type":"userTask"},"x":590,"y":80,"width":105,"height":55}, {"activityId":"exclusivegateway7","properties":{"name":"Exclusive Gateway","type":"exclusiveGateway"},"x":250,"y":280,"width":40,"height":40}, {"activityId":"startevent1","properties":{"name":"Start","type":"startEvent"},"x":10,"y":90,"width":35,"height":35}, {"activityId":"endevent1","properties":{"name":"End","type":"endEvent"},"x":625,"y":283,"width":35,"height":35} ], "sequenceFlows":[ {"id":"flow3","name":"","flow":"(deptLeaderAudit)--flow3-->(exclusivegateway5)","xPointArray":[195,250],"yPointArray":[107,107]}, {"id":"flow4","name":"不同意","flow":"(exclusivegateway5)--flow4-->(modifyApply)","xPointArray":[270,270],"yPointArray":[127,190]}, {"id":"flow5","name":"同意","flow":"(exclusivegateway5)--flow5-->(hrAudit)","xPointArray":[290,358],"yPointArray":[107,107]}, {"id":"flow11","name":"","flow":"(modifyApply)--flow11-->(exclusivegateway7)","xPointArray":[270,270],"yPointArray":[245,280]}, {"id":"flow6","name":"","flow":"(hrAudit)--flow6-->(exclusivegateway6)","xPointArray":[463,495],"yPointArray":[107,107]}, {"id":"flow7","name":"同意","flow":"(exclusivegateway6)--flow7-->(reportBack)","xPointArray":[535,590],"yPointArray":[107,107]}, {"id":"flow9","name":"不同意","flow":"(exclusivegateway6)--flow9-->(modifyApply)","xPointArray":[515,514,323],"yPointArray":[127,217,217]}, {"id":"flow8","name":"","flow":"(reportBack)--flow8-->(endevent1)","xPointArray":[642,642],"yPointArray":[135,283]}, {"id":"flow10","name":"重新申请","flow":"(exclusivegateway7)--flow10-->(deptLeaderAudit)","xPointArray":[250,142,142],"yPointArray":[300,299,135]}, {"id":"flow12","name":"结束流程","flow":"(exclusivegateway7)--flow12-->(endevent1)","xPointArray":[290,625],"yPointArray":[300,300]}, {"id":"flow2","name":"","flow":"(startevent1)--flow2-->(deptLeaderAudit)","xPointArray":[45,90],"yPointArray":[107,107]} ] };
- trigger:
获取当前活动节点和线信息进行高亮处理,eg:
- trigger:
baseUrl + "/service/process-instance/{processInstanceId}/highlights?callback=?"
- result:
{"processInstanceId":"108","processDefinitionId":"leave:1:20","activities":["hrAudit"],"flows":["flow2","flow3","flow5"]});
- trigger:
Reference
- Activiti官网
- Activiti 中文论坛
- Activiti 用户手册
- 推荐博文:咖啡兔 | BPMN2新规范与Activiti5
- Activiti Online Projects: lemon| 宏天