
Flowable CMMN API 全面解析从引擎服务到表达式与单元测试的实战指南【免费下载链接】flowable-engineA compact and highly efficient workflow and Business Process Management (BPM) platform for developers, system admins and business users.项目地址: https://gitcode.com/GitHub_Trending/fl/flowable-engine导读本文以 Flowable 开源工作流引擎中的 CMMNCase Management Model and Notation案例管理模型与符号引擎 API 为核心系统讲解如何通过CmmnEngine及其六大服务与案例管理功能交互涵盖仓储部署、运行时案例实例与计划项Plan Item操作、任务处理、历史数据查询与历史清理以及异常策略、类型安全的 Query API、变量与瞬态变量、UEL 表达式与表达式函数等高级主题。读完本文你将掌握 Flowable CMMN 引擎的完整 API 骨架能够编写可运行的案例定义部署代码、使用表达式编写哨兵条件与监听器逻辑并基于 JUnit Jupiter 为案例逻辑编写标准单元测试。一、CMMN 引擎 API 与六大服务1.1 入口CmmnEngine在 Flowable 中与 CMMN 引擎交互最常见的方式就是通过引擎 API。整个 API 的起点是CmmnEngine它可以通过多种方式创建详见配置章节。从CmmnEngine上可以获取包含案例/CMMN 方法的各个服务对象。从仓库源码可以看到CmmnEngine 接口 继承自 Flowable 的通用Engine接口除声明VERSION常量对应FlowableVersions.CURRENT_VERSION外核心就是暴露各服务的方法CmmnEngine cmmnEngine CmmnEngineConfiguration.createStandaloneCmmnEngineConfiguration(); CmmnRuntimeService runtimeService cmmnEngine.getCmmnRuntimeService(); CmmnRepositoryService repositoryService cmmnEngine.getCmmnRepositoryService(); CmmnTaskService taskService cmmnEngine.getCmmnTaskService(); CmmnManagementService managementService cmmnEngine.getCmmnManagementService(); CmmnHistoryService historyService cmmnEngine.getCmmnHistoryService();CmmnEngineConfiguration.createStandaloneCmmnEngineConfiguration()会初始化并构建一个 CMMN 引擎之后始终返回该引擎实例。这里的静态工厂方法在源码 CmmnEngineConfiguration.java 中定义。配置文件的自动发现机制CmmnEngineConfiguration类会扫描类路径下所有的flowable.cmmn.cfg.xml与flowable-cmmn-context.xml文件对于每个flowable.cmmn.cfg.xmlCMMN 引擎按照 Flowable 典型方式构建CmmnEngineConfiguration.createCmmnEngineConfigurationFromInputStream(inputStream).buildCmmnEngine()对于每个flowable-cmmn-context.xmlCMMN 引擎按 Spring 方式构建先创建 Spring 应用上下文再从该上下文中获取 CMMN 引擎。线程安全与集群友好所有服务都是无状态的。这意味着可以轻松地在集群的多个节点上运行 Flowable各节点访问同一个数据库无需担心之前调用由哪台机器执行。对任意服务的任意调用都是幂等的与执行位置无关。因此你可以为整个服务器持有CmmnEngine与各服务对象的一个引用长期复用。1.2 CmmnRepositoryService部署与案例定义CmmnRepositoryService是使用 Flowable CMMN 引擎时最可能首先用到的服务它提供对部署Deployment与案例定义Case Definition的管理和操作案例定义是 CMMN 1.1 案例的 Java 对应物是对案例每个步骤结构与行为的表示部署是 Flowable CMMN 引擎内的打包单元一个部署可以包含多个 CMMN 1.1 XML 文件以及任意其他资源。一个部署里放什么由开发者决定可以只放一个 CMMN 1.1 XML 文件也可以打包一整包案例和相关资源例如部署hr-cases可以包含所有与 HR 案例相关的内容。CmmnRepositoryService可以部署上述包。部署意味着将包上传到引擎所有案例被检查并解析后才存入数据库。从这一刻起系统就认识了该部署部署中包含的任何案例都可以被启动了。此外该服务还允许查询引擎已知的部署和案例定义获取各种资源例如部署内的文件或引擎自动生成的案例图获取案例定义的 POJO 版本从而可以用 Java 而非 XML 来内省introspect案例。1.3 CmmnRuntimeService案例实例与运行时状态与主要处理静态信息的CmmnRepositoryService相反CmmnRuntimeService处理动态的运行时信息包括启动新案例实例案例定义定义了案例中不同步骤的结构与行为而案例实例是某次对该案例定义的执行。每个案例定义通常同时有多个实例在运行存取案例变量变量是与给定案例实例相关的数据可被案例中的各种构造使用例如计划项转换条件常用变量来决定案例继续走哪条路径查询案例实例与计划项计划项Plan Item是 CMMN 1.1 中已启用enabled计划项的表示外部触发续跑当案例实例处于等待外部触发器的等待状态时使用该服务的各种操作向实例发送信号告知外部触发已收到、案例实例可以继续。从源码 CmmnRuntimeService.java 可以看到更丰富的运行时能力例如createCaseInstanceBuilder()、triggerPlanItemInstance、enablePlanItemInstance、startPlanItemInstance、disablePlanItemInstance、completeStagePlanItemInstance、completeCaseInstance、terminateCaseInstance等覆盖了计划项生命周期的各个状态转换。1.4 CmmnTaskService人工任务人工任务的处理是 CMMN 引擎的核心之一与任务相关的一切都集中在CmmnTaskService中查询分配给用户或组的任务创建新的独立任务standalone task——即与案例实例无关的任务调整任务的分配人或哪些用户以某种方式参与了任务认领claim与完成complete任务认领意味着某人决定成为该任务的负责人assignee即该用户将完成任务完成意味着执行任务的工作通常是填写某种表单。1.5 CmmnHistoryService 与 CmmnManagementServiceCmmnHistoryService暴露 Flowable CMMN 引擎收集的所有历史数据。执行案例时引擎可以保留大量数据可配置例如案例实例的启动时间、谁执行了哪些任务、任务完成耗时、每个案例实例走了哪条路径等。该服务主要暴露查询能力来访问这些数据CmmnManagementService提供关于数据库表的底层信息允许查询不同类型的作业job并执行它们。关于各服务操作与引擎 API 更详细的信息可查阅项目源码目录 modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api 中各接口的 Javadoc。二、异常策略Flowable 的基础异常是org.flowable.engine.common.api.FlowableException注在现行代码结构中其实际包名为org.flowable.common.engine.api.FlowableException属于非受检异常unchecked exception。API 在任何时候都可能抛出该异常但特定方法中预期发生的异常会在 Javadoc 中说明。例如CmmnTaskService的片段/** * Called when the task is successfully executed. * param taskId the id of the task to complete, cannot be null. * throws FlowableObjectNotFoundException when no task exists with the given id. */ void complete(String taskId);上面的例子中传入一个不存在的 id 时会抛出异常。同时由于 Javadoc明确说明 taskId 不能为 null因此传入 null 时会抛出 FlowableIllegalArgumentException。虽然 Flowable 有意避免庞大的异常层级但以下子类在特定情况下会被抛出。其余在流程执行或 API 调用中发生、不属于下述异常的错误一律以普通的FlowableException抛出异常类触发场景FlowableWrongDbExceptionFlowable 引擎发现数据库 Schema 版本与引擎版本不匹配时FlowableOptimisticLockingException数据存储中因并发访问同一数据项发生乐观锁冲突时FlowableClassLoadingException请求加载的类未找到或加载出错时如 JavaDelegates、TaskListeners 等FlowableObjectNotFoundException请求或操作的对象不存在时FlowableIllegalArgumentExceptionFlowable API 调用中提供了非法参数、引擎配置中配置了非法值或流程定义中使用了非法值时FlowableTaskAlreadyClaimedException调用taskService.claim(...)时任务已被认领这些异常类在仓库中均有真实实现集中在 modules/flowable-engine-common-api/src/main/java/org/flowable/common/engine/api 目录下包括FlowableWrongDbException、FlowableOptimisticLockingException、FlowableClassLoadingException、FlowableObjectNotFoundException、FlowableIllegalArgumentException、FlowableTaskAlreadyClaimedException六个文件与文档列举一一对应。三、Query API类型安全的流式查询从引擎查询数据有两种方式Query API与原生查询native queries。Query API 允许用流式 API 编写完全类型安全的查询。你可以给查询添加各种条件所有条件之间按逻辑 AND 组合并且只能指定一个排序规则。示例ListTask tasks taskService.createTaskQuery() .taskAssignee(kermit) .orderByDueDate().asc() .list();四、变量每个案例实例在执行其组成步骤时都需要数据。在 Flowable 中这些数据被称为变量variables存储在数据库中。变量可用于表达式例如哨兵 sentry 的条件、Java 服务任务中调用外部服务例如提供服务调用的输入或存储其结果等场景。案例实例可以有变量称为案例变量 case variables此外计划项实例和人工任务也可以有自己的变量。一个案例实例可以有任意数量的变量每个变量作为ACT_RU_VARIABLE数据库表中的一行存储。createCaseInstanceBuilder方法提供了可选方法用于在通过CmmnRuntimeService创建并启动案例实例时提供变量CaseInstance caseInstance runtimeService.createCaseInstanceBuilder().variable(var1, test).start();变量也可以在案例执行过程中添加例如CmmnRuntimeServicevoid setVariables(String caseInstanceId, MapString, ? extends Object variables);变量同样可以被读取如下所示。注意CmmnTaskService上存在类似的方法MapString, Object getVariables(String caseInstanceId); Object getVariable(String caseInstanceId, String variableName);变量经常被用于 Java 服务任务、表达式、脚本等场景。五、瞬态变量Transient Variables瞬态变量行为与普通变量类似但不会被持久化。瞬态变量通常用于高级场景拿不准时请使用普通案例变量。瞬态变量的规则如下完全不保存历史瞬态变量不会存储任何历史与普通变量一样存放在最高父级在计划项上设置变量时瞬态变量实际存储在案例实例执行case instance execution上。与普通变量一样如果要在特定计划项或任务上设置变量存在local变体方法生命周期仅到下一个等待状态之前瞬态变量只能在案例定义的下一个等待状态之前被访问此后即消失。这里的等待状态指案例实例被持久化到数据存储的时间点设置与读取的对称性瞬态变量只能通过setTransientVariable(name, value)设置但调用getVariable(name)时也会返回瞬态变量同时也存在只检查瞬态变量的getTransientVariable(name)。这样设计的目的是让表达式书写更简单让使用变量的既有逻辑对两类变量一视同仁遮蔽同名持久变量瞬态变量会**遮蔽shadow**同名的持久变量。即当案例实例上同时设置了同名的持久变量和瞬态变量时调用getVariable(someVariable)返回的是瞬态变量的值。可以在大多数暴露普通变量的地方设置和获取瞬态变量PlanItemJavaDelegate实现中的DelegatePlanItemInstance上通过 runtime service 启动案例实例时完成任务时。方法遵循普通案例变量的命名约定CaseInstance caseInstance runtimeService.createCaseInstanceBuilder().transientVariable(var1, test).start();六、表达式Expressions6.1 UEL 与两种表达式类型Flowable 使用UELUnified Expression Language统一表达式语言进行表达式解析UEL 是 EE6 规范的一部分。表达式可用于例如 Java 服务任务、哨兵条件和计划项监听器。虽然表达式分为**值表达式value-expression和方法表达式method-expression**两种但 Flowable 做了抽象在期望表达式的地方两者都可以使用。值表达式解析为一个值。默认情况下所有案例变量都可用所有 Spring Bean如果使用 Spring也可在表达式中使用。非 Spring 环境下可通过CmmnEngineConfiguration的setBeans方法为表达式设置可用 Bean。例如${myVariable} ${myBean.myProperty}方法表达式调用带参数或不带参数的方法。调用无参方法时务必在方法名后加上空括号这使表达式区别于值表达式。传入的参数可以是字面值也可以是会被自行解析的表达式。例如${printer.print()} ${myBean.addNewOrder(orderName)} ${myBean.doSomething(myVar, planItemInstance)}这些表达式支持解析基本类型包括基本类型之间的比较、Bean、列表、数组和 Map。6.2 表达式中可用的默认对象除了所有案例实例变量还有可在表达式中使用的默认对象关键字说明caseInstance持有正在进行的案例实例的附加信息在所有表达式中都可用planItemInstanceDelegatePlanItemInstance持有当前计划项实例的附加信息在所有计划项相关表达式哨兵条件、计划项生命周期监听器、服务任务表达式等中可用planItemInstances暴露所有当前计划项实例的信息用法见下文示例variableContainer表达式所解析的VariableContainer。变量容器是对案例实例、计划项实例、流程实例和执行execution的抽象该关键字允许写出不绑定到特定实现的表达式authenticatedUserId当前已认证用户的 id若没有用户认证则该变量不可用示例${caseInstance.id} ${caseInstance.getVariable(myVariable) test} ${caseInstance.setVariable(myVariable, test)} ${planItemInstance.getPlanItem().getPlanItemDefinition().getName()} ${planItemInstance.getVariable(myVariable) 123} ${planItemInstance.setVariable(myVariable, 123)} ${variableContainer.getVariable(myVariable)} ${variableContainer.setVariable(myVariable, true)}6.3 planItemInstances 关键字计划项实例过滤与统计planItemInstances关键字值得特别说明。使用该关键字可以检索所有当前计划项实例同时它类似于一个 API可以检索当前计划项实例的更多信息。例如下面的表达式${planItemInstances.active().count()}返回当前处于active激活状态的所有计划项实例的计数。如果只关心某个特定计划项可以进一步过滤${planItemInstances.definitionId(a).active().count()}如示例所示planItemInstances关键字允许链式串联各种过滤方法链式串联采用AND 语义。支持的方法如下。按计划项实例状态过滤active()available()enabled()disabled()completed()terminated()以下状态过滤器也受支持但注意它们反映的是非规范合规的内部状态unavailable()waitingForRepetition()asyncActive()按终态/非终态过滤获取处于终态——即 terminated 或 completed——或非终态的计划项实例onlyTerminal()onlyNonTerminal()按 CMMN 模型中设置的标识符过滤definitionId(id1)definitionIds(id1, id2)按名称过滤name(name1)names(name1, name2, name3)使用 lambda 表达式过滤filter(planItemInstance - planItemInstance.name.startsWith(Service))按当前阶段stage过滤某些用例中应只过滤属于当前阶段的计划项实例。当前阶段是计划项的父阶段没有父阶段时则是案例实例本身。currentStage()返回结果的方法这些方法不能继续链式串联因为它们返回结果count()在应用所有过滤器后返回计划项实例的数量getDefinitionIds()返回匹配的计划项实例在 CMMN 模型中定义的所有 id 的字符串列表getDefinitionNames()返回匹配的计划项实例在 CMMN 模型中定义的所有名称列表getList()返回org.flowable.cmmn.api.runtime.PlanItemInstance实例的原始列表。6.4 综合示例统计案例实例中所有活跃计划项实例${planItemInstances.active().count()}统计 id 为 a 或 b 的所有活跃计划项实例${planItemInstances.active().definitionIds(a, b).count()}获取当前阶段中处于终态的所有计划项实例的 id${planItemInstances.currentStage().onlyTerminal().getDefinitionIds()()}将上述表达式的结果存储到瞬态变量中${caseInstance.setTransientVariable(myVar, planItemInstances.currentStage().onlyTerminal().getDefinitionIds()}七、表达式函数Expression Functions7.1 variables 命名空间的内置函数[实验性]表达式函数在 6.4.0 版本中加入。为了让案例变量操作更简单在variables命名空间下提供了一组开箱即用的函数。variables:get(varName)获取变量的值。与直接在表达式中写变量名的主要区别是使用该函数时变量不存在不会抛异常。例如${myVariable hello}在myVariable不存在时会抛异常而${var:get(myVariable) hello}可以正常工作。variables:getOrDefault(varName, defaultValue)类似get但可提供默认值当变量未设置或值为null时返回默认值。variables:exists(varName)若变量有非 null 值则返回true。variables:isEmpty(varName)别名:empty检查变量值是否为空。依变量类型不同行为如下String 变量为空字符串则视为空java.util.Collection变量集合无元素则返回trueArrayNode 变量无元素则返回true变量为null时始终返回true。variables:isNotEmpty(varName)别名:notEmptyisEmpty的逆操作。variables:equals(varName, value)别名:eq检查变量是否等于给定值。它是${execution.getVariable(varName) ! null execution.getVariable(varName) value}这种写法的简写形式。若变量值为 null返回false除非与 null 比较。variables:notEquals(varName, value)别名:neequals的反向比较。variables:contains(varName, value1, value2, …)检查变量是否包含所有传入的值。依变量类型不同行为如下String 变量传入的值作为必须成为变量一部分的子串java.util.Collection变量所有传入值都必须是集合的元素常规contains语义ArrayNode 变量支持检查数组节点是否包含作为变量类型支持的 JsonNode变量值为 null 时一律返回false变量值非 null 且实例类型不属于上述类型时返回false。variables:containsAny(varName, value1, value2, …)类似contains但只要传入值中任意一个而非全部包含在变量中即返回true。variables:base64(varName)将 Binary 或 String 变量转换为 Base64 字符串。比较函数variables:lowerThan(varName, value)别名:lessThan或:lt${execution.getVariable(varName) ! null execution.getVariable(varName) value}的简写variables:lowerThanOrEquals(varName, value)别名:lessThanOrEquals或:lte类似但为variables:greaterThan(varName, value)别名:gt类似但为variables:greaterThanOrEquals(varName, value)别名:gte类似但为。别名机制variables命名空间别名是vars或var所以variables:get(varName)等价于vars:get(varName)或var:get(varName)。注意变量名不需要加引号var:get(varName)等价于var:get(varName)或var:get(varName)。作用域自动注入上述所有函数都无需传入planItemInstance或caseInstance不使用函数时则必须传。引擎在调用函数时会自动注入适当的变量作用域。这也意味着这些函数在编写 BPMN 流程定义表达式时可以完全一样地使用。7.2 在哨兵 if-part 条件中使用变量函数这些变量函数在 CMMN 中尤其有用例如编写哨兵sentryif-part 的条件时。考虑下面的 CMMN 案例定义假设哨兵除完成事件外还有一个 if-part。案例实例刚启动时阶段变为 available该 if-part 条件就会被求值。如果条件是${someVariable someValue}这种形式意味着变量必须在启动案例实例时就可用。但很多情况下这不可行或者变量来得更晚例如来自表单这会导致底层抛出PropertyNotFoundException。考虑到变量的可空性正确的表达式原本必须写成${planItemInstance.getVariable(someVariable) ! null planItemInstance.getVariable(someVariable) someValue}这相当冗长。但使用上述函数可以简化为${var:eq(someVariable, someValue)}或${var:get(someVariable) someValue}函数实现会考虑变量的可空性变量为 null 时不抛异常并正确处理相等性比较。7.3 注册自定义表达式函数此外还可以注册可在表达式中使用的自定义函数。参考org.flowable.common.engine.api.delegate.FlowableFunctionDelegate接口。从源码 FlowableFunctionDelegate.java 可以看到该接口的核心设计是prefix()/prefixes()表达式{prefix:method()}中冒号前的部分用于将表达式文本匹配到对应的FlowableFunctionDelegate实例prefixes()默认方法允许一个实现覆盖多个前缀localName()/localNames()冒号后的方法名部分localNames()允许一个方法覆盖多个名称functionMethod()返回由 JUEL 调用的实际方法。八、历史清理History Cleaning默认情况下历史数据会永久存储这会导致历史表无限增长、影响HistoryService的性能。历史清理功能在6.5.0版本引入允许删除HistoricProcessInstances及其关联数据。一旦流程数据不再需要保留就可以删除以减小历史数据库体积对 CMMN 而言即HistoricCaseInstances及相关数据。8.1 自动历史清理配置HistoricCaseInstances的自动清理默认禁用但可以通过编程方式启用和配置。启用后默认在凌晨 1 点运行一个清理作业删除所有365 天前或更早结束的HistoricCaseInstances及其关联数据。历史流程的删除使用 FlowableBatch 机制完成通过调度作业分批删除流程并将所做工作的信息存储在 batch 表中。CmmnEngine cmmnEngine CmmnEngineConfiguration. .createProcessEngineConfigurationFromResourceDefault() .setEnableHistoryCleaning(true) .setHistoryCleaningTimeCycleConfig(0 0 1 * * ?) .setCleanInstancesEndedAfter(Duration.ofDays(365)) .buildCmmnEngine();上述配置方法在源码 CmmnEngineConfiguration.java 中均有对应实现setEnableHistoryCleaning(boolean)、setHistoryCleaningTimeCycleConfig(String)、setCleanInstancesEndedAfter(Duration)。其中setCleanInstancesEndedAfterNumberOfDays(int)已被标记为弃用deprecated建议改用setCleanInstancesEndedAfter(Duration)。Spring 环境也可以在application.properties或外部化配置中设置flowable.enable-history-cleaningtrue flowable.history-cleaning-after365d flowable.history-cleaning-cycle0 0 1 * * ?8.2 手动删除历史手动清理历史可以通过执行CmmnHistoryService查询构建器上的方法来完成。删除所有超过一年的HistoricCaseInstances及其相关数据int numberOfCasesInBatch 10; Calendar cal new GregorianCalendar(); cal.set(Calendar.YEAR, cal.get(Calendar.YEAR) - 1); cmmnHistoryService.createHistoricCaseInstanceQuery() .finishedBefore(cal.getTime()) .deleteSequentiallyUsingBatch(numberOfCasesInBatch, Custom Delete Batch);九、单元测试案例是软件项目的组成部分它们应当像普通应用逻辑一样被测试使用单元测试。由于 Flowable 是可嵌入式embeddableJava 引擎为业务案例编写单元测试就像编写普通单元测试一样简单。Flowable 支持JUnit Jupiter风格的单元测试。9.1 测试注解与扩展机制在 JUnit Jupiter 风格下需要使用org.flowable.cmmn.engine.test.FlowableCmmnTest注解或手动注册org.flowable.cmmn.engine.test.FlowableCmmnExtension。从源码 FlowableCmmnTest.java 可以看到FlowableCmmnTest是一个元注解meta-annotation它通过ExtendWith(FlowableCmmnExtension.class)完成FlowableCmmnExtension的注册该注解的Target(ElementType.TYPE)、Retention(RetentionPolicy.RUNTIME)表明它作用于类型测试类且运行时保留。关键行为这会使CmmnEngine及各服务作为参数注入到测试方法和生命周期方法BeforeAll、BeforeEach、AfterEach、AfterAll中每个测试前cmmnEngine默认使用类路径上的flowable.cmmn.cfg.xml资源初始化若要指定不同的配置文件需要使用org.flowable.cmmn.engine.test.CmmnConfigurationResource注解见下面第二个示例当配置资源相同时CMMN 引擎会在多个单元测试间静态缓存。使用FlowableCmmnExtension可以为测试方法标注org.flowable.cmmn.engine.test.CmmnDeployment当测试方法标注CmmnDeployment后每个测试前会部署CmmnDeployment#resources中定义的 cmmn 文件若未定义资源则会部署与测试类同包下、名为testClassName.testMethod.cmmn的资源文件测试结束时部署会被删除包括所有相关的案例实例、任务等。另外使用 JUnit Jupiter 还可以将部署的 idorg.flowable.cmmn.engine.test.CmmnDeploymentId注入到测试和生命周期方法中。9.2 示例一使用默认资源的 JUnit Jupiter 测试FlowableCmmnTest class MyTest { private CmmnEngine cmmnEngine; private CmmnRuntimeService cmmnRuntimeService; private CmmnTaskService cmmnTaskService; BeforeEach void setUp(CmmnEngine cmmnEngine) { this.cmmnEngine cmmnEngine; this.cmmnRuntimeService cmmnEngine.getCmmnRuntimeService(); this.cmmnTaskService cmmnEngine.getTaskRuntimeService(); } Test CmmnDeployment void testSingleHumanTask() { CaseInstance caseInstance cmmnRuntimeService.createCaseInstanceBuilder() .caseDefinitionKey(myCase) .start(); assertNotNull(caseInstance); Task task cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); assertEquals(Task 1, task.getName()); assertEquals(JohnDoe, task.getAssignee()); cmmnTaskService.complete(task.getId()); assertEquals(0, cmmnRuntimeService.createCaseInstanceQuery().count()); } }9.3 示例二使用自定义配置资源的 JUnit Jupiter 测试FlowableCmmnTest CmmnConfigurationResource(flowable.custom.cmmn.cfg.xml) class MyTest { private CmmnEngine cmmnEngine; private CmmnRuntimeService cmmnRuntimeService; private CmmnTaskService cmmnTaskService; BeforeEach void setUp(CmmnEngine cmmnEngine) { this.cmmnEngine cmmnEngine; this.cmmnRuntimeService cmmnEngine.getCmmnRuntimeService(); this.cmmnTaskService cmmnEngine.getTaskRuntimeService(); } Test CmmnDeployment void testSingleHumanTask() { CaseInstance caseInstance cmmnRuntimeService.createCaseInstanceBuilder() .caseDefinitionKey(myCase) .start(); assertNotNull(caseInstance); Task task cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); assertEquals(Task 1, task.getName()); assertEquals(JohnDoe, task.getAssignee()); cmmnTaskService.complete(task.getId()); assertEquals(0, cmmnRuntimeService.createCaseInstanceQuery().count()); } }延伸阅读CMMN 配置章节了解CmmnEngine的各种创建方式与数据库、事件监听等配置项CMMN API 接口定义CmmnRuntimeService、CmmnTaskService、CmmnRepositoryService、CmmnHistoryService、CmmnManagementService等接口的完整 JavadocCMMN 引擎实现CmmnEngine接口与其服务获取方法CMMN 引擎测试支持FlowableCmmnTest、FlowableCmmnExtension、CmmnDeployment、CmmnConfigurationResource、CmmnDeploymentId等测试注解与扩展的实现。【免费下载链接】flowable-engineA compact and highly efficient workflow and Business Process Management (BPM) platform for developers, system admins and business users.项目地址: https://gitcode.com/GitHub_Trending/fl/flowable-engine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考