一、什么是BPMN
首先BPMN规范是由标准组织BPMI发布的.BPMN 1.0规范发布于2004年5月。此规范展示了BPMI组织两年多的努力成果。BPMN的主要目标就是要提供被所有业务用户理解的一套 标记语言,包括业务分析者、软件开发者以及业务管理者与监察者。BPMN还将支持生成可执行的BPEL4WS语言。所以,BPMN在业务流程设计与流程实现之间搭建了一条标准化的桥梁。
BPMN定义了 业务流程图,其基于流程图技术,同时为创建业务流程操作的图形化模型进行了裁减。业务流程的模型就是图形化对象的网图,包括活动(也可以说工作)和定义操作顺序的流控制。
二、定义一个流程
注:这个介绍是写在假设你正在用Eclipse IDE创建并且编辑文件。但是你也能用其他的工具去创建包含BPMN2.0的XML文件。
创建一个新的XML文件,并且命名。确保文件是以.bpmn2.0或者.bpmn结尾,否则部署的时候,引擎不会识别。
bpmn2.0架构的根节点是definitions元素。在这个元素下,可以定义多个流程定义(虽然我们建议在每个文件中只定义一个流程,因为这样在部署流程的时候,简化了维护)。一个空的流程定义看起来像下面列出来的一样。注:最简化的definitions元素只需要生命xmls和targetNamespace。targetNamespace可以是任何东西,它对于流程定义分类是有用的。
..
你也能添加在线的架构路径,作为在Eclipse里XML分类配置的替代。
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://www.omg.org/spec/BPMN/2.0/20100501/BPMN20.xsd
process元素有两个属性:
- id:这个属性是必须的并且存储Activiti ProcessDefinition对象的key属性。通过RuntimeService的
startProcessInstanceByKey方法,
id能被用在开始流程定义的一个新的流程实例。这个方法会得到最新的流程定义的发布版本号。ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myProcess");
注:这个方法和
startProcessInstanceById
方法不一样。这个方法中的id是被Activiti引擎在部署的时候生成的,并且能通过调用
processDefinition.getId()方法被检索。生成的id格式是:'key:version',长度被约束为64个字符。如果你得到
异常信息ActivitiException,以生成的id太长开头,那么你要减少流程的key值得文本长度。
- name:这个属性是可选的,存储了流程定义的名字属性。引擎自己不会用这个属性,因此它能用来在用户界面展现一个更人性化的名称。换句话说,这只是一个用来展示的字段,没有什么用处。
在这一节,我们会通过一个非常简单的业务流程,介绍一些基本的Activiti概念和Acitiviti的API。
1、前期准备
这一节假设你已经安装了Activiti的demo并且运行成功。
2、目标
这一节的目标是学习关于Activiti和一些基本的BPMN2.0的概念。最终的结果会是一个简单的Java SE程序,这个程序部署了一个流程定义,并且通过流程引擎的API和流程交互。我们也能触摸到Activiti周围的一些工具。当然,当你在你的业务流程周围创建的web应用程序,在这一节学到的东西能用到。
3、使用情景
使用情景是简单的:我们有一个公司,让我们称之为BPMCorp。在BPMCorp里,金融报表每个月都需要书面给公司的股东。这是会计部门的职责。当报表完成的时候,在发送给公司股东之前,上级管理成员中的一个需要审批。
4、流程图
上面描述的业务流程,能用Activiti Designer来实现图形可视化。然而,这一节我们要自己输入XML。我们流程的图形化的BPMN2.0的符号,看起来像这样:
5、XML表示
业务流程的XML版本看起来像下面展示的一样。我们流程的主要元素是很容易识别的:
- start event告诉我们对于一个流程,输入点是什么。
- User Tasks描述的是我们的流程人员认为的职责。注:第一个任务被分配给 accountancy 组,第二个任务被分配给management组 。后面我们会讲更多的关于用户和组怎么被分配user tasks。
- 当到达end event,流程结束。
- 通过顺序流,每一个元素都被连接在一起。这些顺序流有来源和目的,定义了顺序流的方向。
Write monthly financial report for publication to shareholders. accountancy Verify monthly financial report composed by the accountancy department. This financial report is going to be sent to all the company shareholders. management
6、开始一个流程实例
我们现在已经创建了我们业务流程的流程定义。从这样的一个流程定义中,我们能创建流程实例。在这种情况下,一个流程实例会在特定月份,匹配一份财务报告的创建和检验。所有的流程实例会在同样的流程定义分享。
为了能从给定的流程定义中创建流程实例,我们必须首先部署这个流程定义。发布一个流程定义意味着两件事:
- 流程定义会被保存进为Activiti引擎中配置的数据库中。 因此通过部署我们的业务流程,我们确保引擎会在重启之后重新找到我们的流程定义。
- BPMN2.0流程文件会被解析到一个内存对象模型中,并且能通过Activiti的API来操纵。
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("FinancialReportProcess.bpmn20.xml")
.deploy();
现在我们能用我们定义的id来启动一个新的流程实例。注:在流程术语中,这个id被称为key。
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("financialReport");
调用
startProcessInstanceByKey
方法, 任务被创建之后,用户任务会处于等待状态。在这种情况下,任务被分配给一个组,这也意味着组中的每一个成员都是执行任务的候选人。
现在我们可以把他们糅合在一起,并且创建一个Java程序。创建一个Eclipse项目,并且添加Activiti的jar文件和其依赖jar到classpath中。在我们能调用Activiti的services之前,我们必须首先构造一个ProcessEngine,可以让我们访问到services。这里我们用‘独立’的配置来构造一个ProcessEngine。
public static void main(String[] args) {
// Create Activiti process engine
ProcessEngine processEngine = ProcessEngineConfiguration
.createStandaloneProcessEngineConfiguration()
.buildProcessEngine();
// Get Activiti services
RepositoryService repositoryService = processEngine.getRepositoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();
// Deploy the process definition
repositoryService.createDeployment()
.addClasspathResource("FinancialReportProcess.bpmn20.xml")
.deploy();
// Start a process instance
runtimeService.startProcessInstanceByKey("financialReport");
}
7、任务列表
我们现在能通过TaskService取回任务。
List tasks = taskService.createTaskQuery().taskCandidateUser("kermit").list();
accountancy
我们也能用gourp的名字查询获取到同样的结果:
TaskService taskService = processEngine.getTaskService();
List tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list();
8、签收任务
现在一个会计(accountant)需要签收任务。通过签收任务,那么具体的的用户会成为任务的代理人,并且这个任务会在会计(accountant)组其他成员的任务列表中消失。可以像下面一样通过编程实现:
taskService.claim(task.getId(), "fozzie");
现在这个任务就会在签收人的个人任务列表中。
List tasks = taskService.createTaskQuery().taskAssignee("fozzie").list();
在Activiti Explorer UI中,点击claim按钮会调用同样的操作。这个任务现在被移到登录用户的个人任务列表中。你也能看到任务的代理人变成了当前登录的用户。
9、完成任务
会计现在能开始金融报表的工作了。一旦报表完成,他就可以完成这个任务,这意味着这个任务的所有工作被完成。
taskService.complete(task.getId());
对于Activiti 引擎来说,这是流程实例必须继续执行的一个外部信号。这个任务自己会从运行中数据中移除。流程会移动到执行第二个任务。像第一个任务中描述的同样的机制会分配第二个任务,只是有一个很小的区别,就是第二个任务会被分配到管理(management)组。
在demo中,完成任务是通过点击完成(complete)按钮。因为Fozzie用户不是一个会计,我们需要退出,用kermit(管理人员)登录。第二个任务现在是在未分配任务列表中。
10、结束流程
验收工作可以像前面一样,用同样的方式去检索和签收。完成第二个任务后,流程会移动到结束事件上,也就是完成流程实例。和这个流程实例所有相关的运行执行数据都会从数据库中移除。
你可以登录到Activiti Explorer中查看ACT_RU_EXECUTION表。
可以用historyService验证流程是否结束。
HistoryService historyService = processEngine.getHistoryService();
HistoricProcessInstance historicProcessInstance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();
System.out.println("Process instance end time: " + historicProcessInstance.getEndTime());
11、代码概述
结合之前的所有代码片段,会是这样:
public class TenMinuteTutorial {
public static void main(String[] args) {
// Create Activiti process engine
ProcessEngine processEngine = ProcessEngineConfiguration
.createStandaloneProcessEngineConfiguration()
.buildProcessEngine();
// Get Activiti services
RepositoryService repositoryService = processEngine.getRepositoryService();
RuntimeService runtimeService = processEngine.getRuntimeService();
// Deploy the process definition
repositoryService.createDeployment()
.addClasspathResource("FinancialReportProcess.bpmn20.xml")
.deploy();
// Start a process instance
String procId = runtimeService.startProcessInstanceByKey("financialReport").getId();
// Get the first task
TaskService taskService = processEngine.getTaskService();
List tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list();
for (Task task : tasks) {
System.out.println("Following task is available for accountancy group: " + task.getName());
// claim it
taskService.claim(task.getId(), "fozzie");
}
// Verify Fozzie can now retrieve the task
tasks = taskService.createTaskQuery().taskAssignee("fozzie").list();
for (Task task : tasks) {
System.out.println("Task for fozzie: " + task.getName());
// Complete the task
taskService.complete(task.getId());
}
System.out.println("Number of tasks for fozzie: "
+ taskService.createTaskQuery().taskAssignee("fozzie").count());
// Retrieve and claim the second task
tasks = taskService.createTaskQuery().taskCandidateGroup("management").list();
for (Task task : tasks) {
System.out.println("Following task is available for accountancy group: " + task.getName());
taskService.claim(task.getId(), "kermit");
}
// Completing the second task ends the process
for (Task task : tasks) {
taskService.complete(task.getId());
}
// verify that the process is actually finished
HistoryService historyService = processEngine.getHistoryService();
HistoricProcessInstance historicProcessInstance =
historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult();
System.out.println("Process instance end time: " + historicProcessInstance.getEndTime());
}
}