每隔一阵子就会有人问我:公司要做审批流,Activiti和Flowable到底选哪个?最近我刚帮一个客户做完流程引擎的迁移评估,把这个问题从头到尾又捋了一遍。这两个引擎同根同源,但在API设计、模块拆分、社区维护、授权模式上的差异比很多人想象中更大。这篇文章不打算做成“谁更好”的口水仗,而是结合业务选型视角,把我在调研、集成、迁移和排障过程中总结的对比思路和实操经验写下来,给正在纠结的你一份可以直接抄作业的参考。
先放结论:没有通用的最优解,只有匹配你团队和业务的合适解。下面从项目起源、代码细节、Spring Boot集成、场景匹配、常见坑五个维度展开。
1. 先搞清楚:Activiti和Flowable到底什么关系
1.1 一个“分家”故事
要理解选型,得先理解这两个项目的由来。Activiti 5是Tijs Rademakers主导开发的开源工作流引擎,当年非常火,市面上很多老系统的审批模块都是基于Activiti 5做的。后来这个项目被Alfresco收购,团队在开发Activiti 6时对产品定位产生了重大分歧,核心成员在2016年左右陆续离开,基于Activiti的分支创建了Flowable。
你可以把这个过程理解成一家餐厅换了老板,主厨不满后离职开了新店,菜单大部分相似,但后厨的管理方式和菜品的扩展思路已经完全不一样了。这也是为什么你现在看Activiti和Flowable,表名还是那些ACT_开头,核心API也似曾相识,但仔细用起来发现扩展思路差得很远。
我最早接触的是Activiti 5.16版本,那时部署一个流程要走一堆XML配置,BPMN文件手敲都嫌慢。Activiti 6是Alfresco主导的大改版,引入了更强的模块化思路,但说实话社区里的声音已经被Flowable分走了大半。
1.2 两个项目的版本与现状
从版本演进看,两个项目现在都还在活跃维护,但节奏和方向有明显差异:
| 维度 | Activiti | Flowable |
|---|---|---|
| 诞生基础 | 老牌开源项目,被Alfresco收购后演进 | 2016年从Activiti分支fork出来 |
| 当前主流版本 | Activiti 7.x系列 | Flowable 6.x、7.x |
| 维护方 | Activiti社区,更新节奏相对平缓 | Flowable Inc主导,迭代更快 |
| 核心定位 | 云原生、事件驱动,强绑定Spring Boot生态 | 模块化引擎,覆盖BPMN、CMMN、DMN |
| 开源协议 | Apache 2.0 | 6.8之前Apache 2.0,6.8后社区版转EPL 1.0 |
| 商业支持 | 无官方商业版(靠第三方) | 有商业版和商业授权 |
给你一个直观体感:Activiti的演进更偏“框架化”,Flowable更偏“引擎家族”。Flowable官方把BPMN、CMMN(案例管理)、DMN(决策表)、Form表单引擎拆成独立模块,你可以只引入自己需要的部分;Activiti虽然也在做模块化,但整体上更像一个紧耦合的流程引擎加Spring Boot适配层。
2. 代码与数据层面的真实差异
2.1 API风格与扩展机制:Flowable更“大方”
两个引擎都基于BPMN 2.0规范,任务节点、网关、事件模型基本一致,核心API形式上也很像。但当你需要做深度定制时,差异就出来了。
Activiti的API设计相对内敛,很多内部实现默认不对外开放。你想自定义一个命令或者对任务操作做拦截,需要费不少功夫去实现自定义Command类,然后调用managementService.executeCommand()绕进去。这种方式能跑,但维护成本偏高。
Flowable的API明显更“慷慨”。它保留了Activiti的命令模式和拦截器框架,同时把很多常用高级操作直接封装成Builder模式。比如热词里常提到的追回和驳回到上一节点,Flowable原生提供了状态迁移的API:
runtimeService.createChangeActivityStateBuilder() .processInstanceId(processInstanceId) .moveActivityIdTo("currentTask", "targetTask") .changeState();而Activiti没有直接对应的“改节点”API,要自己写命令类,或者靠流程变量加网关分支来模拟。两种方法都能实现,但开发量和排错复杂度不是一个量级。
还有个比较关键的点:Flowable允许你在全局事件监听器上注册很多种事件类型,包括任务创建、任务完成、流程结束、活动开始、活动结束等;Activiti对事件监听也支持,但粒度相对粗。我做过一个项目需要在任务每次被代理或转办时记录操作日志,Flowable几行代码就能解决,Activiti则要额外接任务监听器或者自己包一层Service。
2.2 表结构与历史数据处理
两者底层的表结构有很多共同祖先,ACT_RU_*运行时表、ACT_HI_*历史表、ACT_GE_*通用表的命名前缀完全一致。但Flowable在演进过程中加了大量自己的表,比如:
ACT_CMN_*:CMMN案例管理相关表ACT_DMN_*:DMN决策表相关表ACT_CO_*:内容模块相关表ACT_HI_TSK_LOG:更细致的任务历史日志
如果你只做纯审批流,基础表差异感知不强。但如果你需要做流程数据统计、历史轨迹回放、或者把流程引擎嵌入到低代码平台里,Flowable的历史数据维度明显更丰富。举例来说,Flowable在ACT_HI_ACTINST里会记录每个BPMN活动节点的耗时和状态,这对做“节点平均处理时长”报表很有帮助;Activiti也有类似记录,但在子流程实例和调用活动(CallActivity)的追溯深度上不如Flowable。
还有一点值得提:Flowable的Async Executor和Activiti的Async Job机制实现完全不同。Flowable把定时任务和异步任务统一纳入自己的执行器管理,任务表里的重试机制和死信处理更透明;Activiti在7.x里也做了大量异步增强,但默认配置下定时器扫描的阻塞问题更容易出现。
2.3 追回、撤回这类“反向操作”谁更顺手
这个话题最近被问得很多。传统审批系统里,“提交后发现自己填错了”和“审批人打回”是两个高频场景,统称“追回”。
实现追回的本质是让流程实例从当前活动节点跳转到目标活动节点,同时处理好历史记录。Activiti常见的做法有两种:一是流程变量加网关分支,适合简单的单向退回;二是自定义Command,通过ChangeActivityStateBuilder类似思路但需要自己拼装,我见过不少团队封装得不彻底,退回去之后历史表数据错乱,流程实例直接“卡死”。
Flowable把这套跳转逻辑做进了官方RuntimeService,还支持迁移动态并跟踪状态差异。同时它还提供了moveExecutionToActivityIdBuilder()这种按执行实例跳转的API,在并行网关、子流程场景下更好用。
实操心得:不管用哪个引擎,追回后要特别关注并行网关。并行分支合流时,如果有一个分支的活动实例还挂在运行表里,你就算把主流程退回了,残留的执行分支也可能导致后续流程激活异常。所以做追回功能时别只调API,还得配合activeActivityIds()查看当前活动节点,避免脏数据残留。
3. Spring Boot集成实操与UI方案
3.1 版本匹配:javax与jakarta这堵墙
现在大量项目都是Spring Boot工程,集成方式直接决定选型体验。最容易被坑的是Java生态从javax.*迁移到jakarta.*的版本断层。
我画个简表方便你对照:
| 引擎版本 | 依赖包名 | 适用的Spring Boot | 备注 |
|---|---|---|---|
| Activiti 7.1.0.M6 | activiti-spring-boot-starter | Spring Boot 2.x | 老项目常用,API稳定 |
| Activiti 7.7+ | 同上 | 可兼容Boot 2,云原生需另配 | 社区更新渐缓 |
| Flowable 6.7.2 | flowable-spring-boot-starter | Spring Boot 2.x | 大量生产项目在用 |
| Flowable 7.x | flowable-spring-boot-starter | Spring Boot 3.x | 需要JDK17+,jakarta命名空间 |
如果你公司技术栈还停留在Spring Boot 2.x + JDK8,老老实实选Activiti 7.1或Flowable 6.7.2都行,别为了追新硬上Flowable 7,否则jakarta.servlet和原来javax.servlet的包冲突能折腾你一下午。
Flowable 6.7.2的典型依赖配置:
<dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter</artifactId> <version>6.7.2</version> </dependency>Activiti 7.1.0.M6的典型依赖配置:
<dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring-boot-starter</artifactId> <version>7.1.0.M6</version> </dependency>从“开箱即用”的角度,Flowable的starter做得更省心,默认配置下启动会自动建表,还能根据classpath下的processes目录自动部署BPMN文件。Activiti 7也支持自动建表,但对ProcessEngineConfiguration的默认策略更偏保守,部分老版本需要显式配置databaseSchemaUpdate。
3.2 老面孔的集成:JeecgBoot为什么默认选了Flowable
热词里提到jeecgboot集成flowable,这不是偶然。JeecgBoot是国内接触度很高的快速开发平台,早期的流程方案用过Activiti,但新版本基本默认走Flowable路线。原因很简单:JeecgBoot本身是低代码式快速开发框架,对流程模块的诉求是能快速扩展动态表单、按钮权限和流程状态联动,Flowable的模块化生态更契合。
如果你是在类似JeecgBoot这类框架上二次开发,需要注意三个点:
第一,数据源配置里一定要带上数据库的时区和编码参数,否则跑一批初始化脚本就报错。比如MySQL:
spring: datasource: url: jdbc:mysql://localhost:3306/flowable_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai flowable: database-schema-update: true async-executor-activate: true process-definition-location-prefix: classpath:/processes/第二,process-definition-location-prefix要指向存放BPMN文件的目录,项目启动时引擎会把该目录下的流程定义自动部署。很多团队把BPMN文件随手丢在resource根目录,结果引擎一个都没扫到,还以为是代码问题。
第三,Flowable在用户体系上不强制接管,你可以用现有的act_id_user表,也可以通过IdmIdentityService接口适配自己的用户表。在JeecgBoot这种自带用户权限体系的框架里,建议别直接用Flowable的默认用户管理,否则一套系统两套用户中心,后面维护非常痛苦。
3.3 前后端UI:模型器能直接裸用吗
很多刚接触的人看到Flowable有官方UI模块flowable-ui,Activiti也有历史遗留的activiti-app,以为能直接把流程设计器嵌入到业务系统里。我的建议是:千万别裸用,只建议做开发辅助工具。
原因很简单:官方建模器是面向“流程管理员”的,而你的产品面向的是“业务用户”,交互习惯、权限粒度、表单绑定逻辑完全不同。生产环境里,流程设计图通常是管理员在后台维护,业务人员只在前端发起审批和查看任务,两者天然应该拆开。
我在实际项目中更推荐的做法是:后端只暴露流程引擎的REST接口或者封装Service层,前端流程设计器用bpmn-js或AntV X6自建,审批工作台结合自己的业务表单体系做。如果项目紧急必须马上看到效果,先接flowable-ui-rest用来做模型预览和调试,但不要让它和外网业务页面共享会话,这里面的接口权限太粗,直接暴露会有安全风险。
4. 面向业务的选型决策框架
4.1 四类典型业务场景,直接对照选
与其背参数对比,不如对照你们自己要做的业务形态。我把常见场景分成四类:
| 业务形态 | 推荐引擎 | 原因 |
|---|---|---|
| 中小企业审批流(报销、请假、公文) | Activiti | 节点少、路由简单,两个引擎都能覆盖,选团队熟悉度更高的 |
| 复杂业务工单、订单状态机、多分支会签 | Flowable | 历史数据更细,状态迁移API完整,复杂跳转维护成本低 |
| 云原生微服务架构、事件驱动集成 | Activiti 7 | 对Spring Cloud和事件网关的适配更好,云原生定位更明确 |
| 低代码平台、需要内置动态表单和决策表 | Flowable | 模块拆分细,CMMN/DMN/Form能力齐全,更适配低代码底座 |
重点说一下第二类。如果你要实现的不是单纯审批,而是类似“工单流转+状态条件分支+跨部门会签+超时自动升级”的复合流程,Flowable的开发效率会明显高一些。我在一个客服工单项目里用Flowable,节点间的条件流转配置只花了一个迭代;换成Activiti的话,大量条件路由逻辑得靠流程变量和ServiceTask硬编码,后续改动节点还要同步改代码,维护压力会大不少。
4.2 团队技术栈与License成本
技术选型最怕只从“技术牛不牛”出发,忘了考虑团队能不能接住。
从学习曲线看,两者核心API相似,有BPMN基础的人切换成本都不高。但差异在于:Flowable的官方文档和样例工程更多,特别是针对Spring Boot的快速入门写得很细;Activiti 7的文档相对散,很多场景要靠社区帖子拼答案。如果团队以初中级开发为主,Flowable的学习路径更平滑。
从License成本看,这直接影响商业产品落地。Activiti 7基于Apache 2.0协议,商用自由度高,不强制开源;Flowable在6.8版本之前也是Apache 2.0,但6.8之后社区版转成了EPL 1.0,简单理解就是商用没问题,但对修改过的代码可能存在开源传递要求,而且如果要用高级特性官方会引导你购买商业版。
这里我不展开具体法务判断,只提醒一句:如果你的项目是要交付给甲方的商业软件,选型时务必让法务确认License改动对你们的影响,别等到合同都签了才发现授权模型不匹配。
4.3 我常用的三问判断法
很多人问我有没有万能的“选型公式”,我总结了一套三问判断法,实测下来还比较准:
- 你们的核心场景是不是只有审批?如果是,用Activiti就足够了,别因为追求热门把技术栈搞得越来越重。
- 流程里有没有大量动态条件、并行分支、状态回退、会签加签这类复杂操作?如果有,倾向Flowable,它的条件表达式和状态迁移API能省很多事。
- 未来架构是不是重度依赖Spring Cloud和微服务事件流?如果是,重点看Activiti 7的云原生能力;如果在可预见的未来仍然是单体应用加关系型数据库,选Flowable更稳。
这套判断法本质是在“开发成本、维护成本、生态匹配”之间做取舍。技术选型不是性能竞赛,而是看谁能让团队长期维护不痛苦。
5. 常见问题与排查技巧实录
5.1 启动报错的三个高频场景
不管集成哪个引擎,我遇到最多的启动报错集中在下面三个场景:
第一个是Could not create Driver或时区错误。原因通常是数据源URL没加serverTimezone,或者MySQL驱动版本太老。解决方法是在JDBC URL里显式指定serverTimezone=Asia/Shanghai,并把驱动升级到与数据库版本匹配的版本。
第二个是Could not autowire ProcessEngine。仔细观察通常是spring boot starter版本和项目里的其他依赖冲突,比如Activiti和Flowable同时存在,或者引入了多个流程引擎starter。我在一个老项目里发现pom里同时挂着activiti-spring-boot-starter和flowable-spring-boot-starter的传递依赖,启动后Bean冲突直接起不来,清理掉其中一个就好。
第三个是Could not deploy BPMN,类路径下找不到流程文件。这时先检查BPMN文件是否在你配置的resourcePrefix目录里,再看下文件有没有被Maven打包排掉,最后确认BPMN文件中的xmlns命名空间是否合法。很多坑不是代码问题,是文件压根没进classpath。
5.2 任务卡住与“追回”翻车实录
任务不流转是流程引擎最让人头疼的问题。我把它分成两类来排查。
一类是流程不自动推进。看到任务长时间停在某个节点,先查ACT_RU_JOB表,重点看有没有定时任务或异步任务状态异常:
SELECT * FROM act_ru_job WHERE handler_type = 'timer' AND retries = 0;如果这里出现多行重试为0的记录,基本可以判断是异步执行器没启动,或线程被一直阻塞。Flowable里确认配置async-executor-activate: true,Activiti里确认asyncExecutorActivate参数开启。生产环境我建议禁用数据库自动升级,但异步执行器一定要常开。
另一类是“追回”之后流程死冲突。有一次我们把流程从审批节点回退到发起节点,调整后重新提交,结果并行网关合并时一直报“can't find active execution”。排查最有效的方法是先看当前活动实例:
List<String> activeActivityIds = runtimeService.getActiveActivityIds(processInstanceId);发现并行分支里有一条执行实例没有正常销毁,导致重新进入时匹配不上。解决办法是在追回之前,用runtimeService.createChangeActivityStateBuilder()把目标并行分支一并迁移,而不是只移动主活动节点。这个坑属于典型的API用对了但理解不深,记录在案。
5.3 条件表达式与网关表达式踩坑
Flowable和Activiti都支持表达式路由,一个典型的条件分支在BPMN文件里长这样:
<conditionExpression xsi:type="tFormalExpression">${days <= 3}</conditionExpression>这里的${days}来自流程变量。最常踩的坑有三个:
- 变量不存在时表达式默认返回false,分支不流转且不报错,表现就是“流程静默卡住”。
- 布尔变量用
${approved}没问题,但如果有人在任务提交时传了一个字符串"false",表达式会拿字符串去和布尔值比较,结果完全不一样。 - 数值比较时不要加引号,
${days <= 3}是正确的,${days <= "3"}在某些表达式引擎里会转换成字符串比较,结果诡异。
排查条件路由问题时,我的习惯是先查ACT_RU_VARIABLE表看变量类型和值,再用流程实例的变量快照和条件表达式逐一对标。很多时候不是引擎问题,是提交变量的人把类型搞错了。
5.4 直接从实战角度给出的避坑清单
最后整理几条选型和集成过程中实用的避坑建议:
- 别在生产库上开启
database-schema-update: true,只允许开发环境自动建表,生产环境的表结构变更一定要走脚本评审。 - BPMN文件一定要纳入代码仓库和版本管理,不要直接操作数据库里的流程定义,否则测试和生产环境流程版本漂移会让你崩溃。
- 不要把后台模型设计器的端口暴露到公网,流程引擎的REST API本身权限体系比较标准,但直接暴露会给攻击者提供便利。
- 用户体系尽量解耦,不要依赖Flowable或Activiti默认的IDM用户表,还是接自己系统的用户权限中心更稳。
- 选型评估时一定要做一次POC,用你们业务里最复杂的一条流程分别跑两个引擎,一步验证API、一步看数据和排障效率。
6. 谈一点我个人在实际操作中的体会
这几年做过不少流程引擎相关的选型和迁移,我的体感是:Flowable在复杂流程和模块化扩展上更有优势,Activiti在云原生和轻量接入上有它的生态位置。但真正让一个项目跑得稳的,往往不是引擎本身的参数,而是团队对流程模型的理解深度和排障经验。
如果你们团队刚接触流程引擎,我更建议先用Flowable的官方样例把BPMN的基本元素跑一遍,感受下引擎查数据、部署模型、处理历史任务的方式,再去纠结选型。光看文档对比没有用,动手跑一遍流程,你立刻就知道哪个引擎的API更符合你的直觉。最后再强调一句:选型这种事,没有标准答案,只有适合自己的答案。