工作流引擎Activiti使用进阶!详细解析工作流框架中高级功能的使用示例

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 本篇文章介绍了Activiti的几个高级用例。主要包括监听流程解析,使用UUID生成器,多租户,执行自定义的SQL,实现流程引擎配置,安全的BPMN 2.0结构以及事件日志的使用。使用这些高级功能,可以使得集成工作流Activiti的项目具有更多的可操作性。

Activiti高级功能简介

  • Activiti的高级用例,会超越BPMN 2.0流程的范畴,使用Activiti高级功能需要有Activiti开发的明确目标和足够的Activiti开发经验

监听流程解析

  • bpmn 2.0 xml文件需要被解析为Activiti内部模型,然后才能在Activiti引擎中运行.解析过程发生在发布流程或在内存中找不到对应流程的时候,这时会从数据库查询对应的xml
  • 对于每个流程 ,BpmnParser类都会创建一个新的BpmnParse实例.这个实例会作为解析过程中的容器来使用
  • 解析过程:

    • 对于每个BPMN 2.0元素,引擎中都会有一个对应的org.activiti.engine.parse.BpmnParseHandler实例
    • 解析器会保存一个BPMN 2.0元素与BpmnParseHandler实例的映射
    • 默认Activiti使用BpmnParseHandler来处理所有支持的元素
    • 同时也使用BpmnParseHandler来提供执行监听器,以支持流程历史
  • 可以向Activiti引擎中添加自定义的org.activiti.engine.parse.BpmnParseHandler实例
  • 经常使用的用例是把执行监听器添加到对应的环节,来处理一些事件队列.Activiti在内部就是这样进行历史处理的
  • 要想添加这样的自定义处理器,需要为Activit增加配置:
<property name="preBpmnParseHandlers">
  <list>
    <bean class="org.activiti.parsing.MyFirstBpmnParseHandler" />
  </list>
</property>

<property name="postBpmnParseHandlers">
  <list>
    <bean class="org.activiti.parsing.MySecondBpmnParseHandler" />
    <bean class="org.activiti.parsing.MyThirdBpmnParseHandler" />
  </list>
</property>
  • 当自定义处理器内部逻辑对处理顺序有要求时需要考虑:

    • 配置到preBpmnParseHandlers的BpmnParseHandler实例会添加在默认处理器的前面
    • 配置到postBpmnParseHandlers的BpmnParseHandler实例会添加在默认处理器的后面
  • 接口- org.activiti.engine.parse.BpmnParseHandler:
public interface BpmnParseHandler {

  Collection<Class>? extends BaseElement>> getHandledTypes();

  void parse(BpmnParse bpmnParse, BaseElement element);

}
  • BpmnParseHandler接口中:

    • getHandledTypes()方法会翻译这个解析器处理的所有类型的集合,这些都是BaseElement的子类,返回集合的泛型限制也说明了这一点
    • 也可以继承AbstractBpmnParseHandler类并重写getHandledType()方法,这样就只需要返回一个类型,而不是一个集合
    • 这个类也包含需要默认解析处理器所需要的方法
    • BpmnParseHandler实例只有在解析器访问到这个方法返回的类型时才会被调用
  • 示例:

    • BPMN 2.0 xml包含process元素时,就会执行executeParse方法中的逻辑
    • 这是一个已经完成类型转换的方法,替换BpmnParseHandler接口中的parse方法
public class TestBPMNParseHandler extends AbstractBpmnParseHandler<Process> {

  protected Class<? extends BaseElement> getHandledType() {
    return Process.class;
  }

  protected void executeParse(BpmnParse bpmnParse, Process element) {
     ..
  }

}
  • ==注意:==

    • 在编写自定义解析处理器时,不要使用任何解析BPMN 2.0结构的内部类,这会导致出现问题很难定义
    • 应该实现BpmnParseHandler接口或集成内部抽象类 org.activiti.engine.impl.bpmn.parser.handler.AbstractBpmnParseHandler
    • 也可以替换默认的BpmnParseHandler实例,把解析BPMN 2.0元素替换为解析Activiti内部模型:
<property name="customDefaultBpmnParseHandlers">
  <list>
    ...
  </list>
</property>
  • 示例: 将所有任务强制设置为异步
public class CustomUserTaskBpmnParseHandler extends ServiceTaskParseHandler {

  protected void executeParse(BpmnParse bpmnParse, ServiceTask serviceTask) {

    // Do the regular stuff
    super.executeParse(bpmnParse, serviceTask);

    // Make always async
    ActivityImpl activity = findActivity(bpmnParse, serviceTask.getId());
    activity.setAsync(true);
  }

}

支持高并发的UUID的ID生成器

  • 在高并发的场景中,默认的ID生成器可能因为无法很快的获取新ID区域而导致异常
  • 所有流程引擎都有一个ID生成器,默认的ID生成器会在数据库划取一块ID范围,其余引擎不能使用相同范围的ID
  • 在引擎运行期间,当默认的ID生成器发现已经越过ID范围时,就会启动一个新事务来获得新范围.在极限的情况下,高负载会导致问题
  • 对于大部分情况,默认ID生成已经足够:

    • 默认的org.activiti.engine.impl.db.DbIdGenerator有一个idBlockSize属性,可以配置获取ID范围的大小,这样就可以改变获取ID的行为
    • 另一个可以选用的默认ID生成器是org.activiti.engine.impl.persistence.StrongUuidGenerator:

      • 会在本地生成一个唯一的UUID作为所有实体的标识
      • 因为生成UUID不需要访问数据库,所以在高并发环境下的表现比较好
    • 默认ID生成器的性能依赖于运行硬件
  • 将UUID生成器配置到Activiti:
<property name="idGenerator">
    <bean class="org.activiti.engine.impl.persistence.StrongUuidGenerator" />
</property>
  • 使用UUID生成器需要添加依赖:
 <dependency>
    <groupId>com.fasterxml.uuid</groupId>
    <artifactId>java-uuid-generator</artifactId>
    <version>3.1.3</version>
</dependency>

多租户

  • 多租户:

    • 通常是在软件需要作为多个不同组织服务时产生的概念
    • 关键是数据分片,组织不能看到其余组织的数据
    • 在这种场景下,组织,部门,小组就叫做租户
  • 多租户和安装多个实例是从基本上不同的:

    • 多租户是一个Activiti流程引擎实例为每个组织分别运行,对应不同的数据表
    • 安装多个Activiti流程引擎实例时,虽然Activiti是轻量级的,运行流程引擎不会消耗很多资源,但是增加了复杂性,并需要更多维护工作.然而对于一些场景,也是正确的解决方案
  • Activiti的多租户主要围绕着数据分片来实现:

    • Activiti没有强行校验多租户的规则,即Activiti不会校验查询和使用数据时用户是否使用了正确的租户
    • 校验由Activiti引擎的调用者层负责完成
    • Activiti只确认租户信息会被保存,并在查询流程数据时会被用到
  • 在向Activiti流程引擎发布流程定义时,需要传递一个租户标识.是一个字符串,限制在256字符内,作为租户的唯一标识
 repositoryService.createDeployment()
            .addClassPathResource(...)
            .tenantId("myTenantId")
            .deploy();
  • 通过部署传递租户Id有以下作用:

    • 所有包含在部署中的流程定义都会继承部署的tenantId
    • 所有从这些流程定义发起的流程实例,都会继承流程定义的tenantId
    • 所有流程实例运行阶段创建的任务都会继承流程实例的tenantId.单独运行的task也可以包含tenantId
    • 所有流程实例运行阶段创建的分支都会继承流程实例的tenantId
    • 在流程本身或通过API触发一个信号抛出事件可以通过tenantId实现.信号只会在租户环境下执行:如果有多个信号捕获事件,并且名字相同,实际只有正确的tenantId下的事件会被调用
    • 所有作业(定时器,异步调用)会集成tenantId,或者来自流程定义(比如定时开始事件),或流程实例(运行期创建的作业,比如异步调用).这样其实潜在的可以支持为一些租户指定不同优先级的自定义jobExecutor
    • 所有历史实体(历史流程实例,任务和节点)会从对应的运行状态集成tenantId
    • 作为单独的一部分,model也可以设置tenantId.这里的model用来存储Activiti modeler设计的bpmn 2.0模型
  • 为了确保流程数据使用tenantId,所有的查询API都可以通过tenantId进行查询,可以使用其他的实体的对应查询实现替换:
runtimeService.createProcessInstanceQuery()
    .processInstanceTenantId("myTenantId")
    .processDefinitionKey("myProcessDefinitionKey")
    .variableValueEquals("myVar", "someValue")
    .list()

查询API也允许对tenantId使用like语法, 也可以过滤未设置tenantId的实体

  • 重要注意点:

    • 因为数据库的限制,特别是处理null的唯一校验.默认表示未设置租户的tenantId的值是空字符串
    • 流程定义key,流程定义version,tenantId的组合应该是唯一的,这个有数据库约束校验这个规则
    • 要注意tenantId不应设置为null,会影响一些数据库Oracle的查询,会把空字符串当做null处理
    • 这也是为什么withoutTenantId查询会检查空字符串或null.这意味着相同的流程定义,即流程定义key相同可以部署到不同的租户下,可以拥有各自的版本.当不使用租户时也不会影响使用
  • 这些限制不会影响Activiti在集群环境下运行
  • 可以通过调用repositoryServicechangeDeploymentTenantId(String deploymentId, String newTenantId) 修改tenantId. 会修改之前继承的所有tenantId. 当需要从非多租户环境向多租户环境下切换时,会非常实用

执行自定义SQL

  • Activiti API允许使用高级API操作数据库:

    • 在查询数据方面,查询API和Native Query API是非常强大的
    • 但是对于某些情况,不够轻便
    • 使用完全自定义的SQL语句:select, insert, update和delete.可以执行在Activiti的数据存储之上,但是完全又可以配置在流程引擎中:比如使用事务
  • 为了使用自定义SQL,Activiti引擎使用MyBatis框架的功能:

    • 因此使用自定义SQL的第一件事,要创建MyBatis映射类
    • 假设不需要全部的任务数据,只需要其中的一小部分.可以使用Mapper实现:
public interface MyTestMapper {

    @Select("SELECT ID_ as id, NAME_ as name, CREATE_TIME_ as createTime FROM ACT_RU_TASK")
    List<Map<String, Object>> selectTasks();

}
  • Mapper需要设置到流程引擎配置中:
...
<property name="customMybatisMappers">
  <set>
    <value>org.activiti.standalone.cfg.MyTestMapper</value>
  </set>
</property>
...
  • 这个Mapper是一个接口:

    • MyBatis框架会在运行阶段为这个接口创建一个实例
    • 返回值是没有类型的,是一个map的list,和对应的行列对应
    • 如果需要也可以使用MyBatis映射
  • 执行上面的查询:

    • 可以使用managementService.executeCustomSql方法
    • 这个方法需要一个CustomSqlExecution实体
    • 这个实体类是一个封装类,隐藏了引擎的内部实现所需执行的信息
    • 但是由于Java泛型,查询返回的结果可读性差
  • 示例:

    • mapper类和返回类型类
    • 简单调用mapper方法 并返回结果
CustomSqlExecution<MyTestMapper, List<Map<String, Object>>> customSqlExecution =
          new AbstractCustomSqlExecution<MyTestMapper, List<Map<String, Object>>>(MyTestMapper.class) {

  public List<Map<String, Object>> execute(MyTestMapper customMapper) {
    return customMapper.selectTasks();
  }

};

List<Map<String, Object>> results = managementService.executeCustomSql(customSqlExecution);

list中的Map只包含id,name和create time, 不是全部的任务对象

  • 可以通过这样的方式执行任意SQL:
 @Select({
        "SELECT task.ID_ as taskId, variable.LONG_ as variableValue FROM ACT_RU_VARIABLE variable",
        "inner join ACT_RU_TASK task on variable.TASK_ID_ = task.ID_",
        "where variable.NAME_ = #{variableName}"
    })
    List<Map<String, Object>> selectTaskWithSpecificVariable(String variableName);

使用这种方法,任务表会与变量表关联.只会获得对应名称的变量,任务Id和对应的数值会被返回

使用ProcessEngineConfigurator实现流程引擎配置

  • 可以使用ProcessEngineConfigurator实现一种高级的扩展流程引擎的配置:

    • 创建一个org.activiti.engine.cfg.ProcessEngineConfigurator接口的实现
    • 注入到流程引擎配置里
<bean id="processEngineConfiguration" class="...SomeProcessEngineConfigurationClass">

    ...

    <property name="configurators">
        <list>
            <bean class="com.mycompany.MyConfigurator">
                ...
            </bean>
        </list>
    </property>

    ...

</bean>
  • 实现ProcessEngineConfigurator接口需要实现两个方法:

    • configure: 将ProcessEngineConfiguration作为参数,可以通过这种方法添加自定义配置,这个方法在流程创建之前,在所有默认配置执行之前保证调用到
    • getPriority: 如果一些configurator存在依赖项的时候,允许对configurator进行排序
  • configurator实例:

    • LDAP集成:

      • 这个configurator用来替换默认的usergroup管理器类,使用处理LDAP用户存储的类
      • 基本上一个configurator允许很大程度上修改或增强流程引擎,对高级的场景非常有用
    • 使用自定义的版本替换流程定义缓存, 如下:
public class ProcessDefinitionCacheConfigurator extends AbstractProcessEngineConfigurator {

    public void configure(ProcessEngineConfigurationImpl processEngineConfiguration) {
            MyCache myCache = new MyCache();
            processEngineConfiguration.setProcessDefinitionCache(enterpriseProcessDefinitionCache);
    }

}
  • 流程引擎配置器也可以通过ServiceLoader自动从classpath中加载:

    • 放在jar中的configurator实现必须放在classpath
    • 并在jarMETA-INF/services目录下包含一个org.activiti.engine.cfg.ProcessEngineConfigurator文件
    • 文件的内容是自定义实现的全类名
    • 当流程引擎启动时,日志会显示找到了哪些configurator
INFO  org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl  - Found 1 auto-discoverable Process Engine Configurators
INFO  org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl  - Found 1 Process Engine Configurators in total:
INFO  org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl  - class org.activiti.MyCustomConfigurator
  • 这种ServiceLoader的方式在某些环境下可能无法正常运行.使用ProcessEngineConfigurationenableConfiguratorServiceLoader属性来禁用这个功能,这个属性的默认值为true

启动安全BPMN 2.0xml

  • 大多数情况下,BPMN 2.0流程发布到Activiti引擎是在严格的控制下的
  • 然而在某些情况下,可能需要把比较随意的BPMN 2.0 xml上传到引擎,这时就要要考虑恶意用户会攻击服务器
  • 为了避免BPMN 2.0xml引擎服务器受到攻击,可以在引擎中设置enableSafeBpmnXml:
<property name="enableSafeBpmnXml" value="true"/>
  • 默认这个功能没有开启.因为这个功能需要使用StaxSource
  • 由于JDK6,JBoss使用的是旧版的xml解析实现,无法使用StaxSource类,所以不能启用安全的BPMN 2.0xml
  • 如果Activiti运行的平台支持安全的BPMN 2.0xml功能,建议打开

事件日志

  • 在Activiti 5.16版本中,添加了事件日志机制:

    • 这种日志机制构建在通用目的下的Activiti引擎的事件机制,默认是禁用的
    • 目的是由引擎产生的事件会被捕获,包含所有事件数据的map会被创建出来,并提供给org.activiti.engine.impl.event.logger.EventFlusher, 会把数据刷新到别的地方
    • 默认会使用一个简单地基于数据库的事件处理器或者叫作刷新器,会使用jacksonmap转换为JSON, 并保存到数据库中的EventLogEntryEntity实体
    • 默认会创建数据库日志表ACT_EVT_LOG. 如果没有使用事件日志,可以删除这个表
  • 启用数据库日志:
processEngineConfiguration.setEnableDatabaseEventLogging(true);

或者在流程引擎运行阶段:

databaseEventLogger = new EventLogger(processEngineConfiguration.getClock());
runtimeService.addEventListener(databaseEventLogger);
  • EventLogger类可以继承:

    • 在需要使用自定义的数据日志时:

      • createEventFlusher() 方法需要返回一个org.activiti.engine.impl.event.logger.EventFlusher接口的实例
      • managementService.getEventLogEntries(startLogNr, size) 可以获取Actviti的EventLogEntryEntity实例
  • 可以使用大数据的NoSQL存储: MongoDb,Elastic Search等等来存储JSON。
  • 使用的类是可插拔的: org.activiti.engine.impl.event.logger.EventLogger/EventFlusher和很多EventHandler
  • 可以切换成自定义应用场景: 不在数据库中存储JSON,而是放到队列或大数据存储中
  • ==注意:==

    • 事件日志机制是Activiti传统历史管理器的附加品
    • 虽然所有数据都在数据库表中,但是并没有为查询优化,不容易获取
    • 真实的使用场景:

      • 审计跟踪
      • 将事件日志数据放到大数据存储中
相关文章
|
1月前
|
监控
新功能上线:云解析DNS-重点域名监控功能发布
新功能上线:云解析DNS-重点域名监控功能发布
|
8月前
|
弹性计算 运维 安全
优化管理与服务:操作系统控制平台的订阅功能解析
本文介绍了如何通过操作系统控制平台提升系统效率,优化资源利用。首先,通过阿里云官方平台开通服务并安装SysOM组件,体验操作系统控制平台的功能。接着,详细讲解了订阅管理功能,包括创建订阅、查看和管理ECS实例的私有YUM仓库权限。订阅私有YUM仓库能够集中管理软件包版本、提升安全性,并提供灵活的配置选项。最后总结指出,使用阿里云的订阅和私有YUM仓库功能,可以提高系统可靠性和运维效率,确保业务顺畅运行。
|
7月前
|
存储 前端开发 JavaScript
调用DeepSeek API增强版纯前端实现方案,支持文件上传和内容解析功能
本方案基于DeepSeek API增强版,提供纯前端实现的文件上传与内容解析功能。通过HTML和JavaScript,用户可选择文件并调用API完成上传及解析操作。方案支持多种文件格式(如PDF、TXT、DOCX),具备简化架构、提高响应速度和增强安全性等优势。示例代码展示了文件上传、内容解析及结果展示的完整流程,适合快速构建高效Web应用。开发者可根据需求扩展功能,满足多样化场景要求。
2505 64
|
8月前
|
人工智能 API 开发者
HarmonyOS Next~鸿蒙应用框架开发实战:Ability Kit与Accessibility Kit深度解析
本书深入解析HarmonyOS应用框架开发,聚焦Ability Kit与Accessibility Kit两大核心组件。Ability Kit通过FA/PA双引擎架构实现跨设备协同,支持分布式能力开发;Accessibility Kit提供无障碍服务构建方案,优化用户体验。内容涵盖设计理念、实践案例、调试优化及未来演进方向,助力开发者打造高效、包容的分布式应用,体现HarmonyOS生态价值。
493 27
|
8月前
|
人工智能 API 语音技术
HarmonyOS Next~鸿蒙AI功能开发:Core Speech Kit与Core Vision Kit的技术解析与实践
本文深入解析鸿蒙操作系统(HarmonyOS)中的Core Speech Kit与Core Vision Kit,探讨其在AI功能开发中的核心能力与实践方法。Core Speech Kit聚焦语音交互,提供语音识别、合成等功能,支持多场景应用;Core Vision Kit专注视觉处理,涵盖人脸检测、OCR等技术。文章还分析了两者的协同应用及生态发展趋势,展望未来AI技术与鸿蒙系统结合带来的智能交互新阶段。
531 31
|
8月前
|
供应链 监控 搜索推荐
反向海淘代购独立站:功能解析与搭建指南
“反向海淘”指海外消费者购买中国商品的现象,体现了中国制造的创新与强大。国产商品凭借高性价比和丰富功能,在全球市场备受欢迎。跨境电商平台的兴起为“反向海淘”提供了桥梁,而独立站因其自主权和品牌溢价能力逐渐成为趋势。一个成功的反向海淘代购独立站需具备多语言支持、多币种支付、物流跟踪、商品展示、购物车管理等功能,并通过SEO优化、社交媒体营销等手段提升运营效果。这不仅助力中国企业开拓海外市场,还推动了品牌全球化进程。
251 19
|
8月前
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
806 29
|
8月前
|
人工智能 自然语言处理 搜索推荐
ViDoRAG:开源多模态文档检索框架,多智能体推理+图文理解精准解析文档
ViDoRAG 是阿里巴巴通义实验室联合中国科学技术大学和上海交通大学推出的视觉文档检索增强生成框架,基于多智能体协作和动态迭代推理,显著提升复杂视觉文档的检索和生成效率。
509 8
ViDoRAG:开源多模态文档检索框架,多智能体推理+图文理解精准解析文档
|
8月前
|
SQL 运维 监控
高效定位 Go 应用问题:Go 可观测性功能深度解析
为进一步赋能用户在复杂场景下快速定位与解决问题,我们结合近期发布的一系列全新功能,精心梳理了一套从接入到问题发现、再到问题排查与精准定位的最佳实践指南。
|
8月前
|
机器学习/深度学习 人工智能 Java
Java机器学习实战:基于DJL框架的手写数字识别全解析
在人工智能蓬勃发展的今天,Python凭借丰富的生态库(如TensorFlow、PyTorch)成为AI开发的首选语言。但Java作为企业级应用的基石,其在生产环境部署、性能优化和工程化方面的优势不容忽视。DJL(Deep Java Library)的出现完美填补了Java在深度学习领域的空白,它提供了一套统一的API,允许开发者无缝对接主流深度学习框架,将AI模型高效部署到Java生态中。本文将通过手写数字识别的完整流程,深入解析DJL框架的核心机制与应用实践。
494 3

推荐镜像

更多
  • DNS