创建一个流程引擎
先说一下我的环境
Windows 7
JDK 8
IDEA
第一个工作流我们就完成一个请假的流程:
1. 员工发出请假申请
2. 经理同意或拒绝该申请
3. 发送电子邮件给员工
使用 IDEA 创建 Maven 项目
添加依赖项
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-engine</artifactId>
<version>6.2.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.30</version>
</dependency>
由于我用的是 mysql 数据库, 所以添加的是 mysql 的启动.
当你添加完了依赖后你的项目看起来是这个样子的.(2017年12月13日 最新版本为 6.2.1)
Flowable在内部使用SLF4J作为日志框架, 所以我们还需要加入对应的坐标
dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
Log4j需要一个属性文件进行配置, 使用以下内容将log4j.properties文件添加到src / main / resources
文件夹中
log4j.rootLogger = DEBUG,CA
log4j.appender.CA = org.apache.log4j.ConsoleAppender
log4j.appender.CA.layout = org.apache.log4j.PatternLayout
log4j.appender.CA.layout.ConversionPattern =%d {hh:mm:ss,SSS} [%t]%-5p%c%x - %m%n
创建一个新的Java类并添加main
方法:
public class App
{
public static void main( String[] args )
{
ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/basics_database?useUnicode=true&characterEncoding=UTF8&autoReconnect=true&failOverReadOnly=false&allowMultiQueries=true")
.setJdbcUsername("root")
.setJdbcPassword("root")
.setJdbcDriver("com.mysql.jdbc.Driver")
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
ProcessEngine processEngine = cfg.buildProcessEngine();
}
}
如果我们要创建一个 ProcessEngine
流程引擎, 那么我们就需要先创建一个 ProcessEngineConfiguration
实例. 它允许你配置和调整设置的流程引擎.
通常, ProcessEngineConfiguration
是使用配置XML文件创建的,
但是, 我们也可以通过Java代码来创建它.
对于 ProcessEngineConfiguration
实例的创建我们要重点说一下 setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE)
代码. 当我们设置为 true
的时候, 如果我们数据库中的表不存在会被自动创建.
当我们流程引擎配置完成之后, 我们就使用 cfg.buildProcessEngine()
方法来实例化一个流程引擎.
注意:
ProcessEngine
实例是一个线程安全的对象, 通常只需在应用程序中实例化一次.
启动应用程序
应用程序启动后, 我们需要给他一定的初始化时间, 当程序正确结束后, 我们看一下数据库中是不是多了很多表呢.
部署流程定义
我们将建立的流程是一个非常简单的请假流程. Flowable引擎期望过程在BPMN 2.0格式中定义, 这是业界广泛接受的XML标准.
在Flowable术语中, 我们将这作为一个流程定义来说明. 流程定义定义了请假流程所涉及的不同步骤, 而一个流程实例与特定员工的请假相匹配.
我们假设这个过程是通过提供一些信息来开始的, 例如员工姓名.
当然, 这可以作为这个过程中的第一步. 但是, 通过将其作为输入数据, 只有在发出真正的请求时才会创建实例.
在另一种情况下, 用户可以在提交之前改变主意并取消, 但是流程实例已经被创建. 在某些情况下, 这可能是有价值的信息(例如, 流程已启动多少次, 但尚未完成), 具体取决于业务目标.
- 左边的圆叫做启动事件. 这是流程实例的起点.
- 第一个矩形是一个用户任务. 这是人类用户必须执行的过程中的一个步骤. 在这种情况下, 经理需要批准或拒绝请求.
- 根据经理的决定, Exclusive Gateway(带X字的菱形)将流程实例路由到批准路径或拒绝路径.
- 如果同意, 我们必须在某个外部系统中注册该请求, 然后再为用户通知用户任务, 通知他们你的请假同意.
- 如果被拒绝, 则会向员工发送电子邮件, 通知他们这一点.
对应于上图的BPMN 2.0 XML如下所示. 请注意, 这只是过程的一部分. 如果您使用的是图形建模工具, 则底层的XML文件还包含描述图形信息的可视化部分, 例如流程定义的各个元素的坐标(所有图形信息包含在XML 中的BPMNDiagram标记中, 这是定义标记的子元素).
<?xml version="1.0" encoding="GBK" standalone="yes"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:tns="http://sourceforge.net/bpmn/definitions/_1513158409301" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:yaoqiang="http://bpmn.sourceforge.net" exporter="Yaoqiang BPMN Editor" exporterVersion="5.3" expressionLanguage="http://www.w3.org/1999/XPath" id="_1513158409301" name="" targetNamespace="http://sourceforge.net/bpmn/definitions/_1513158409301" typeLanguage="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://bpmn.sourceforge.net/schemas/BPMN20.xsd">
<process id="PROCESS_1" isClosed="false" isExecutable="true" processType="None">
<extensionElements>
<yaoqiang:description/>
<yaoqiang:pageFormat height="841.8897637795276" imageableHeight="831.8897637795276" imageableWidth="588.1102362204724" imageableX="5.0" imageableY="5.0" orientation="0" width="598.1102362204724"/>
<yaoqiang:page background="#FFFFFF" horizontalCount="1" verticalCount="1"/>
</extensionElements>
<startEvent id="_2" isInterrupting="true" name="开始" parallelMultiple="false">
<outgoing>_10</outgoing>
<outputSet/>
</startEvent>
<userTask completionQuantity="1" id="_3" implementation="##unspecified" isForCompensation="false" name="同意或拒绝" startQuantity="1">
<incoming>_10</incoming>
<outgoing>_11</outgoing>
</userTask>
<exclusiveGateway gatewayDirection="Diverging" id="_4">
<incoming>_11</incoming>
<outgoing>_12</outgoing>
<outgoing>_13</outgoing>
</exclusiveGateway>
<serviceTask completionQuantity="1" id="_5" implementation="##WebService" isForCompensation="false" name="在外部系统中输入假期" startQuantity="1">
<incoming>_12</incoming>
<outgoing>_14</outgoing>
</serviceTask>
<serviceTask completionQuantity="1" id="_6" implementation="##WebService" isForCompensation="false" name="发送拒绝邮件" startQuantity="1">
<incoming>_13</incoming>
<outgoing>_16</outgoing>
</serviceTask>
<userTask completionQuantity="1" id="_7" implementation="##unspecified" isForCompensation="false" name="假期批准" startQuantity="1">
<incoming>_14</incoming>
<outgoing>_15</outgoing>
</userTask>
<sequenceFlow id="_10" sourceRef="_2" targetRef="_3"/>
<endEvent id="_9" name="结束">
<incoming>_16</incoming>
<inputSet/>
</endEvent>
<sequenceFlow id="_11" sourceRef="_3" targetRef="_4"/>
<sequenceFlow id="_12" name="同意" sourceRef="_4" targetRef="_5">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${approved}
]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="_13" name="拒绝" sourceRef="_4" targetRef="_6">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${!approved}
]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow id="_14" sourceRef="_5" targetRef="_7"/>
<sequenceFlow id="_15" sourceRef="_7" targetRef="_8"/>
<sequenceFlow id="_16" sourceRef="_6" targetRef="_9"/>
<endEvent id="_8" name="结束">
<incoming>_15</incoming>
<inputSet/>
</endEvent>
</process>
</definitions>
Exclusive Gateway(带X字的菱形)的流显然是特殊的: 都具有以表达式的形式定义的条件.
当流程实例执行到达Exclusive Gateway时, 将评估条件获取并解析为true, 只有第一个表达式满足.
当然, 如果需要不同的路由行为, 其他类型的Exclusive Gateway也是可能的.
这里以表达式形式写出的条件是$ {approved}
, 它是$ {approved == true}
的简写形式.
approved变量称为流程变量. 流程变量是一个持久的数据位, 与流程实例一起存储, 可以在流程实例的生命周期中使用.
在这种情况下, 这意味着我们将不得不在流程实例中的某个点设置此流程变量.
将它部署到引擎中
现在我们有了BPMN 2.0 XML文件的流程, 接下来我们需要将它部署到引擎中. 部署流程定义意味着:
- 流程引擎会将XML文件存储在数据库中, 因此可以在需要时进行检索.
- 流程定义被解析为一个内部可执行的对象模型, 以便流程实例可以从中启动.
要将流程定义部署到Flowable引擎, 需要使用RepositoryService
, 可以从ProcessEngine
对象中获取.
使用RepositoryService
, 通过传递XML文件的位置并调用deploy()
方法来实际执行它, 创建一个新的部署:
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("holiday-request.bpmn20.xml")
.deploy();
现在, 我们可以通过API查询流程定义来确认流程定义是否已经被引擎所了解. 这是通过RepositoryService
创建一个新的ProcessDefinitionQuery
对象来完成的。
启动一个流程实例
我们现在将流程定义部署到流程引擎, 因此可以使用此流程定义作为蓝图来启动流程实例.
要启动流程实例, 我们需要提供一些初始流程变量. 通常情况下, 当某个进程被自动触发时, 您将通过呈现给用户的表单或通过REST API获取这些表单.
在这个例子中, 我们将保持简单并使用 java.util.Scanner
类在命令行上简单地输入一些数据:
Scanner scanner= new Scanner(System.in);
System.out.println("你是谁?");
String employee = scanner.nextLine();
System.out.println("你要多少假期?");
Integer nrOfHolidays = Integer.valueOf(scanner.nextLine());
System.out.println("你为什么需要他们?");
String description = scanner.nextLine();
接下来, 我们可以通过RuntimeService
启动一个流程实例. 收集的数据以java.util.Map
实例的形式传递, 其中的关键字是稍后用于检索变量的标识符.
流程实例使用键启动. 此键匹配在BPMN 2.0 XML文件中设置的id属性, 在此情况下为PROCESS_1
。
<process id="PROCESS_1" isClosed="false" isExecutable="true" processType="None">
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("employee", employee);
variables.put("nrOfHolidays", nrOfHolidays);
variables.put("description", description);
ProcessInstance processInstance =
runtimeService.startProcessInstanceByKey("PROCESS_1", variables);
当流程实例启动时, 会创建一个execution并放入启动事件. 这个execution遵循用户任务的顺序流程以供管理者批准, 并执行用户任务行为. 此行为将在数据库中创建一个任务, 以后可以使用查询找到该任务.
查询和完成任务
在更现实的应用程序中, 将会有一个用户界面, 员工和经理可以登录并查看他们的任务列表.
通过这些, 他们可以检查存储为流程变量的流程实例数据, 并决定他们想要处理的任务. 在这个例子中, 我们将通过执行通常位于驱动UI的服务调用后面的API调用来模拟任务列表.
要注意我们还没有为userTask进行分配. 在这个例子中, 我们将第一个userTask分配给经理组. 将第二个 userTask 分配给请假申请的原始请求者.
为此, 请将 candidateGroups
属性添加到第一个任务:
<userTask flowable:candidateGroups="managers" completionQuantity="1" id="_3" implementation="##unspecified" isForCompensation="false" name="同意或拒绝" startQuantity="1"></userTask>
而第二个任务, 如下所示. 请注意, 我们没有像上面的flowable:candidateGroups="managers"
那样使用静态值, 而是基于流程实例启动时所传递的流程变量的动态赋值:
<userTask flowable:assignee="${employee}" completionQuantity="1" id="_7" implementation="##unspecified" isForCompensation="false" name="假期批准" startQuantity="1"></userTask>
为了得到实际的任务列表, 我们通过TaskService
创建一个TaskQuery
, 并且配置查询只返回managers
的任务:
TaskService taskService = processEngine.getTaskService();
List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup("managers").list();
System.out.println("You have " + tasks.size() + " tasks:");
for (int i=0; i<tasks.size(); i++) {
System.out.println((i+1) + ") " + tasks.get(i).getName());
}
使用任务标识符, 我们现在可以获得特定的流程实例变量,
并在屏幕上显示实际的请求:
System.out.println("你想完成哪个任务?");
int taskIndex = Integer.valueOf(scanner.nextLine());
Task task = tasks.get(taskIndex - 1);
Map<String, Object> processVariables = taskService.getVariables(task.getId());
System.out.println(processVariables.get("employee") + " wants " +
processVariables.get("nrOfHolidays") + " of holidays. 你赞成这个吗?");
如果上面都做完了, 我们可以执行以下程序, 就可以看到我们的任务列表.
boolean approved = scanner.nextLine().toLowerCase().equals("y");
variables = new HashMap<String, Object>();
variables.put("approved", approved);
taskService.complete(task.getId(), variables);
该任务现在已经完成, 并且基于同意的流程变量来选择离开专用网关的两个路径之一.
编写JavaDelegate
还有最后一块难题还没有完成: 我们还没有实现自动逻辑,
当请求被同意时, 这些自动逻辑就会被执行. 在BPMN 2.0 XML中, 这是一个 serviceTask, 它看起来像:
<serviceTask flowable:class="Flowable.cc.CallExternalSystemDelegate" ></serviceTask>
实际上, 这个逻辑可以是任何东西, 从用HTTP REST调用一个服务到执行一些传统的代码调用到一个组织几十年来一直使用的系统. 我们不会在这里实现实际的逻辑, 只是记录处理.
使该类实现 org.flowable.engine.delegate.JavaDelegate
接口并实现 execute
方法:
public class CallExternalSystemDelegate implements JavaDelegate {
public void execute(DelegateExecution delegateExecution) {
}
}