
很多人在 Spring Boot 3.x 发布之后都有一个顾虑工作流引擎跟不跟得上Flowable 7.x 出来的那一刻这个答案基本确定了——可以。我最近在项目里从零跑通了一条完整链路Spring Boot 3.2 集成 Flowable 7.0设计一个请假审批流程然后部署、发起、查询任务、完成审批每一步都踩了一遍顺便排了一遍坑。这篇把整个过程拆开写清楚重点放在版本兼容、配置位置、BPMN 文件设计和几个核心 Service 的用法上给正准备把 Flowable 引入 Spring Boot 3.x 项目的朋友做参考。这个系列第一篇主要解决“跑通”的问题工程怎么搭、流程怎么定义、怎么部署、怎么发起、怎么完成任务。不讲太深的设计模式也不扯流程引擎底层原理目标就是让你照着做能在一个下午内看到自己的第一个流程在数据库里留下轨迹。1. 内容整体设计与思路拆解1.1 为什么是 Flowable而不是 Activiti 或 Camunda选型这件事很多人会在 Flowable 和 Activiti 之间纠结。说白了Activiti 由原 Flowable 团队早期成员创立后来走向了商业化路线Camunda 则是另一个分支社区版功能也强但如果你要的是“纯粹的开源流程引擎 与 Spring Boot 无缝整合 文档贴近国内开发者习惯”Flowable 一直是稳妥的选择。更重要的是 Flowable 的 Spring Boot Starter 做得很完整依赖加进去之后自动配置就能生效不像 Activiti 那样经常需要手动排除数据源冲突。Flowable 7.x 对 Spring Boot 3.x 的支持非常明确官方直接给出了适配 Jakarta EE 9 的版本不像早期版本需要自己打补丁。如果你接下来要做审批流、工单流、发布流程Flowable 在这类场景里的稳定性和资料完整性都更有优势。1.2 版本选型的关键差异javax 到 jakartaSpring Boot 3.x 最让人注意的变化是 Java 基线提升到了 17同时 Jakarta EE 9 全面取代了 Java EE 8 的 javax 命名空间。这对老项目来说是质的改变所有依赖都要跟着升级。Flowable 在 6.x 时代还绑在 javax 上没法直接跑在 Spring Boot 3.x 里。到了 7.0.0Flowable 把核心模块的命名空间全部迁移到了 jakarta.*这才和 Spring Boot 3.x 兼容。这是整个集成方案里最关键的一个前提——你不需要做任何额外的类库替换只需要确保 Flowable 版本在 7.0.0 及以上。所以集成前先确认一下你的项目环境JDK 版本17 或更高Spring Boot3.0.x / 3.1.x / 3.2.x 均可Flowable7.x构建工具Maven 3.6 或 Gradle 7.5这个配置组合是官方验证过的组合直接起步不会走弯路。1.3 集成方案全景整体架构与核心组件Flowable 不是简单的一个工具库它是一整套流程引擎。集成到 Spring Boot 后你的应用会被拆分成几个角色流程引擎ProcessEngine整个 Flowable 的核心负责解释 BPMN、管理状态、触发行为数据层Flowable 有自己的数据库表act_ 前缀包括流程定义、流程实例、任务、历史等Service 层RepositoryService、RuntimeService、TaskService、HistoryService 等对外暴露操作入口业务层你自己的业务代码在合适的时机调用这些 Service设计整个集成方案的时候我建议你把 Flowable 当成独立的“流程基础设施”来看待不要在业务代码里直接操作流程引擎内部的对象而是通过 Service API 与它交互。这样做的好处是后续如果流程引擎升级业务层不用大改。另外一个很容易忽略的设计点是流程引擎的事务管理与业务事务的边界。Flowable 默认在一个事务里完成流程状态变更如果你的业务代码也要同时更新业务表的数据一定要确保两者在同一个事务上下文中协调工作不然后面会出现“流程走到了下一步业务数据却没保存”的怪问题。这个我会在第 5 部分专门展开讲。2. 依赖引入与基础配置2.1 引入依赖flowable-spring-boot-starter-process新建一个 Spring Boot 3.2 工程之后第一步是引入 Flowable 的 Spring Boot Starter。绝大多数业务场景只需要流程引擎和任务管理功能所以引入flowable-spring-boot-starter-process就够了。这个包会自动把 Process Engine、CMMN、DMN 等基础模块带进来但又不至于引入全部组件导致启动变慢。我在 pom.xml 里的依赖配置如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.4/version relativePath/ /parent properties java.version17/java.version flowable.version7.0.1/flowable.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency dependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter-process/artifactId version${flowable.version}/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency /dependencies这里有两个细节值得留意。第一mysql-connector-j是 MySQL 官方新坐标老坐标mysql-connector-java在 Spring Boot 3.x 里已经被替换掉别写错了。第二Flowable 的 7.0.1 版本相对 7.0.0 修复了一些与 Spring Boot 3.2 的兼容细节建议直接用 7.0.1 或更新补丁版本。2.2 核心配置分析application.yml 关键项依赖引入之后需要在 application.yml 里配置数据源和 Flowable 的行为参数。写过 Flowable 6.x 的人要注意7.x 的配置项前缀仍然是flowable.*但有一些选项的默认值变了。我的配置是这样spring: datasource: url: jdbc:mysql://localhost:3306/flowable_demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghainullCatalogMeansCurrenttrue username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver flowable: database-schema-update: true async-executor-activate: false history-level: audit db-history-required: true process-definition-location-prefix: classpath*:/processes/ check-process-definitions: true enable-process-definition-info-cache: true配置逐条说明database-schema-update: true表示启动时自动检查并创建 Flowable 所需的表结构。开发阶段用 true 非常高效但生产环境必须改成 false 或使用专门的升级脚本。async-executor-activate: false这是被问得最多的配置。Flowable 默认有一个异步执行器专门跑定时任务、异步延续等。在简单流程里用不到开着反而会多几个后台线程和表关掉可以降低学习期的干扰。history-level: audit指定历史记录的精细度。audit 是推荐级别保留流程实例和任务的所有数据又不像 full 级别那样记录所有变量快照数据量可控。process-definition-location-prefix配置了 BPMN 文件的扫描位置。重新部署后所有放在resources/processes/下的.bpmn20.xml或.bpmn文件会被自动部署。check-process-definitions: true加上上面那个前缀Spring Boot 启动时就会自动部署流程定义这个能力很方便但要留意重复部署的问题——下面会讲到。2.3 数据源与流程引擎的协同关系Flowable 的 Starter 会自动检测 Spring 容器中的数据源并以此创建自己的引擎。也就是说你不用额外配置一个“Flowable 专用数据源”它直接复用你项目里的 DataSource。但是在使用 MySQL 的时候有一个连接参数必须注意nullCatalogMeansCurrenttrue。这个参数的作用是让 JDBC 的 DatabaseMetaData 在 catalog 为空时只检索当前数据库的表避免 Flowable 在初始化表结构时把其他数据库的表也扫描进来导致“表已存在”的误解或者莫名的判断偏差。如果你用的是 PostgreSQL就没这个参数不需要额外处理。项目初期建议直接用 MySQL 或 H2 起步H2 的配置更简单本地测试很快但如果你后续要上生产环境就直接用 MySQL 作为目标库来调避免换来换去出现边界问题。Flowable 启动时会创建大约 70 张表表名以ACT_开头分属不同模块。初次启动的时候数据库里看到一批ACT_表是正常的不用担心这是“垃圾表”。这里面核心的表有ACT_RE_*流程定义和流程资源ACT_RU_*运行时数据包括流程实例、任务、变量ACT_HI_*历史数据包括历史流程实例、历史任务、历史活动理解这些表的分类对后面排查问题很有帮助。比如流程发起失败要查ACT_RU_EXECUTION任务丢失要查ACT_RU_TASK而流程审批记录则要到ACT_HI_TASKINST里去核对。3. 流程定义设计从 BPMN 到 XML3.1 设计一个“请假申请”流程节点与连线集成配置完成之后第一步实际动作就是设计一个可运行的流程。这个环节直接决定了后续所有操作的目标。我做了一个经典的“请假申请”流程总共三个节点开始事件StartEvent流程的入口用户任务“经理审批”UserTask审批人处理申请结束事件EndEvent流程结束流程的流转方向是提交申请之后进入经理审批节点审批通过之后流程结束。这个流程虽然简单但它完整覆盖了工作流引擎的三个核心要素节点Node、连线SequenceFlow、执行监听器可选。理解了这个结构后面加条件分支、会签、子流程都会容易很多。为什么从这么简单的模型开始因为工作流引擎最容易把人绕晕的地方不在 API而在 BPMN 语义。先把最简单的结构跑通你能直观地看到每个节点在数据库里留下的痕迹再叠加复杂度心里就有底了。3.2 BPMN XML 逐个拆解Flowable 的流程定义使用 BPMN 2.0 标准格式本质是一个 XML 文件。我在src/main/resources/processes/leave-process.bpmn20.xml下创建了如下内容?xml version1.0 encodingUTF-8? definitions xmlnshttp://www.omg.org/spec/BPMN/20100524/MODEL xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:flowablehttp://flowable.org/bpmn targetNamespacehttp://www.example.com/process process idleaveProcess name请假审批流程 isExecutabletrue startEvent idstartEvent name开始/ sequenceFlow idflow1 sourceRefstartEvent targetRefmanagerApproval/ userTask idmanagerApproval name经理审批 flowable:assignee${approvalUser}/ sequenceFlow idflow2 sourceRefmanagerApproval targetRefendEvent/ endEvent idendEvent name结束/ /process /definitions这里的关键点process标签中的id是流程定义 Key后续启动流程时就是用这个 Key 来定位的所以尽量起一个语义清晰且稳定的名字。flowable:assignee${approvalUser}指定了任务的处理人。这里使用的是表达式启动流程实例时我们可以通过流程变量传入具体的审批人。isExecutabletrue这个属性一定要保留。不写的话Flowable 可能认为这个流程定义只是用于建模展示不会真正执行。整个 XML 的语义就是开始节点 → 顺序流 → 用户任务经理审批→ 顺序流 → 结束节点。不要把 BPMN 想象得很复杂它就是一套用统一符号描述“谁在什么条件下做什么事”的行业标准。3.3 部署工具与 API 实现写好 BPMN 文件之后有两种方式可以部署。第一种方式是“自动部署”就是前面配置里的flowable.process-definition-location-prefix。Spring Boot 启动时自动扫描resources/processes/下的所有流程定义文件并入库。这种方式开发阶段特别方便不需要写任何部署代码。第二种方式是“编程部署”通过RepositoryService手动完成。它适合在运行阶段动态部署新流程——比如从数据库或文件系统读取 BPMN 内容动态发布到引擎。我建议开发初期用自动部署正式项目里用接口控制发布。因为自动部署有时候会带来一个隐蔽问题每次修改 BPMN 文件后重启都会生成一个新版本。如果 QAs 或测试环境不去清库流程定义版本会越来越多看起来乱实际上并不影响你按 Key 启动引擎默认取最新版本但在排查任务来源时会有干扰。有条件的话在正式环境写一个部署接口用自己的方式和发布流程绑定会清爽很多。编程部署的核心代码非常简单repositoryService.createDeployment() .addClasspathResource(processes/leave-process.bpmn20.xml) .name(请假审批流程) .deploy();注意addClasspathResource的路径不能以/开头否则会找不到文件。这个细节在官方文档里没有特别强调但实践里真的会有人栽在这里。4. 流程的核心操作实现4.1 发起流程RuntimeService 的 startProcessInstanceByKey流程部署完成之后通过RuntimeService来创建流程实例。这里的“流程实例”可以理解成一次具体的请假申请。同样一个“请假审批流程”张三发起一次、李四发起一次就是两个流程实例。我的发起代码如下Service public class LeaveProcessService { private final RuntimeService runtimeService; public LeaveProcessService(RuntimeService runtimeService) { this.runtimeService runtimeService; } public String startLeaveProcess(String userId, String managerId) { MapString, Object variables new HashMap(); variables.put(approvalUser, managerId); variables.put(startUserId, userId); variables.put(days, 3); ProcessInstance processInstance runtimeService .startProcessInstanceByKey(leaveProcess, variables); return processInstance.getId(); } }这里需要注意的两点startProcessInstanceByKey的参数是 BPMN 文件里process的id也就是leaveProcess。如果同一个流程被部署了多个版本引擎会自动选择最新版本。流程变量的作用域是流程实例级别的。上面塞进去的approvalUser会被任务查询和表达式直接使用。要注意流程变量在历史记录里也能看到不要把密码、身份证号之类的敏感信息放进去。history-level设为audit时流程变量只是部分保留但没有必要冒这个风险。startProcessInstanceByKey返回的ProcessInstance.getId()是流程实例的 ID在调试查询时会用到。流程实例的业务标识可以通过 Business Key 绑定比如把请假单号关联到流程实例上不过这不是本篇的重点先不提。4.2 查询与完成任务TaskService 完整链路流程启动后会停留在“经理审批”节点。这个时候审批人的待办事项要通过TaskService查询。这里要注意一个常见的误区TaskService 查询的是“任务”不是“流程实例”。每个用户任务在运行时都是一条独立的 Task 记录存储在ACT_RU_TASK表里。任务和流程实例是多对一的关系查询待办时要面向 Task 来操作。查询任务的标准姿势如下ListTask tasks taskService.createTaskQuery() .taskAssignee(managerId) .orderByTaskCreateTime().desc() .list(); for (Task task : tasks) { System.out.println(task.getId() - task.getName() - task.getProcessInstanceId()); }taskAssignee是精确匹配它和 BPMN 里的flowable:assignee对应。也就是说查询出来的任务一定是分配给了 managerId 这个人的。除了taskAssignee你还可以用taskCandidateUser来匹配候选人。这两者的区别在于assignee 是直接指定处理人而 candidate user 只是候选不一定归属。比如多人抢单的场景任务不绑死在某个人身上就是用候选人的方式来做。这个后面写会签或抢单流程时会用到。完成任务的操作更直接taskService.complete(taskId);调用这一行之后引擎会找到该任务所在的流程实例顺着 BPMN 的连线走到下一个节点。如果下一个节点是结束事件流程实例就自动结束。结束之后ACT_RU_EXECUTION里对应的运行时数据会被清理而历史数据会保留在ACT_HI_*表里。4.3 完整业务流程闭环演示代码为了让你看到全貌我把从启动到完成任务的完整链路整合在一个 Service 里演示。这个示例没有引入 Controller 层直接以服务方法展示方便你在单元测试或命令行启动器里验证。Service public class LeaveFlowDemoService { private final RuntimeService runtimeService; private final TaskService taskService; private final HistoryService historyService; public LeaveFlowDemoService(RuntimeService runtimeService, TaskService taskService, HistoryService historyService) { this.runtimeService runtimeService; this.taskService taskService; this.historyService historyService; } public void runDemo() { // 1. 发起流程 MapString, Object variables new HashMap(); variables.put(approvalUser, manager01); ProcessInstance instance runtimeService .startProcessInstanceByKey(leaveProcess, LEAVE-2025-001, variables); System.out.println(流程实例ID: instance.getId()); // 2. 查询经理的待办任务 ListTask tasks taskService.createTaskQuery() .taskAssignee(manager01) .list(); if (tasks.isEmpty()) { System.out.println(没有找到待办任务); return; } // 3. 完成第一个任务 Task task tasks.get(0); taskService.complete(task.getId()); // 4. 通过历史服务验证流程执行结果 HistoricProcessInstance historicProcessInstance historyService .createHistoricProcessInstanceQuery() .processInstanceId(instance.getId()) .singleResult(); System.out.println(流程结束状态: (historicProcessInstance.getEndTime() ! null ? 已结束 : 运行中)); } }这段代码里我用startProcessInstanceByKey的第二个参数传入了 Business KeyLEAVE-2025-001。Business Key 的作用是把业务系统的唯一标识和流程实例绑定比如请假单号、工单号这样可以实现“给用户展示业务单号内部通过单号反查流程实例”。这在分页列表和详情页关联时是一个非常刚需的功能。从运行日志里你能看到完整的变化流程启动 → 数据库出现运行时记录 → 任务创建 → 完成任务 → 运行时记录清理 → 历史表写入结束时间。Flowable 的全生命周期在数据库层面的表现一目了然。5. 常见问题与排查技巧实录5.1 版本冲突Spring Boot 3.x 与 Flowable 依赖的坑Flowable 7.x 的 Spring Boot Starter 对传递依赖的版本管理比较克制但它自己带了一些基础库。如果你发现启动时抛出类似NoClassDefFoundError: javax/xml/bind/...的异常先不要慌这是已明确不存在的旧依赖残留。Spring Boot 3.x 移除了很多 Java EE 时代的 API 实现比如 JAXB。Flowable 7.x 已经把自身代码适配到了 Jakarta 命名空间所以这种异常更多是项目里其他老依赖导致的。排查的方式很简单用mvn dependency:tree看依赖树找出javax开头的类从哪个 jar 里来排除它或者替换为对应的jakarta版本。另一种常见冲突是 Spring Boot 与 Flowable 对同一个库的版本要求不一致导致启动报错。解决办法是让 Spring Boot 的版本管理生效Flowable Starter 的依赖排除掉不必要的项保留核心模块即可。5.2 数据库建表问题与 MySQL 大小写敏感Flowable 的表名和列名在 MySQL 下默认不区分大小写但如果你在 Windows 环境本地开发、Linux 环境跑生产两边对表名大小写敏感的处理不一样可能会出现在本地跑得好好的、一上 Linux 就报“表不存在”的情况。解决这类问题的建议是统一使用小写表名数据库配置里把lower_case_table_names设置为 1。另外MySQL 5.7 和 8.0 对 DDL 的处理也有细微差别Flowable 官方推荐 MySQL 8.0 起步如果还在用 5.7部分字段类型可能不在预期中。在此之前记得加上nullCatalogMeansCurrenttrue这个参数前面也提过它会避免建表时扫描数据库中的所有 catalog。5.3 事务边界业务数据与流程数据的一致性这应该是所有 Flowable 初学者最后都会遇到的灵魂问题。一个请假审批流程你不仅要让流程走到下一步还要更新业务库里的“请假单状态”这两个操作如何保证同时成功或同时失败Flowable 的 Service API 默认在自身事务里提交如果直接这样写leaveOrderMapper.updateStatus(orderId, APPROVING); runtimeService.startProcessInstanceByKey(leaveProcess, variables);万一流程启动失败前面的updateStatus已经提交了业务数据就会出现“状态已变但流程不存在”的脏数据。正确的做法是把两个操作放在同一个事务里Transactional(rollbackFor Exception.class) public void submitLeave(LeaveOrder order) { leaveOrderMapper.updateStatus(order.getId(), APPROVING); runtimeService.startProcessInstanceByKey(leaveProcess, variables); }Flowable 的 Starter 会自动参与 Spring 的管理事务所以加了Transactional之后两个操作会进入同一个事务。这一点是我个人强烈建议每个集成 Flowable 的团队从一开始就遵守的规范不在事务外同时修改业务表和流程表。5.4 其他常见坑位总结问题现象可能原因排查方向启动后没有部署任何流程检查process-definition-location-prefix路径和文件后缀确认 BPMN 文件在 classpath 下且后缀是.bpmn20.xml或.bpmnstartProcessInstanceByKey抛异常提示流程不存在流程定义的id和你传入的 Key 不一致打开数据库查ACT_RE_PROCDEF表的KEY_字段查询不到任务任务被分配给其他人或者已经完成用taskCandidateUser查询候选人场景任务节点突然跳过不等待检查是否给flowable:skipExpression设置了自动跳过的表达式或者检查绑定了条件表达式是否被满足启动慢且日志里有锁等待异步执行器不断获取任务确认async-executor-activate是否设置为 false流程重复部署产生多个版本自动部署 手动部署同时使用或文件内容发生变化用repositoryService.createDeploymentQuery().list()检查版本数量有一个比较隐蔽的坑是流程变量类型的序列化。如果你往流程变量里塞了一个自定义对象Flowable 默认会通过 Java 序列化存储到数据库。一旦这个对象的类结构变化反序列化时会报错而且报错信息往往不明确。初学阶段流程变量尽量只放基本类型、String、Date 和 Map等需要复杂变量时再研究 JSON 序列化的方案。5.5 补充流程引擎在单元测试中的调试技巧写这段是额外想提醒你的。Flowable 提供了基于内存数据库的测试方案用 H2 数据库配合database-schema-update: true可以在不连接外部数据库的情况下启动引擎跑完整流程。这样单元测试的启动速度会非常快而且不会有环境依赖。我在本地通常用SpringBootTest配合 H2 来做流程测试配置大致是spring: datasource: url: jdbc:h2:mem:flowable;DB_CLOSE_DELAY-1;DB_CLOSE_ON_EXITFALSE driver-class-name: org.h2.Driver username: sa password:这个配置对日常调试极其友好。你可以在测试里直接调用runtimeService和taskService写完整个流程的断言逻辑跑得又快又稳。另外一个调试技巧是如果 BPMN 文件里加了条件表达式需要在发起流程或设置任务完成事件时把相关流程变量传进去否则条件判定会失败。出错时看日志不一定直观很多新手会遇到“流程莫名其妙终止在中间节点”的情况其实就是因为条件表达式的变量缺失Flowable 默认把缺失变量的条件判断视为 false。结尾集成这块我先讲到这里。Spring Boot 3.x 加 Flowable 7.x 跑通一个完整流程核心就是三步把依赖配好、把 BPMN 文件写对、把三个 Service 用熟。实际操作中你会发现真正花时间的地方不在代码而是理解 Flowable 的运行时数据模型和事务行为。我的个人建议是第一次跑通流程之后去数据库里翻一遍ACT_RU_TASK、ACT_RU_EXECUTION、ACT_HI_PROCINST这几张表肉眼看一下数据是怎么变化的比你多看十篇教程都有用。这套基础链路跑通之后下一步可以考虑做流程表单绑定、监听器、条件分支以及和业务权限体系的集成。我会在系列后续文章里继续展开。如果你在集成的时候卡在某个奇怪的问题上优先检查版本配套和数据源参数绝大多数坑都在那里。