升级到 Spring Boot 4 的第一周,最容易被骗的是这一幕:Maven reactor 全绿,本地 clean package 也过了,结果一上联调——大整数金额或脱敏字段行为漂移、超大 JSON 没被拦住、CRUD 生成器吐出的 ServiceImpl 再次编译失败、MCP 异步重进偶发丢登录态、共享测试库被 Flowable 8 写过后再也切不回 7.x。
先说结论:Boot 4 大量坑不在「能不能编译」,而在 默认协议换了、容器能力缩了、第三方主版本与库状态绑死了。Forge Admin 在独立兼容分支把底座钉到 Boot 4.1.1 / Flowable 8.0.0 / Sa-Token 1.45 / MyBatis-Plus 3.5.17 / Spring AI 2.0.1 / Redisson 4.8.0,跑过 T0–T6 兼容矩阵后才合入主线。下面五条都来自真实源码与 pitfalls,按「现象 → 根因 → Forge 怎么改」写,方便你对照自己的工程做清单。
同系列上一篇写过 Flowable 缺
applicationTaskExecutor、以及 Sa-Token 旧 Redis Session 类型删不掉导致「看起来像缺租户」:传送门。本文 不再展开 那两条细节,只在文末清单留勾选项,避免重复占篇幅。
版本矩阵(先对号入座)
| 组件 | Boot 3 基线(升级前) | Forge Boot 4 目标 |
|---|---|---|
| Spring Boot | 3.5.x | 4.1.1 |
| Flowable | 7.0.1 | 8.0.0 |
| Sa-Token | 1.38 + Boot3 starter | 1.45 + sa-token-spring-boot4-starter |
| MyBatis-Plus | 3.5.7 一带 | 3.5.17(拆出 mybatis-plus-spring) |
| Web | 常见 Undertow / starter-web | spring-boot-starter-webmvc + Tomcat |
| JSON | 单一 Jackson 2 习惯 | 业务 Jackson 2 兼容桥 + MCP Jackson 3 |
证据主提交:7ce8974ec「完成底座升级及现有功能兼容回归」(引入请求体限流 Filter、Sa-Token Servlet 注册、代码生成模板包名);合入主线:6bf663717;版本属性钉死:744d94218。兼容过程文档在仓库 code-copilot/changes/spring-boot4-t0 … t6。
坑 1:默认 Jackson 3 会绕过你的字段加解密与大整数协议
现象:HTTP 接口路径还在,前端却发现 Long / 金额 / 脱敏字段行为与升前不一致;或者业务 HTTP 与 MCP 偶发「抢」同一个 mapper,序列化异常难以复现。
根因:Boot 4 默认走向 Jackson 3。Forge 业务侧长期依赖 Jackson 2 上的字段级加解密(Jackson2ObjectMapperBuilderCustomizer)以及大整数、日期约定;而 MCP Java SDK 需要 tools.jackson 的 JsonMapper。两套栈如果混进同一个全局 bean,比「单纯升级 Jackson 大版本」更危险——编译期几乎看不出来。
Forge 怎么改:
- 业务 HTTP 显式选择 Jackson 2(Admin / App / Flow 的
application.yml均已落地):
spring:
http:
converters:
# 必须显式选择 Jackson 2,否则 Boot 4 默认 Jackson 3 会绕过 Forge 的字段序列化。
preferred-json-mapper: jackson2
jackson2:
date-format: yyyy-MM-dd HH:mm:ss
serialization:
fail_on_empty_beans: false
deserialization:
fail_on_unknown_properties: false
- 流程变量同样钉死:
flowable.variable-json-mapper: jackson2,避免历史 JSON 类型变量被 Jackson 3 换存储语义。 - 加解密继续挂在 Jackson 2 Builder:
forge-starter-crypto的JacksonCryptoConfiguration。 - MCP transport 单独注入
@Qualifier("mcpServerJsonMapper")的 Jackson 3JsonMapper,再用JacksonMcpJsonMapper包一层(见ForgeMcpServerAutoConfiguration)。业务ObjectMapper与 MCP mapper 分轨。
可带走:升级第一周先全仓 grep preferred-json-mapper;新 SDK / MCP 只用 qualifier,不要把 Jackson 3 bean 设成全局默认。pitfalls 原话大意是:Boot 4 迁移必须验证 MVC 协议,不能只看编译。
坑 2:Undertow 没了,Tomcat 的「表单上限」盖不住 JSON
现象:你以为已经配了 server.tomcat.max-http-form-post-size=20MB,超限 JSON 却仍能打进加密过滤器或业务层;或者反过来,从 Undertow 迁到 Tomcat 后,原先「靠容器挡一把」的假设失效。
根因:Boot 4 不再支持 Undertow。Forge 的 forge-starter-web 改为依赖 spring-boot-starter-webmvc,注释写明:「Boot 4 要求 Servlet 6.1,使用其支持的默认 Tomcat。」 Flow 服务 application.yml 也写了:「Boot 4 不再支持 Undertow;沿用请求体限制,改用 Tomcat 的有界工作线程。」 更关键的是:Tomcat 的 max-http-form-post-size 只约束表单,并不覆盖任意 application/json 读流或 chunked 请求。
Forge 怎么改:表单上限保留作一层,再加 读流计数 Filter:
RequestBodyLimitFilter:Content-Length超限直接 413;否则包装ServletInputStream,按字节累加,超限回{"code":413,"message":"请求体超过大小限制"}。实现故意避免为了限流而一次readAllBytes无限缓存。RequestBodyLimitAutoConfiguration:默认取forge.web.max-body-size/FORGE_MAX_HTTP_POST_SIZE(20MB),Filter 顺序放在加密过滤器读取原始正文之前。
路径:forge-starter-web/.../RequestBodyLimitFilter.java,随 commit 7ce8974ec 进入兼容分支并合入主线。
可带走:Boot 4 + Tomcat 项目,把「JSON 体上限」当成独立安全需求;只配 form-post-size 等于裸奔一半。
坑 3:MyBatis-Plus ServiceImpl 搬家了,生成器不改会持续产废代码
现象:手改的业务类终于编译过了,低代码 / CRUD 生成器再点一次,「又」冒出找不到的 import;或半个仓库还在旧包名,半个仓库在新包名。
根因:MP 3.5.17 把 Spring 集成拆到独立模块 mybatis-plus-spring。IService / ServiceImpl 从
com.baomidou.mybatisplus.extension.service.impl
迁到
com.baomidou.mybatisplus.spring.service.impl。
只改手写类、不改 Velocity 模板,等于给未来每一次「生成」埋雷。
Forge 怎么改:
- 生产代码与模板同步:
templates/vm/serviceImpl.java.vm、service.java.vm已改为新 import(同属7ce8974ec)。 - BOM 显式管理
mybatis-plus-spring、mybatis-plus-spring-boot4-starter,并继续钉mybatis-plus-jsqlparser-4.9——数据权限 SQL 协议不跟这次一起改,降低升级面。
可带走:升级 MyBatis-Plus 时,把「生成器模板」写进 PR 检查清单;只 sed 手写 Java 不够。
坑 4:Sa-Token 上下文只挂 REQUEST,ASYNC / ERROR 重进会丢登录态
现象:同步 Controller 一切正常;MCP Streamable HTTP、异步重新分派或 ERROR 转发后,偶发「未登录 / 上下文空」。有人会先去改 CORS 或租户拦截器,越改越远。
根因(请务必与系列上文区分):这里不是 Redis 里旧 SaSessionForJacksonCustomized 反序列化失败(那篇已写过),而是 Servlet 调度类型 问题。Sa-Token 1.45 使用 ThreadLocal ModelBox;若 Filter 只覆盖 REQUEST,ASYNC / ERROR 重进时上下文不会从已结束的请求线程自动继承。
Forge 怎么改:显式注册上游同一个 SaTokenContextFilterForJakartaServlet 实例,避免 Boot 再自动注册一份、也避免自己再维护第二套认证实现:
registration.setAsyncSupported(true);
registration.setDispatcherTypes(
DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
类:SaTokenServletContextConfiguration(forge-starter-auth)。类注释原话:「Servlet 异步重新分派也必须建立 Sa-Token 上下文,不能依赖已结束的请求线程。」
可带走:升到 sa-token-spring-boot4-starter 后,用一条真实 ASYNC / MCP 路径回归鉴权;只测同步接口会漏。
坑 5:Flowable 8 动过的库,不能「改回版本号」假装还是 7.x
现象:共享测试库先被 Boot 4 / Flowable 8.0.0 启动写过,再切回仓库里的 Flowable 7.0.1 启动 flow-server,会连环失败:
Could not update Flowable database schema: unknown version from database: '8.0.0.0'- 只把
act_ge_property/act_id_property改回7.0.1.1后,又报 Liquibase:Table 'flw_event_deployment' already exists(以及 App / CMMN / DMN 同类问题)
根因:引擎主版本与 库内 schema 版本标记 + Liquibase changelog 是一套的。半升级 / 半回退会留下「有表、无 changelog」。这条写在 code-copilot/memory/pitfalls/flow.md(发现日期 2026-10-13),是实踩记录,不是推演。
Forge 原则:
- 代码与库同主版本:要上 Boot4 + Flowable8,就 Admin / Flow / 依赖一起升,不要只升一半去蹭共享库。
- 误污染且仍要跑 7.x 的测试库:恢复 7.x 版本键,并删除 changelog 已乱的 Event Registry / App / CMMN / DMN 引擎表;保留流程核心
act_re_/ru_/hi_与业务侧sys_flow_*(生产库勿照抄「清空」动作,细节以 pitfalls 为准)。 - 变量协议:
flowable.variable-json-mapper: jackson2,与坑 1 同一套「业务 JSON 语义不变」。
合并分支常炸的附加项:Boot4 兼容分支合入 main 后,曾同时存在两个 V1.0.218__*.sql,Flyway 直接无法启动(pitfalls db-flyway.md,2026-10-16)。合并后、提交前先查重:
ls forge-server/db/migration/V*.sql | sed 's/__.*//' | sort | uniq -d
已执行的那份文件名与内容都不动;另一方 git mv 到当前最大版本号 + 1。Admin 默认:
spring.flyway.locations: ${FORGE_FLYWAY_LOCATIONS:classpath:db/migration}
spring.flyway.fail-on-missing-locations: true
避免生产 JAR 正确、却去扫服务器工作目录里残留的旧 db/migration。
建议升级顺序(Forge 实操摘要)
结合 T0 探针 → T1 底座 → 合入主线的经验,建议读者按这个顺序做,而不是「一次性改 BOM 再祈祷」:
- 先隔离验证:用独立 profile / 探针工程确认 Flowable 8、Sa-Token Boot4 starter、目标 AI BOM 能否装配;保留负面结果,不要为了绿灯降级到未验证的 milestone。
- 再改底座 POM:统一 Boot / MP / Sa-Token / Webmvc;同时改生成器模板与
preferred-json-mapper。 - 协议回归优先于功能回归:大整数、加解密、JSON 体 413、登录态 ASYNC、Flyway locations——这些比「某个业务页面像素」更能决定能不能上联调。
- 库状态单独门禁:Flowable 引擎表、Flyway history、Redis 旧 Session(系列上文)各算一条;共享测试库被 8.x 写过就不要幻想「改个版本号回去」。
- 最后才合入主线:Forge 是
6bf663717合并兼容分支时才把主线校验与纯净版 SQL 规则一并保留;合并后立刻跑 Flyway 同号查重。
官方 Migration Guide 仍然要读,但它不会替你处理「生成器模板」「业务 Jackson 协议」「共享测试库被引擎主版本污染」这些工程细节——而这正是清单文存在的理由。
升级自查清单(建议收藏)
| # | 检查项 | 过关信号 |
|---|---|---|
| 1 | preferred-json-mapper: jackson2 + 加解密仍在 Jackson2 Customizer | 大整数 / 脱敏 / 金额回归与升前一致 |
| 2 | MCP / 新 SDK 使用独立 Jackson3 @Qualifier mapper | 业务 ObjectMapper 不被 Jackson3 顶掉 |
| 3 | webmvc + Tomcat;JSON 体有读流限流(413) | 超大 JSON 进不了加密 / 业务 Filter |
| 4 | ServiceImpl/IService 新包 + 生成器 .vm 同步 | 再生成 CRUD 仍可编译 |
| 5 | Sa-Token Filter 覆盖 REQUEST / ASYNC / ERROR | MCP / 异步重进仍有登录上下文 |
| 6 | Flowable 主版本与库一致;禁止只改 schema.version 回退 | 7 / 8 不混用同一测试库 |
| 7 | Flyway classpath:db/migration;合并后查同号双文件 | 启动无 checksum / duplicate version |
| 8 | (系列上文)自定义 Executor 时补 applicationTaskExecutor 或 mode=force | 见 上一篇 |
| 9 | (系列上文)Sa-Token 1.45 读旧 SaSessionForJacksonCustomized | 同上:先查 Session,再查租户拦截器 |
写在最后
这篇是 Forge Admin Boot4 兼容系列第 2 篇:偏「升级路径与可复用清单」;第 1 篇偏「双故障现场」。仓库地址以项目 README 为准。若你也在升 Boot 4,欢迎在评论区丢一条你卡最久的堆栈——下篇可以按读者出题继续拆(例如 Redisson 4 节点 API、Spring AI 2 与通义 milestone 的 ToolExecutionEligibilityPredicate 缺类等,我们 pitfalls 里也有实锤,本篇篇幅先停在五条主坑 + 清单)。
觉得能少踩一个坑的话,麻烦 点赞 + 收藏 这份表;收藏通常比点赞更救命。也欢迎把清单拷进你们团队的升级 RFC——改成自己的仓库路径即可,不必和 Forge 一一对应。