Flowable2-第一个应用程序

创建一个流程引擎

先说一下我的环境
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术语中, 我们将这作为一个流程定义来说明. 流程定义定义了请假流程所涉及的不同步骤, 而一个流程实例与特定员工的请假相匹配.

我们假设这个过程是通过提供一些信息来开始的, 例如员工姓名.

当然, 这可以作为这个过程中的第一步. 但是, 通过将其作为输入数据, 只有在发出真正的请求时才会创建实例.

在另一种情况下, 用户可以在提交之前改变主意并取消, 但是流程实例已经被创建. 在某些情况下, 这可能是有价值的信息(例如, 流程已启动多少次, 但尚未完成), 具体取决于业务目标.

  1. 左边的圆叫做启动事件. 这是流程实例的起点.
  2. 第一个矩形是一个用户任务. 这是人类用户必须执行的过程中的一个步骤. 在这种情况下, 经理需要批准或拒绝请求.
  3. 根据经理的决定, Exclusive Gateway(带X字的菱形)将流程实例路由到批准路径或拒绝路径.
  4. 如果同意, 我们必须在某个外部系统中注册该请求, 然后再为用户通知用户任务, 通知他们你的请假同意.
  5. 如果被拒绝, 则会向员工发送电子邮件, 通知他们这一点.

对应于上图的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文件的流程, 接下来我们需要将它部署到引擎中. 部署流程定义意味着:

  1. 流程引擎会将XML文件存储在数据库中, 因此可以在需要时进行检索.
  2. 流程定义被解析为一个内部可执行的对象模型, 以便流程实例可以从中启动.

要将流程定义部署到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) {

    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,295评论 18 399
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 学习: 每天中午一小时 读完《聪明人都是清单控》,运用清单思维做整理如 吵架清单、作息清单、目标清单、朋友清单、优...
    Sunnyshuang阅读 136评论 0 1
  • 1.出版社 每个出版社各有所长,尽量选要读领域的知名出版社。比如商务印书馆的汉译名著系列。 2.作者 作者是书的灵...
    栗子酱paramita阅读 297评论 0 0