2021-06-01

[TOC] 

## 1.简介

Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN 2.0流程定义(用于定义流程的行业XML标准), 创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据,等等。这个章节将用一个可以在你自己的开发环境中使用的例子,逐步介绍各种概念与API。

Flowable可以十分灵活地加入你的应用/服务/构架。可以将JAR形式发布的Flowable库加入应用或服务,来嵌入引擎。 以JAR形式发布使Flowable可以轻易加入任何Java环境:Java SE;Tomcat、Jetty或Spring之类的servlet容器;JBoss或WebSphere之类的Java EE服务器,等等。 另外,也可以使用Flowable REST API进行HTTP调用。也有许多Flowable应用(Flowable Modeler, Flowable Admin, Flowable IDM 与 Flowable Task),提供了直接可用的UI示例,可以使用流程与任务。

所有使用Flowable方法的共同点是核心引擎。核心引擎是一组服务的集合,并提供管理与执行业务流程的API。 下面的教程从设置与使用核心引擎的介绍开始。后续章节都建立在之前章节中获取的知识之上。

第一节展示了以最简单的方式运行Flowable的方法:只使用Java SE的标准Java main方法。这里也会介绍许多核心概念与API。

**另: Flowable是Activiti(Alfresco持有的注册商标)的fork。在下面的章节中,你会注意到包名,配置文件等等,都使用flowable。**

### 1.flowable的五个引擎

#### 1.1 flowable包含五个引擎,分别是:

1. 内容引擎 ContentEngine

1. 身份识别引擎 IdmEngine

1. 表单引擎 FormEngine

1. 决策引擎 DmnEngine

1. 流程引擎 ProcessEngine

#### 1.2 引擎包含的服务

每个引擎由相对应的 EngineConfiguration进行创建,在创建过程中对每个引擎使用的服务进行初始化。

##### 1.2.1 内容引擎 ContentEngine

**内容引擎包含的服务有: ContentManagementService**

>ContentManagementService提供对数据库表的管理操作,包括:

>

>Map<String, Long> getTableCount() 获取每个表的记录数量;

>String getTableName(Class<?> flowableEntityClass); 根据实体类获得对应的数据库表名;

>TableMetaData getTableMetaData(String tableName); 根据数据库表名获得表的列名和列类型;

>TablePageQuery createTablePageQuery(); 创建一个可以进行排序、根据条件分页的查询类。

>ContentService

>

>实现对内容的创建、删除、保存和获取的基本操作。

>

>ContentItem newContentItem();

>

>void saveContentItem(ContentItem contentItem);

>

>void saveContentItem(ContentItem contentItem, InputStream inputStream);

>

>InputStream getContentItemData(String contentItemId);

>

>void deleteContentItem(String contentItemId);

>

>void deleteContentItemsByProcessInstanceId(String processInstanceId);

>

>void deleteContentItemsByTaskId(String taskId);

>

>ContentItemQuery createContentItemQuery();

ContentEngineConfiguration

ContentEngineConfiguration最主要的作用是提供Mybatis的封装,实现数据库相关配置的获取。

同时,内容引擎配置还提供了操作系统级的文件操作的路径设置、文件读取、文件保存的功能。

##### 1.2.2 身份识别引擎 IdmEngine

**身份识别引擎包含的服务有:IdmIdentityService**

>提供用户的创建、修改、删除、密码修改、登录、用户头像设置等; 

>提供组Group的创建、删除、用户与组关系的关联、删除关联; 

>提供权限的创建、删除、关联等。

>

>IdmManagementService

>

>对身份识别相关的数据库表进行统计、获取表的列信息。

>

>IdmEngineConfiguration

>

>提供数据库配置信息。

##### 1.2.3 表单引擎 FormEngine

>表单引擎包含的服务有:

>

>FormManagementService

>FormRepositoryService

>FormService

>FormEngineConfiguration

##### 1.2.4 决策引擎 DmnEngine

>决策引擎包含的服务有:

>

>DmnManagementService

>DmnRepositoryService

>DmnRuleService

>DmnHistoryService

>DmnEngineConfiguration

##### 2.2.5 流程引擎 ProcessEngine

流程引擎包含的服务有:

> RepositoryService

> RuntimeService

> HistoryService

> IdentityService。

> TaskService。

> FormService。

> ManagementService。

>DynamicBpmnService

## 2.Flowable Api

### 2.1 流程引擎API与服务

引擎API是与Flowable交互的最常用手段。总入口点是ProcessEngine。ProcessEngine可以使用多种方式创建。使用ProcessEngine,可以获得各种提供工作流/BPM方法的服务。P**rocessEngine与服务对象都是线程安全的,因此可以在服务器中保存并共用同一个引用。**

![image](http://doc.miguren.cn/bpmn/images/api.services.png)

**流程引擎中的常用服务接口**

>提供一系列管理流程部署和流程定义的API。

**RepositoryService:**

RepositoryService repositoryService = processEngine.getRepositoryService();

>在流程运行时对流程实例进行管理与控制。

**RuntimeService**

RuntimeService runtimeService = processEngine.getRuntimeService();

>对流程的历史数据进行操作,包括查询、删除这些历史数据。

**HistoryService**

HistoryService historyService = processEngine.getHistoryService();

>提供对流程角色数据的API,这些角色数据包括用户组、用户及它们之间的关系。

**IdentityService**

IdentityService identityService = processEngine.getIdentityService();

> 对流程任务进行管理,例如任务提醒、任务完成和创建任务等。

**TaskService**

TaskService taskService = processEngine.getTaskService();

> 表单服务。

**FormService**

FormService formService = processEngine.getFormService();

> 提供对流程引擎进行管理和维护的服务。

**ManagementService**

ManagementService managementService = processEngine.getManagementService();

>可用于修改流程定义中的部分内容,而不需要重新部署它。例如可以修改流程定义中一个用户任务的办理人设置,或者修改一个服务任务中的类名。

**DynamicBpmnService**

DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService();

* * *

**RepositoryService很可能是使用Flowable引擎要用的第一个服务**。这个服务提供了管理与控制部署(deployments)与流程定义(process definitions)的操作。在这里简单说明一下,流程定义是BPMN 2.0流程对应的Java对象,体现流程中每一步的结构与行为。部署是Flowable引擎中的包装单元,一个部署中可以包含多个BPMN 2.0 XML文件及其他资源。开发者可以决定在一个部署中包含的内容,可以是单个流程的BPMN 2.0 XML文件,也可以包含多个流程及其相关资源(如’hr-processes’部署可以包含所有与人力资源流程相关的的东西)。RepositoryService可用于部署这样的包。部署意味着将它上传至引擎,引擎将在储存至数据库之前检查与分析所有的流程。在部署操作后,可以在系统中使用这个部署包,部署包中的所有流程都可以启动。

此外,这个服务还可以:

- 查询引擎现有的部署与流程定义。

- 暂停或激活部署中的某些流程,或整个部署。暂停意味着不能再对它进行操作,激活刚好相反,重新使它可以操作。

- 获取各种资源,比如部署中保存的文件,或者引擎自动生成的流程图。

- 获取POJO版本的流程定义。它可以用Java而不是XML的方式查看流程。

* * *

**RuntimeService用于启动流程定义的新流程实例**。前面介绍过,流程定义中定义了流程中不同步骤的结构与行为。流程实例则是流程定义的实际执行过程。同一时刻,一个流程定义通常有多个运行中的实例。RuntimeService也用于读取与存储流程变量。流程变量是流程实例中的数据,可以在流程的许多地方使用(例如排他网关经常使用流程变量判断流程下一步要走的路径)。RuntimeService还可以用于查询流程实例与执行(Execution)。执行也就是BPMN 2.0中 'token' 的概念。通常执行是指向流程实例当前位置的指针。最后,还可以在流程实例等待外部触发时使用RuntimeService,使流程可以继续运行。流程有许多等待状态(wait states),RuntimeService服务提供了许多操作用于“通知”流程实例:已经接收到外部触发,流程实例可以继续运行。

* * *

Flowable这样的BPM引擎来说,核心是需要人类用户操作的任务,**所有任务相关的东西都组织在TaskService中**,例如

* 查询分派给用户或组的任务

* 创建独立运行(standalone)任务。这是一种没有关联到流程实例的任务。

* 决定任务的执行用户(assignee),或者将用户通过某种方式与任务关联。

* 认领(claim)与完成(complete)任务。认领是指某人决定成为任务的执行用户,也即他将会完成这个任务。完成任务是指“做这个任务要求的工作”,通常是填写某个表单。

* * *

**IdentityService**,它用于管理(创建,更新,删除,查询……)组与用户。

Flowable实际上在运行时并不做任何用户检查。例如任务可以分派给任何用户,而引擎并不会验证系统中是否存在该用户。这是因为Flowable有时要与LDAP、Active Directory等服务结合使用。

****

**FormService是可选服务**。

也就是说Flowable没有它也能很好地运行,而不必牺牲任何功能。这个服务引入了开始表单(start form)与任务表单(task form)的概念。 开始表单是在流程实例启动前显示的表单,而任务表单是用户完成任务时显示的表单。Flowable可以在BPMN 2.0流程定义中定义这些表单。表单服务通过简单的方式暴露这些数据。再次重申,表单不一定要嵌入流程定义,因此这个服务是可选的。

* * *

**HistoryService** 暴露Flowable引擎收集的所有历史数据。当执行流程时,引擎会保存许多数据(可配置),例如流程实例启动时间、谁在执行哪个任务、完成任务花费的事件、每个流程实例的执行路径,等等。这个服务主要提供查询这些数据的能力。

* * *

**ManagementService**

通常在用Flowable编写用户应用时不需要使用。它可以读取数据库表与表原始数据的信息,也提供了对作业(job)的查询与管理操作。Flowable中很多地方都使用作业,例如定时器(timer),异步操作(asynchronous continuation),延时暂停/激活(delayed suspension/activation)等等。

* * *

**DynamicBpmnService**

可用于修改流程定义中的部分内容,而不需要重新部署它。例如可以修改流程定义中一个用户任务的办理人设置,或者修改一个服务任务中的类名。

### 2.2 异常策略

**Flowable的异常基类是org.flowable.engine.FlowableException**,这是一个非受检异常(unchecked exception)。在任何API操作时都可能会抛出这个异常,javadoc提供了每个方法可能抛出的异常。例如,从TaskService中摘录:

```

/**

* 当任务成功执行时调用。

* @param taskId 需要完成的任务id,不能为null。

* @throws FlowableObjectNotFoundException 若给定id找不到任务。

*/

void complete(String taskId);

```

在上例中,如果所用的id找不到任务,就会抛出异常。并且,**由于javadoc中明确要求taskId不能为null,因此如果传递了null值**,会抛出FlowableIllegalArgumentException异常。

尽管我们想避免过大的异常层次结构,但在特定情况下仍然会抛出下述异常子类。所有流程执行与API调用中发生的错误,如果不符合下面列出的异常,**会统一抛出FlowableExceptions**。

* FlowableWrongDbException: 当Flowable引擎检测到数据库表结构版本与引擎版本不匹配时抛出。

* FlowableOptimisticLockingException: 当对同一数据实体的并发访问导致数据存储发生乐观锁异常时抛出。、

* FlowableClassLoadingException: 当需要载入的类(如JavaDelegate, TaskListener, …)无法找到,或载入发生错误时抛出。

* FlowableObjectNotFoundException: 当请求或要操作的对象不存在时抛出。

* FlowableIllegalArgumentException: 当调用Flowable API时使用了不合法的参数时抛出。可能是引擎配置中的不合法值,或者是API调用传递的不合法参数,也可能是流程定义中的不合法值。

* FlowableTaskAlreadyClaimedException: 当对已被认领的任务调用taskService.claim(…)时抛出。

### 2.3 查询APi

从引擎中查询数据有两种方式:**查询API与原生(native)查询**。查询API可以使用链式API,通过编程方式进行类型安全的查询。你可以在查询中增加各种条件(所有条件都用做AND逻辑),也可以明确指定排序方式。下面是示例代码:

```

// 查询任务 受理人为kermit ,流程绑定参数为orderId- 0815的集合数据,并以日期排序

List<Task> tasks = taskService.createTaskQuery()

.taskAssignee("kermit")

.processVariableValueEquals("orderId", "0815")

.orderByDueDate().asc()

.list();

long count = taskService.createNativeTaskQuery()

.sql("SELECT count(*) FROM " + managementService.getTableName(Task.class) + " T1, " +

managementService.getTableName(VariableInstanceEntity.class) + " V1 WHERE V1.TASK_ID_ = T1.ID_")

.count(); 

```

### 2.4 变量

流程实例按步骤执行时,需要使用一些数据。在Flowable中,这些数据称作变量(variable),并会存储在数据库中。变量可以用在表达式中(**例如在排他网关中用于选择正确的出口路径**),也可以在Java服务任务(service task)中用于调用外部服务(例如为服务调用提供输入或结果存储),等等。

流程实例可以持有变量(称作流程变量 process variables);用户任务以及执行(executions)——流程当前活动节点的指针——也可以持有变量。流程实例可以持有任意数量的变量,每个变量存储为**ACT_RU_VARIABLE**数据库表的一行。

所有的startProcessInstanceXXX方法都有一个可选参数,用于在流程实例创建及启动时设置变量。例如,在RuntimeService中:

```

//启动流程实例, variables为传递的流程变量

ProcessInstance startProcessInstanceByKey(String processDefinitionKey, Map<String, Object> variables);

```

也可以在流程执行中加入变量。例如,(RuntimeService):

```

void setVariable(String executionId, String variableName, Object value);

void setVariableLocal(String executionId, String variableName, Object value);

void setVariables(String executionId, Map<String, ? extends Object> variables);

void setVariablesLocal(String executionId, Map<String, ? extends Object> variables);

```

请注意可以为给定执行(**请记住,流程实例由一颗执行的树(tree of executions)组成**)设置局部(local)变量。局部变量将只在该执行中可见,对执行树的上层则不可见。这可以用于 数据不应该暴露给流程实例的其他执行,或者变量在流程实例的不同路径中有不同的值(例如使用并行路径时)的情况。

可以用下列方法读取变量。请注意TaskService中有类似的方法。这意味着任务与执行一样,可以持有局部变量,其生存期为任务持续的时间。

```

Map<String, Object> getVariables(String executionId);

Map<String, Object> getVariablesLocal(String executionId);

Map<String, Object> getVariables(String executionId, Collection<String> variableNames);

Map<String, Object> getVariablesLocal(String executionId, Collection<String> variableNames);

Object getVariable(String executionId, String variableName);

<T> T getVariable(String executionId, String variableName, Class<T> variableClass);

```

变量通常用于Java代理(Java delegates)、表达式(expressions)、执行(execution)、任务监听器(tasklisteners)、脚本(scripts)等等。在这些结构中,提供了当前的execution或task对象,可用于变量的设置、读取。简单示例如下:

```

execution.getVariables();

execution.getVariables(Collection<String> variableNames);

execution.getVariable(String variableName);

execution.setVariables(Map<String, object> variables);

execution.setVariable(String variableName, Object value);

```

请注意也可以使用上例中方法的局部变量版本。

由于历史(与向后兼容)原因,当调用上述任何方法时,引擎会从数据库中取出所有变量。也就是说,如果你有10个变量,使用getVariable("myVariable")获取其中的一个,实际上其他9个变量也会从数据库取出并缓存。这并不坏,因为后续的调用可以不必再读取数据库。比如,如果流程定义包含三个连续的服务任务(因此它们在同一个数据库事务里),在第一个服务任务里通过一次调用获取全部变量,也许比在每个服务任务里分别获取需要的变量要好。请注意对读取与设置变量都是这样。

当然,如果使用大量变量,或者你希望精细控制数据库查询与流量,上述的做法就不合适了。我们引入了可以更精细控制的方法。这个方法有一个可选的参数,告诉引擎是否需要读取并缓存所有变量:

```

Map<String, Object> getVariables(Collection<String> variableNames, boolean fetchAllVariables);

Object getVariable(String variableName, boolean fetchAllVariables);

void setVariable(String variableName, Object value, boolean fetchAllVariables);

```

### 2.5 瞬时变量

### 2.5 任务监听listener

![a6c2efb2915d7ced1c84f5e3f2c6d76d.jpeg](evernotecid://D1CE3FA2-D07A-4091-9C4F-04C70ACE32BC/appyinxiangcom/29066271/ENResource/p10)

#### 2.5.1 任务监听器类型

* create:在任务创建且所有任务属性设置完成之后才触发。

* assignment:在任务被分配给某个班里人之后触发,它是在create事件触发前被触发。

* complete:在配置了监听器的任务完成时触发,也就是说运行期任务删除之前触发。

* delete:任务删除触发

#### 2.5.2 任务监听器的三种监听器执行类型

1. 类(class): :需要类的全路径

例子:

```

public class StartTaskListener implements TaskListener {

@Override

public void notify(DelegateTask delegateTask) {

logger.debug("调用了任务监听器");

}

}

```

任务监听的配置:

![c6d3fdd7d3b6a8baebb3159b29c6e0a8.png](evernotecid://D1CE3FA2-D07A-4091-9C4F-04C70ACE32BC/appyinxiangcom/29066271/ENResource/p11)

2.表达式(expression):定义一个表达式,类似EL的语法

![ddbc75a05afa142395a1e72be096d528.png](evernotecid://D1CE3FA2-D07A-4091-9C4F-04C70ACE32BC/appyinxiangcom/29066271/ENResource/p12)

3.委托表达式(delegateExpression):指的是一个实现监听接口

1.自定义接口 实现TaskListener, @Component 标识

```

@Component(value = "taskBusinessCallListener")

public class TaskBusinessCallListener extends BusinessCallListener implements TaskListener {

/**

* dubbo的类名

*/

private FixedValue clazzName;

/**

* 方法名

*/

private FixedValue method;

/**

* 版本号

*/

private FixedValue version;

/**

* 参数 多个的话用分号隔开 实例 userCode:00004737;status:1

*/

private FixedValue params;

@Override

public void notify(DelegateTask delegateTask) {

String processInstanceId = delegateTask.getProcessInstanceId();

//执行回调

this.callBack(processInstanceId, clazzName.getExpressionText(), method.getExpressionText(), version.getExpressionText(), params.getExpressionText());

}

}

```

![1a81a9ac921f605b37da59b94775e635.png](evernotecid://D1CE3FA2-D07A-4091-9C4F-04C70ACE32BC/appyinxiangcom/29066271/ENResource/p13)

* * *

* * *

* * *

* * *

* * *

flowable的办理方式,分为两种:签收模式办理和直接办理。

术语:

Assignee: 任务的受理人,即执行人。它有两种情况(有值,NULL)

Owner: 任务的委托人。

CandidateGroup: 候选用户组

CandidateUser: 候选人

delegateTask: 委派任务/签收的任务

resolveTask:  委派任务的代办,任务的拥有者把任务委派他人来办理,他人办完后,又重新回到任务拥有者,会产生流转记录。

turnTask: 转办任务,只是改变当前任务的办理人而已,不会产生流转记录。

CompleteTask: 完成任务,或叫办结提交下一步。

claimTask:任务签收

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

推荐阅读更多精彩内容