实际上前面那些都是前置知识,真正上手的话其实还是一脸懵的情况,这里我也是按照文档生成一个最简单的demo。这个demo就是一个简单的Maven工程,你只要启动main方法就可以看到效果。
github实例代码地址
这里如果直接下载代码,如果不熟悉maven的话可能不能直接上手用。其实不需要多做什么,直接点击对应的SimpleDemo下面的pom文件,点击右键找到“add maven xxx”的就可以了,转化成maven工程就可以直接上手用了。
当然也可以根据文档或者我们下面的步骤一部接一部的来
文档对应的地址
flowable构建命令行程序
一创建一个maven工程的flowable命令行程序
创建一个maven工程,这个就不赘述了,文档还有其他关于如何创建的maven工程的文章也很多,一搜一大把。
maven主要还是需要解决他的pom文件里面的依赖,这我直接贴完整的
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>flowable-test</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-engine</artifactId>
<version>6.7.2</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.3.176</version>
</dependency>
<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>
<!-- 这个其实也可以替代上面两个slf4j设置-->
<!-- <dependency>-->
<!-- <groupId>org.slf4j</groupId>-->
<!-- <artifactId>slf4j-simple</artifactId>-->
<!-- <version>1.7.21</version>-->
<!-- <scope>compile</scope>-->
<!-- </dependency>-->
</dependencies>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
这里slf4j是因为flowable需要的,所以也是需要添加对应的依赖。
当然它对应的日志配置文件log4j.properties
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
再加一个main方法作为demo的入口
package org.flowable;
public class HolidayRequest {
public static void main(String[] args) {
}
}
二 创建流程引擎
初始化ProcessEngine流程引擎实例,类似于MySQL的innoDB,系统处理的核心部分。
这是一个线程安全的对象,因此通常只需要在一个应用中初始化一次。 ProcessEngine由ProcessEngineConfiguration实例创建。该实例可以配置与调整流程引擎的设置。 通常使用一个配置XML文件创建ProcessEngineConfiguration,但是(像在这里做的一样)也可以编程方式创建它。 ProcessEngineConfiguration所需的最小配置,是数据库JDBC连接。
这里用的代码直接配置数据库就不管了,最简单那的种。
ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
.setJdbcUrl("jdbc:h2:mem:flowable;DB_CLOSE_DELAY=-1")
.setJdbcUsername("sa")
.setJdbcPassword("")
.setJdbcDriver("org.h2.Driver")
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
ProcessEngine processEngine = cfg.buildProcessEngine();
二 部署流程定义
构建的流程是一个非常简单的请假流程。Flowable引擎需要流程定义为BPMN 2.0格式。大部分我们还都是画图直接画的,像平时干脆直接那别的流程改一改,作为一个新的流程。
画的图背后实际还是xml文件,如下,但是其中很多部分都是通用的,但是设计比较复杂的流程或和比较多的话,还是图更直接一点。
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
xmlns:flowable="http://flowable.org/bpmn"
typeLanguage="http://www.w3.org/2001/XMLSchema"
expressionLanguage="http://www.w3.org/1999/XPath"
targetNamespace="http://www.flowable.org/processdef">
<process id="holidayRequest" name="Holiday Request" isExecutable="true">
<startEvent id="startEvent"/>
<sequenceFlow sourceRef="startEvent" targetRef="approveTask"/>
<userTask id="approveTask" name="Approve or reject request"/>
<sequenceFlow sourceRef="approveTask" targetRef="decision"/>
<exclusiveGateway id="decision"/>
<sequenceFlow sourceRef="decision" targetRef="externalSystemCall">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${approved}
]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="decision" targetRef="sendRejectionMail">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${!approved}
]]>
</conditionExpression>
</sequenceFlow>
<serviceTask id="externalSystemCall" name="Enter holidays in external system"
flowable:class="org.flowable.CallExternalSystemDelegate"/>
<sequenceFlow sourceRef="externalSystemCall" targetRef="holidayApprovedTask"/>
<userTask id="holidayApprovedTask" name="Holiday approved"/>
<sequenceFlow sourceRef="holidayApprovedTask" targetRef="approveEnd"/>
<serviceTask id="sendRejectionMail" name="Send out rejection email"
flowable:class="org.flowable.SendRejectionMail"/>
<sequenceFlow sourceRef="sendRejectionMail" targetRef="rejectEnd"/>
<endEvent id="approveEnd"/>
<endEvent id="rejectEnd"/>
</process>
</definitions>
三部署流程
这里有一个词deploy,除了部署(deploy)到引擎中,对应的方法也叫deploy()
部署一个流程定义意味着:
流程引擎会将XML文件存储在数据库中,这样可以在需要的时候获取它。
流程定义转换为内部的、可执行的对象模型,这样使用它就可以启动流程实例。
将流程定义部署至Flowable引擎,需要使用RepositoryService,其可以从ProcessEngine对象获取。使用RepositoryService,可以通过XML文件的路径创建一个新的部署(Deployment),并调用deploy()方法实际执行:
四 启动流程实例
有流程了,只是定义了一个而已,实际上我们工作流要的是一个个“审批”,这就是根据流程定义得到的流程实例。
要启动流程实例,需要提供一些初始化流程变量。一般来说,可以通过呈现给用户的表单,或者在流程由其他系统自动触发时通过REST API,来获取这些变量。在这个例子里,我们简化为使用java.util.Scanner类在命令行输入一些数据。
Scanner scanner= new Scanner(System.in);
System.out.println("Who are you?");
String employee = scanner.nextLine();
System.out.println("How many holidays do you want to request?");
Integer nrOfHolidays = Integer.valueOf(scanner.nextLine());
System.out.println("Why do you need them?");
String description = scanner.nextLine();
启动的实例,这边是根据key,这里是holidayRequest。
<process id="holidayRequest" name="Holiday Request" isExecutable="true">
<startEvent id="startEvent"/>
<sequenceFlow sourceRef="startEvent" targetRef="approveTask"/>
<userTask id="approveTask" name="Approve or reject request" flowable:candidateGroups="managers"/>
<sequenceFlow sourceRef="approveTask" targetRef="decision"/>
<exclusiveGateway id="decision"/>
<sequenceFlow sourceRef="decision" targetRef="externalSystemCall">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${approved}
]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="decision" targetRef="sendRejectionMail">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${!approved}
]]>
</conditionExpression>
</sequenceFlow>
参这XML文件使用key启动(实际上还有别的方式)
RuntimeService runtimeService = processEngine.getRuntimeService();
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("employee", employee);
variables.put("nrOfHolidays", nrOfHolidays);
variables.put("description", description);
ProcessInstance processInstance =
runtimeService.startProcessInstanceByKey("holidayRequest", variables);
五 事务
Flowable中,数据库事务扮演了关键角色,用于保证数据一致性,并解决并发问题。
当调用Flowable API时,默认情况下,所有操作都是同步的,并处于同一个事务下。这意味着,当方法调用返回时,会启动并提交一个事务。
在Flowable中,当一个流程实例运行时,总会有一个数据库事务从前一个等待状态持续到下一个等待状态。数据持久化之后,可能在数据库中保存很长时间,甚至几年,直到某个API调用使流程实例继续执行。请注意当流程处在等待状态时,不会消耗任何计算或内存资源,直到下一次APi调用。
六 查询与完成任务
查看任务列表。其中可以看到作为流程变量存储的流程实例数据,并决定如何操作任务。在这个例子中,我们通过执行API调用来模拟任务列表,通常这些API都是由UI驱动的服务在后台调用的。
用户任务candidateGroups属性
为用户任务配置办理人。我们想将第一个任务指派给"经理(managers)"组,而第二个用户任务指派给请假申请的提交人。因此需要为第一个任务添加candidateGroups属性。
<userTask id="approveTask" name="Approve or reject request" flowable:candidateGroups="managers"/>
用户任务assignee属性
第二个任务添加assignee属性。请注意我们没有像上面的’managers’一样使用静态值,而是使用一个流程变量动态指派。这个流程变量是在流程实例启动时传递的
<userTask id="holidayApprovedTask" name="Holiday approved" flowable:assignee="${employee}"/>
获得实际的任务列表,需要通过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());
}
可以使用任务Id获取特定流程实例的变量,并在屏幕上显示实际的申请
System.out.println("Which task would you like to complete?");
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. Do you approve this?");
完成任务
七 实现JavaDelegate
最后一步也是要注意的一步,和前面的userTask不同,这个是serviceTask。
<serviceTask id="externalSystemCall" name="Enter holidays in external system"
flowable:class="org.flowable.CallExternalSystemDelegate"/>
这时候要创建一个类CallExternalSystemDelegate,这个类继承org.flowable.engine.delegate.JavaDelegate,是用来处理execute,没有execute方法就没办法完成任务。
boolean approved = scanner.nextLine().toLowerCase().equals("y");
variables = new HashMap<String, Object>();
variables.put("approved", approved);
taskService.complete(task.getId(), variables);
这里要注意这个类的包名一定是org.flowable,一定是org.flowable,一定是org.flowable。
可以看一下我github的示例代码
完整代码
package com.flowable.holiday;
import org.flowable.engine.*;
import org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
/**
* @Author: zhangpeng
* @Description:
* @Date: 2022/7/28
*/
public class HolidayRequest {
public static void main(String[] args) {
//(0)流程引擎的配置和实例初始化
//ProcessEngineConfiguration 流程实例引擎的配置,配置数据库连接
ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
.setJdbcUrl("jdbc:h2:mem:flowable;DB_CLOSE_DELAY=-1")
.setJdbcUsername("sa")
.setJdbcPassword("")
.setJdbcDriver("org.h2.Driver")
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
ProcessEngine processEngine = cfg.buildProcessEngine();
//xml文件 deploy方法就是部署流程定义部署至Flowable引擎。从流程引擎processEngin获取流程存储库的的服务RepositoryService,往里面部署我们定义的流程
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("holiday-request.bpmn20.xml")
.deploy();
//RepositoryService创建的ProcessDefinitionQuery对象实现
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.deploymentId(deployment.getId())
.singleResult();
System.out.println("Found process definition : " + processDefinition.getName());
// (1)启动流程:输入申请的假期
//屏幕输入字段作为
Scanner scanner= new Scanner(System.in);
System.out.println("Who are you?");
String employee = scanner.nextLine();
System.out.println("How many holidays do you want to request?");
Integer nrOfHolidays = Integer.valueOf(scanner.nextLine());
System.out.println("Why do you need them?");
String description = scanner.nextLine();
//RuntimeService开启实例
RuntimeService runtimeService = processEngine.getRuntimeService();
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("employee", employee);
variables.put("nrOfHolidays", nrOfHolidays);
variables.put("description", description);
ProcessInstance processInstance =
runtimeService.startProcessInstanceByKey("holidayRequest", variables);
//(2)
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("Which task would you like to complete?");
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. Do you approve this?");
boolean approved = scanner.nextLine().toLowerCase().equals("y");
variables = new HashMap<String, Object>();
variables.put("approved", approved);
taskService.complete(task.getId(), variables);
}
}