7.3。入门:10分钟的教程
在本节中,我们将介绍一个(非常简单的)业务流程,我们将使用它来介绍一些基本的Activiti概念和Activiti API。
7.3.1。先决条件
本教程假设您正在运行Activiti演示设置,并且您正在使用独立的H2服务器。编辑db.properties
并设置jdbc.url=jdbc:h2:tcp://localhost/activiti
,然后根据H2的文档运行独立服务器。
7.3.2。目标
本教程的目标是了解Activiti和一些基本的BPMN 2.0概念。最终结果将是一个简单的Java SE程序,它部署流程定义,并通过Activiti引擎API与此流程交互。我们还将触及Activiti周围的一些工具。当然,在围绕业务流程构建自己的Web应用程序时,也可以使用本教程中学到的内容。
7.3.3。用例
用例很简单:我们有一家公司,我们称之为BPMCorp。在BPMCorp,每个月都需要为公司股东撰写财务报告。这是会计部门的责任。报告完成后,高层管理人员中的一位成员需要在将文件发送给所有股东之前批准该文件。
7.3.4。流程图
可以使用Activiti Designer以图形方式显示上述业务流程 。但是,对于本教程,我们将自己键入XML,因为我们在这一点上以这种方式学习最多。我们流程的图形化BPMN 2.0表示法如下所示:
我们看到的是无启动事件(左侧的圆圈),然后是两个用户任务:“编写月度财务报告”和 “验证月度财务报告”,以无终止事件结束(右侧带有粗边框的圆圈) 。
7.3.5。XML表示
此业务流程的XML版本(FinancialReportProcess.bpmn20.xml)如下所示。很容易识别我们流程的主要元素(点击链接转到该BPMN 2.0构造的详细部分):
-
在(无)启动事件告诉我们什么入口点的过程。
-
在用户任务声明是我们的过程的人工任务的表示。请注意,第一个任务分配给会计组,而第二个任务分配给管理组。有关如何将用户和组分配给用户任务的详细信息,请参阅有关用户任务分配的部分。
-
当达到无结束事件时,该过程结束。
-
元素通过顺序流相互连接。这些序列流具有a
source
和target
定义序列流的方向。
| <definitions id="definitions" targetNamespace="http://activiti.org/bpmn20" xmlns:activiti="http://activiti.org/bpmn" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"> <process id="financialReport" name="Monthly financial report reminder process"> <startEvent id="theStart" /> <sequenceFlow id="flow1" sourceRef="theStart" targetRef="writeReportTask" /> <userTask id="writeReportTask" name="Write monthly financial report" > <documentation> Write monthly financial report for publication to shareholders. </documentation> <potentialOwner> <resourceAssignmentExpression> <formalExpression>accountancy</formalExpression> </resourceAssignmentExpression> </potentialOwner> </userTask> <sequenceFlow id="flow2" sourceRef="writeReportTask" targetRef="verifyReportTask" /> <userTask id="verifyReportTask" name="Verify monthly financial report" > <documentation> Verify monthly financial report composed by the accountancy department. This financial report is going to be sent to all the company shareholders. </documentation> <potentialOwner> <resourceAssignmentExpression> <formalExpression>management</formalExpression> </resourceAssignmentExpression> </potentialOwner> </userTask> <sequenceFlow id="flow3" sourceRef="verifyReportTask" targetRef="theEnd" /> <endEvent id="theEnd" /> </process> </definitions> |
7.3.6。启动流程实例
我们现在已经创建了业务流程的流程定义。从这样的流程定义中,我们可以创建流程实例。在这种情况下,一个流程实例将与特定月份的单个财务报告的创建和验证相匹配。所有流程实例共享相同的流程定义。
为了能够从给定的流程定义创建流程实例,我们必须首先部署此流程定义。部署流程定义意味着两件事:
-
流程定义将存储在为Activiti引擎配置的持久数据存储中。因此,通过部署我们的业务流程,我们确保引擎在引擎重启后找到流程定义。
-
BPMN 2.0流程文件将被解析为内存中的对象模型,该模型可以通过Activiti API进行操作。
有关部署的更多信息,请参阅有关部署的专用部分。
如该部分所述,部署可以通过多种方式进行。一种方法是通过API如下。请注意,与Activiti引擎的所有交互都通过其服务进行。
| Deployment deployment = repositoryService.createDeployment() .addClasspathResource("FinancialReportProcess.bpmn20.xml") .deploy(); |
现在,我们可以使用id
流程定义中定义的流程实例启动新的流程实例(请参阅XML文件中的流程元素)。请注意,id
Activiti术语中的这个称为密钥。
| ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("financialReport"); |
这将创建一个首先通过start事件的流程实例。在启动事件之后,它遵循所有传出的顺序流(在这种情况下只有一个)并且达到第一个任务(写月度财务报告)。Activiti引擎现在将任务存储在持久数据库中。此时,附加到任务的用户或组分配将被解析并存储在数据库中。值得注意的是,Activiti引擎将继续执行流程执行步骤,直到达到等待状态,例如用户任务。在这种等待状态下,流程实例的当前状态存储在数据库中。在用户决定完成任务之前,它仍处于该状态。此时,引擎将继续运行,直到达到新的等待状态或进程结束。当引擎在此期间重新启动或崩溃时,该过程的状态在数据库中是安全的。
创建任务后,该startProcessInstanceByKey
方法将返回,因为用户任务活动是等待状态。在这种情况下,任务被分配给一个组,这意味着该组的每个成员都是执行该任务的候选者。
我们现在可以将所有这些放在一起并创建一个简单的Java程序。创建一个新的Eclipse项目,并将Activiti JAR和依赖项添加到其类路径中(这些可以在Activiti发行版的libs文件夹中找到)。在我们调用Activiti服务之前,我们必须首先构建一个ProcessEngine
允许我们访问服务的服务。这里我们使用'standalone'配置,它构造一个ProcessEngine
使用也在演示设置中使用的数据库。
您可以在此处下载流程定义XML 。此文件包含如上所示的XML,但还包含必要的BPMN 图交换信息,以便在Activiti工具中可视化该过程。
| 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.3.7。任务列表
我们现在可以TaskService
通过添加以下逻辑来检索此任务:
| List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("kermit").list(); |
请注意,我们传递给此操作的用户需要是会计组的成员,因为它是在流程定义中声明的:
| <potentialOwner> <resourceAssignmentExpression> <formalExpression>accountancy</formalExpression> </resourceAssignmentExpression> </potentialOwner> |
我们还可以使用任务查询API来使用组的名称获得相同的结果。我们现在可以在代码中添加以下逻辑:
| TaskService taskService = processEngine.getTaskService(); List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("accountancy").list(); |
由于我们已经将我们配置ProcessEngine
为使用与演示设置正在使用的相同的数据库,因此我们现在可以登录到Activiti Explorer。默认情况下,没有用户在会计组中。使用kermit / kermit登录,单击“组”,然后单击“创建组”。然后单击“用户”并将该组添加到fozzie。现在使用fozzie / fozzie登录,我们会发现在选择“ 流程”页面并单击“每月财务报告”流程对应的“操作”列中的“启动流程”链接后,我们就可以启动业务流程。
如上所述,该过程将执行第一个用户任务。由于我们以kermit身份登录,因此我们可以看到在我们启动流程实例后,有一个新的候选任务可供他使用。选择“ 任务”页面以查看此新任务。请注意,即使该进程是由其他人启动的,该任务仍然可以作为候选任务显示给会计组中的每个人。
7.3.8。声称任务
会计师现在需要申领任务。通过声明任务,特定用户将成为任务的受让人,并且任务将从会计组的其他成员的每个任务列表中消失。声明任务是以编程方式完成的,如下所示:
| taskService.claim(task.getId(), "fozzie"); |
该任务现在位于声明任务的个人任务列表中。
| List<Task> tasks = taskService.createTaskQuery().taskAssignee("fozzie").list(); |
在Activiti UI App中,单击声明按钮将调用相同的操作。该任务现在将移至登录用户的个人任务列表。您还会看到任务的受理人已更改为当前登录的用户。
7.3.9。完成任务
会计师现在可以开始编制财务报告。报告完成后,他可以完成任务,这意味着完成该任务的所有工作。
| taskService.complete(task.getId()); |
对于Activiti引擎,这是一个外部信号,必须继续执行流程实例。任务本身将从运行时数据中删除。遵循任务的单个传出转换,将执行移至第二个任务(“报告验证”)。现在将使用与第一个任务描述的机制相同的机制来分配第二个任务,但差异很小,即任务将分配给 管理组。
在演示设置中,单击任务列表中的完整按钮即可完成任务。由于Fozzie不是会计师,我们需要退出Activiti Explorer并以kermit(谁是经理)身份登录。现在,第二个任务在未分配的任务列表中可见。
7.3.10。结束这个过程
可以以与以前完全相同的方式检索和声明验证任务。完成第二个任务会将流程执行移至结束事件,从而完成流程实例。将从数据存储中删除流程实例和所有相关的运行时执行数据。
当您登录Activiti Explorer时,您可以对此进行验证,因为在存储流程执行的表中不会找到任何记录。
以编程方式,您还可以使用以下命令验证进程是否已结束 historyService
| HistoryService historyService = processEngine.getHistoryService(); HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(procId).singleResult(); System.out.println("Process instance end time: " + historicProcessInstance.getEndTime()); |
7.3.11。代码概述
结合前面部分的所有片段,你应该有这样的东西(这段代码考虑到你可能会通过Activiti Explorer UI启动一些流程实例。因此,它总是检索一个任务列表而不是一个任务,所以它总是有效):
| 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<Task> 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 management 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()); } } |
7.3.12。未来的改进
很容易看出这个业务流程太简单,无法在现实中使用。但是,当您浏览Activiti中提供的BPMN 2.0构造时,您将能够通过以下方式增强业务流程:
-
定义充当决策的网关。这样,经理可以拒绝重建会计任务的财务报告。
-
声明和使用变量,以便我们可以存储或引用报表,以便可以在表单中显示它。
-
在流程结束时定义服务任务,将任务发送给每个股东。
-
等等