Appearance
一次未完成的 Spring Boot 升级:从 2.2.10 到 2.5.14 的工程复盘
最近,我开始重新整理过去的 Wiki 文档和 Git 提交记录,回顾此前做过的一些技术探索和工程改造。
在整理过程中,AI 提醒了我一件事:一次没有最终上线的技术升级,其探索过程同样值得记录。
这让我想起了 2025 年底曾经尝试过的一次 Spring Boot 升级。
当时,我尝试将一个长期运行的业务系统从 Spring Boot 2.2.10.RELEASE 升级到 2.5.14。实验最终没有进入生产环境,但留下了一个编号为 V14 的开发分支。
于是,我重新查看历史代码、Maven 配置和 Git 提交记录,尝试还原当时的升级过程。
起初,我和 AI 采用的是比较简单的协作方式:AI 提出 Git 命令,我在本地执行,再将结果提供给 AI 分析。
后来,由于需要检查的提交越来越多,我改为使用 Codex 直接对本地 Git 仓库进行只读调查。
随着调查深入,我逐渐发现,仅仅依靠 Git 历史并不能完整解释过去发生的事情。
例如,V14 为什么会出现?部分兼容性修改为什么提前进入生产?模块化治理究竟是在升级之前还是之后开始的?
这些问题,有的能够从 Git 中找到答案,有的需要结合我自己的开发经历进行回忆,还有一些即使经过讨论,仍然无法完全确认。
这次复盘实际上由三个方面共同推动:
- AI 的提醒和追问:帮助我发现值得记录的经历,并提出过去没有主动思考的问题。
- 我自己的回忆和判断:补充当时的开发背景、工作方式和技术决策。
- Git、Wiki 等历史记录:提供可以核查的代码事实,也用来验证或纠正我的回忆。
三者都有各自的局限。
Git 能证明代码发生过什么变化,却未必能解释为什么变化;个人回忆能够补充背景,但可能遗忘细节;AI 能提出假设和分析线索,也可能得出不准确的解释。
因此,本文并不试图宣称已经完整还原了当年的全部过程。
我更希望通过现存证据、历史回忆和技术分析之间的交叉验证,尽可能准确地记录这次没有完成的升级实验。
本文在叙述中区分可以通过资料验证的事实、我对开发过程的回忆,以及仍需验证的技术推断。对于重要结论,会直接说明其证据来源或不确定性,而不为每一段机械地添加分类标签。
脱敏说明: 本文涉及的项目名称、模块名称及 Git 提交标识均已进行展示层脱敏。真实 Git Hash 统一替换为 BASE-01、UPG-01、FIX-01、MOD-01 等编号。这些编号仅用于本文内部关联,并非真实 Git SHA,不能直接用于 Git 命令查询。
相关技术版本、代码变化和提交之间的关系则尽可能依据原始记录保留,以便说明当时的工程情况。
一、为什么要升级一个仍在正常运行的系统?
1.1 当时的技术基线
项目是一个持续运行和维护的 Java 业务系统,涉及订单、支付、渠道对接,以及多个内部系统之间的数据交互。
当时主要使用以下技术栈:
| 组件 | 原有版本 |
|---|---|
| Java | 8 |
| Spring Boot | 2.2.10.RELEASE |
| Spring Cloud | Hoxton.SR12 |
| Dubbo | 2.7.8 |
| Nacos Server | 2.0.3 |
| Elasticsearch Server | 7.6.2(复盘时核实,历史版本待确认) |
| 构建工具 | Maven |
系统主要包含两个应用:
project-payment-console:面向运营人员的后台。project-payment-portal:面向终端用户的前台。
此外,系统还需要与多个内部系统进行服务调用和数据交互。
从业务角度看,原有技术栈仍然能够支撑日常开发和运行。
但从长期维护的角度看,我并不希望一直停留在 Java 8 和 Spring Boot 2.2.10 上。
1.2 我的目标并不是停留在 Spring Boot 2.5
Spring Boot 2.2.10 发布于 2020 年 9 月。
到了 2025 年底,它已经是一个发布五年多的旧版本,而且早已结束常规开源维护。
我希望逐步使用更新的 Java API、框架能力和依赖生态,也希望有机会获得更好的性能与长期维护条件。
我的长期目标甚至是 Java 25 和 Spring Boot 4.x。
但对于一个仍在运行的遗留系统,我并不打算一步完成这样的跨越。
项目中存在不少历史依赖,还需要与其他内部系统进行交互。直接跨越多个 Spring Boot 大版本,意味着需要同时面对大量兼容性变化。
因此,我当时选择先尝试一个中间版本:Spring Boot 2.5.14。
Spring Boot 2.5.14 于 2022 年 5 月发布,是 2.5 系列最后一个常规开源版本。随着该版本发布,2.5 系列结束了常规开源支持。
因此,到了 2025 年底,将 2.5.14 作为长期升级方案,已经不能解决技术栈持续维护的问题。
另外,Spring Boot 3.0 迁移指南早已建议,原有 Spring Boot 2.x 项目在迁移到 3.0 之前,先升级到最新的 2.7.x。
最后一个 Spring Boot 2.7 开源版本为 2.7.18。
这意味着,如果从当时的技术生态重新评价,2.7.x 比 2.5.14 更值得考虑为通往 Spring Boot 3.x 的阶段性版本。
那么,我当时为什么选择 2.5.14,而不是 2.7.18?
目前我已经无法准确还原最初的选型过程。
历史记录中确实出现过 Spring Boot 2.7.3 的相关线索,但这些线索不足以证明我当时的完整尝试顺序,也不能据此补写一段未经证实的版本选择过程。
能够确认的是,最终保留下来的实验分支采用 Spring Boot 2.5.14。
另外,2.5.14 仍然支持 Java 8。这使得实验能够暂时保留原有 Java 运行时,将主要调整范围放在 Spring Boot、Spring Cloud 及相关依赖组合上。
1.3 这是一项自主开展的技术探索
这次升级并不是一项要求在指定日期完成的正式交付任务。
我是在完成正常业务开发之外,利用可支配的时间进行研究。
如果有新的业务功能、渠道接口或生产问题需要处理,我仍然会优先完成这些工作。
有时间时,再继续研究框架升级。
这种背景也决定了我的实验方式。
我没有一开始就建立一份完整的升级验收计划,而是先尝试修改依赖、启动应用、处理兼容性问题。
如果某个修改能够同时适用于 Spring Boot 2.2.10 和 2.5.14,我就希望先完成验证,并尽早将其合入生产主线。
这样,即使整体升级最终没有完成,已经验证过的局部修改仍然可以保留下来。
二、升级策略:为什么最终出现了 V14?
2.1 先解释当时的 Git 分支管理方式
理解 V14 之前,需要先了解我当时的开发习惯。
项目主要使用以下分支:
prod:生产分支。test:测试分支。feat-xxx:独立功能开发分支。
开发新功能时,我通常从 prod 创建一个 Feature 分支。
例如:
text
prod
├── feat-channel-a
├── feat-channel-b
└── feat-channel-c三个 Feature 分别承载不同需求。
开发过程中,我会在对应的 Feature 分支上持续提交代码。
当功能具备测试条件时,再将相关提交 cherry-pick 到 test,随后部署测试环境进行验证。
如果测试发现问题,我仍然会回到原 Feature 分支继续修改,然后将新增提交 cherry-pick 到 test。
测试完成、满足上线条件后,再将该 Feature 的完整变更集成到 prod。
这里有一个重要区别:
我使用 cherry-pick,并不是为了从一个 Feature 中随意挑选某几个提交,而是为了按照提交顺序,将这个 Feature 所需的完整变更应用到目标分支。
2.2 为什么不直接 merge 测试分支?
这与项目当时的实际发布情况有关。
假设有三个渠道开发任务:
- 渠道 A 已经完成开发并通过测试。
- 渠道 B 已经完成开发并通过测试。
- 渠道 C 也已经完成开发并通过测试。
但由于商务安排或者业务优先级发生变化,A、B 暂时不准备上线,只有 C 需要发布。
此时,test 可能已经包含三个渠道的代码,而 prod 尚未包含这些修改。
如果直接将 test merge 到 prod,就可能把 A、B 两个暂不准备发布的功能一起带入生产。
因此,我不能简单地将测试分支作为生产分支的直接合并来源。
我采用的方式是:每个 Feature 独立开发,分别将其完整变更 cherry-pick 到需要集成的分支。
例如:
text
feat-channel-a ── cherry-pick ──> test
feat-channel-b ── cherry-pick ──> test
feat-channel-c ── cherry-pick ──> test
feat-channel-c ── cherry-pick ──> prod图中的 cherry-pick 表示按照正确顺序应用该 Feature 所需的提交序列,并不是任意选择单个提交。
我希望集成的是某个 Feature 的完整功能,而不是测试分支上积累的所有代码。
2.3 cherry-pick 不意味着可以忽略提交依赖
假设一个 Feature 在开发过程中产生了十次提交:
text
Commit 1 增加基础接口
Commit 2 增加请求对象
Commit 3 增加 HTTP 客户端
Commit 4 增加签名验签
...
Commit 10 完成业务处理这些提交可能存在明确的前后依赖。
如果只 cherry-pick 第十次提交,却没有引入前面的基础接口、对象和组件,那么代码可能无法编译,也可能在运行时出现问题。
因此,我通常会按照原有顺序,将该功能所需的完整提交序列应用到目标分支。
至于如何把一次开发组织成职责清楚的多个提交,我在后文介绍 AI 辅助 Git 提交时再展开。
2.4 为什么进入生产时,我仍然倾向于 cherry-pick?
从 Git 的技术能力来看,Feature 是从 prod 创建的,当然可以 merge 回 prod。
直接合并一个未混入其他需求的独立 Feature,也不会凭空引入测试分支中的其他功能。
不过,我在实际开发中已经形成了一套相对统一的 cherry-pick 集成习惯。
尤其是当某个 Feature 持续开发较长时间时,prod 可能已经发生了不少变化。
其他开发人员持续提交业务代码,我自己也可能正在进行公共类迁移、包结构调整或者 Maven 模块拆分。
我更倾向于将该 Feature 的完整提交序列重新应用到当前生产主线上,而不是为每次集成额外产生 merge commit。
当然,cherry-pick 并不能避免全部冲突。目标分支发生变化后,仍然可能出现文本冲突或运行层面的兼容性问题。
它也有自身的代价。
cherry-pick 通常会生成新的提交哈希。同一个逻辑修改在 Feature、test 和 prod 中可能对应不同的提交 ID。
如果后来删除原始 Feature 分支,又没有保留提交来源关系,那么事后追踪某项修改的来龙去脉就会更加困难。
这一点也与本次 V14 历史调查遇到的问题有关。
2.5 小步开发、独立验证、尽早集成
长期处理这样的开发任务后,我逐渐形成了一个习惯:
小步开发、独立验证、尽早集成、按需启用。
我并不希望一个已经开发和验证完成的功能,因为整个大需求尚未结束,就一直停留在独立 Feature 分支中。
例如对接一个银行渠道,可能需要完成汇率查询、开户、付款、退款、回调等多个接口。
我通常不会等所有接口全部开发完成后,才进行第一次集成。
我可能先完成汇率查询。
虽然从业务上看只有一个接口,但它已经涉及配置管理、HTTP 调用、加密解密、签名验签,以及对应的 UT、IT 和测试数据。
当这个最小功能链路完成验证,并确认能够安全地与现有系统共存后,我就希望先将它集成到生产主线。
至于业务是否正式启用,则可以通过相应配置和功能入口进行控制。
当然,代码进入生产分支并不等于可以忽略生产验证。即使业务入口暂时关闭,也需要确认新增代码不会在应用启动、定时任务或其他运行路径中产生意外影响。
这种开发习惯还有一个实际收益:
缩短 Feature 与生产主线之间的偏离时间。
尤其是在遗留系统持续进行模块化调整时,今天可能迁移一个类,明天可能调整一个包,后天又拆出新的 Maven 模块。
一个 Feature 长期不进入生产主线,未来再集成时可能产生更多适配工作。
所以,我更愿意将已经完成验证的阶段性成果及时纳入主线,而不是让大量完成的代码长期停留在不同的 Feature 分支中。
2.6 这套习惯也影响了 Spring Boot 升级
Spring Boot 升级实验采用了类似的思路。
如果我在新版本中发现某个兼容性问题,而修复方式同时适用于旧版本,那么就可以先处理这个局部问题。
验证没有破坏 Spring Boot 2.2.10 的现有行为后,将修改合入 prod。
然后继续推进升级实验。
这与直接将 Spring Boot 2.5.14 整体发布到生产环境完全不同。
前者是独立完成验证的局部兼容性调整;后者涉及整个应用的运行环境和依赖组合,需要更广泛的验证。
2.7 V14 更可能是分支演进编号,而不是十四次失败
现存升级实验分支最终编号为 V14。
最初重新调查 Git 历史时,我曾经尝试按 V1~V14 推断每一轮分别解决了什么技术问题。
但回顾自己的日常开发习惯后,我逐渐意识到,这个理解可能并不准确。
当时生产主线持续变化。
其他业务功能不断进入生产,部分兼容性修复也可能提前合入 prod。
为了让升级实验始终基于相对新的生产代码,我很可能多次放弃旧实验分支,再从最新的生产基线重新建立分支。
这与我日常采用的小步开发方式相吻合。
因此,我现在更倾向于认为,V14 主要反映实验分支的多次重建,而不是十四次独立的兼容性故障。
但这仍然只是根据我的开发习惯、历史回忆和现存 Git 记录得出的推断。
早期分支已经清理,我无法逐一证明 V1~V13 为什么建立。
所以,本文不会将 V14 描述成十四次升级失败,也不会为这些编号编造已经找不到的故障过程。
三、重新调查 Git:我究竟保留了什么?
3.1 V14 并不是只有一个升级提交
通过 Codex 对现存 Git 历史进行调查,我确认最终保留的 V14 分支包含四个独有提交。
它们的关系如下:
text
BASE-01
│
└── UPG-01 Spring Boot 2.5.14 升级补丁
│
└── UPG-02 Nacos Discovery 依赖调整
│
└── UPG-03 Elasticsearch 相关修改
│
└── UPG-04 Portal CORS 修改以上是经过脱敏的提交关系示意图,编号与原始 Git 历史中的提交对应,但不代表真实 Git Hash。
其中,UPG-01 是核心框架升级提交。
后面三个提交分别涉及 Nacos Discovery、Elasticsearch 和 Portal CORS。
这些代码能够证明当时确实继续处理过相关模块的适配。
不过,仅凭这些提交还不能确定对应修改在当时分别经过了哪些运行验证。
3.2 AuthorDate 和 CommitDate 为什么不同?
调查中还发现一个值得注意的细节。
核心升级提交 UPG-01 的日期为:
text
AuthorDate: 2025-12-25
CommitDate: 2026-03-03而它的直接父提交 BASE-01,已经是 2026 年 3 月 2 日的生产代码基线。
这与我平时从较新的生产分支重新建立实验分支、再应用已有修改的开发方式相吻合。
因此,我推测 V14 可能经历过类似的分支重建或补丁重新应用过程。
不过,具体每次重建发生在什么时候、出于什么原因,仍然无法仅凭现存提交日期确认。
Git 保存了当前可见的代码关系,但并没有完整保留我当时每一次调整实验基线的原因。
四、技术改造:哪些问题能够找到代码证据?
虽然部分原始异常日志没有完整保留,但 Git 历史仍然留下了不少可以确认的代码修改。
这次复盘还补充了一项原先缺少的证据:在当前生产分支和 V14 实验分支分别执行 Maven Dependency Tree,取得了两个版本的实际依赖解析结果。
因此,本章涉及依赖版本时,会优先使用这两份解析结果,而不是只根据 POM 属性或 Spring Boot 默认版本进行判断。
4.1 Spring Boot 与 Spring Cloud 版本调整
核心升级提交 UPG-01 对 Maven 依赖进行了较为集中的修改。
主要版本变化如下:
diff
- Spring Boot 2.2.10.RELEASE
+ Spring Boot 2.5.14
- Spring Cloud Hoxton.SR12
+ Spring Cloud 2020.0.6
+ Spring Cloud Alibaba BOM 2021.1Dubbo 仍然保留在 2.7.8。
这说明我当时并没有尝试一次性更新所有基础组件。
我的想法是先调整 Spring Boot、Spring Cloud 及相关依赖组合,观察系统是否能够在保留 Java 8 和部分旧组件版本的情况下继续运行。
这里需要注意,Spring Boot 升级并不是简单修改一个 Maven 版本号。
它还会影响 Spring Framework、Spring Cloud、依赖管理、自动配置及相关第三方组件。
因此,即使能够编译成功,也不能直接说明升级已经完成。
4.2 OpenFeign:参数名称显式化
这是目前能够找到比较明确证据的一次兼容性调整。
相关脱敏提交编号为 FIX-01。
原来的 Feign 接口存在这样的代码:
java
@RequestParam Long id以及:
java
@PathVariable String orderId后来调整为:
java
@RequestParam(value = "id") Long idjava
@PathVariable(value = "orderId") String orderId改动本身很小。
但它与当时的升级实验有关。
根据我的回忆,原有 Spring Boot 2.2.10 及相关依赖组合下,未显式指定参数名称的写法能够正常工作。
但在升级实验的新依赖组合中,参数名称推断没有达到预期。
显式指定名称后,问题得到处理。
随后,我确认这种写法在旧版本下也能够正常使用,因此将该修改提前合入生产主线。
这正好体现了我当时的升级策略:
能够同时兼容新旧版本的局部修复,可以先完成验证并进入生产,而不必等待整套框架升级完成。
不过,这里不能直接得出“Spring Boot 2.5 必须为所有 @PathVariable 显式填写 value”的结论。
参数名称解析还可能受到 Spring Cloud OpenFeign、Java 编译参数以及具体接口声明方式的影响。
本次重新检查 POM 时,我确认新旧两个根 POM 都继承了 spring-boot-starter-parent,并且在 maven-compiler-plugin 中显式配置了:
xml
<parameters>true</parameters>因此,不能简单地将问题归因于项目根 POM 没有开启 Java 参数名称保留功能。
如果以后重新调查,应该进一步检查具体子模块是否覆盖了编译插件配置、编译产物是否保留参数名称,以及当时使用的 Spring Cloud OpenFeign 版本和参数解析行为。
目前能够确认的是,显式填写注解参数名解决了当时遇到的问题,但完整触发条件尚未恢复。
4.3 Ribbon 到 Spring Cloud LoadBalancer
在 Console 和 Portal 的 Maven 配置中,我还能找到一项明确的依赖调整。
原来的服务调用配置使用:
xml
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>升级分支则引入:
xml
<artifactId>spring-cloud-starter-loadbalancer</artifactId>此外,Console 中还存在针对部分内部依赖的 Ribbon Starter 排除配置。
这与 Spring Cloud 2020.0 系列的组件演进有关。
当时升级 Spring Cloud 后,原有 Ribbon 相关依赖需要重新处理。
从 Git 和 POM 中能够确认的是,Console 和 Portal 的依赖声明确实发生了变化。
但这只能证明依赖层面的适配,不能替代服务调用的端到端验证。
4.4 Nacos:通过 Dependency Tree 核实实际版本
V14 中有一笔单独的 Nacos Discovery 依赖修改:
UPG-02
该提交整理了 Discovery 相关依赖配置。
最初调查时,我主要通过 POM 和 BOM 判断依赖变化,但仅凭版本声明并不能确定 Maven 最终使用哪个 Nacos Client。
为此,我在本次复盘中分别切换到当前生产分支和 V14 实验分支,执行 Maven Dependency Tree。
升级前后的实际依赖版本
最终得到以下结果:
| 组件 | 当前生产分支 | V14 实验分支 |
|---|---|---|
| Nacos Discovery Starter | 2.2.3.RELEASE | 2021.1 |
| Nacos Client | 1.3.3 | 1.4.1 |
| Nacos Common | 1.3.3 | 1.4.1 |
| Nacos API | 1.3.3 | 1.4.1 |
因此,这次实验中能够通过 Maven Dependency Tree 确认的 Nacos Client 版本变化为:
1.3.3 → 1.4.1
当前生产分支的主要依赖路径为:
text
common-biz
└── common-infra
└── nacos-discovery-starter:2.2.3.RELEASE
└── nacos-client:1.3.3V14 在包含相关 Reactor 模块的依赖解析中,公共模块下显示了 Discovery Starter 和 Nacos Client:
text
common
├── nacos-discovery-starter:2021.1
└── nacos-client:1.4.1以上省略了完整 GroupId,并统一使用脱敏模块名称。
本次复盘以最终 Maven 依赖解析结果作为版本对照依据,不再根据历史遗留的 Properties 配置推断实际使用的客户端版本。
为什么第一次依赖树出现了不属于 V14 的模块?
这次调查还有一个值得记录的细节。
第一次单独检查 V14 的 Portal 模块时,依赖树中出现了后来模块治理产生的 common-biz 和 common-infra。
但这些并不是 V14 当时的公共模块结构。
我怀疑 Maven 使用了本地仓库中同版本号的内部构件。
项目的部分内部 Maven 构件采用固定版本号,而不是 SNAPSHOT 版本。
因此,如果不同分支中使用了相同的内部构件版本号,而本地仓库已经存在其中一个分支安装的构件,就可能出现依赖路径与当前源码模块结构不一致的问题。
随后,我重新执行 Maven Dependency Tree,并增加 -am 参数,让相关 Reactor 模块一起参与解析。
这次依赖路径恢复为 V14 当前源码对应的模块结构。
不过,Nacos Client 仍然解析为 1.4.1。
因此,这次检查纠正的是依赖路径,而不是 Nacos Client 的最终版本。
这也是我后来更加重视 Maven 实际解析结果的原因。
同一个项目中,POM 声明、传递依赖以及本地仓库中的内部构件,都可能影响对最终依赖关系的判断。
Spring Cloud Alibaba 的版本组合
Spring Cloud Alibaba 官方版本说明中,2021.1 对应的参考组合为:
- Spring Cloud:
2020.0.1 - Spring Boot:
2.4.2
而 V14 实际使用的是:
- Spring Cloud Alibaba:
2021.1 - Spring Cloud:
2020.0.6 - Spring Boot:
2.5.14
因此,V14 的版本组合并非官方版本对应表中列出的组合。
这不等于一定存在兼容性故障,但如果重新开展实验,值得优先检查。
历史 gRPC 异常
此前的历史记录中,还出现过 Nacos gRPC 连接检查超时、客户端未完成连接等异常线索。
但本次依赖树确认,当前生产分支与 V14 分别使用 Nacos Client 1.3.3 和 1.4.1,都属于 Nacos 1.x 系列。
Nacos 2.x 客户端才引入基于 gRPC 的长连接机制。
此外,Nacos 官方说明 2.0.x 服务端可以兼容 1.x 客户端,因此 Server 2.0.3 与 Client 1.4.1 在通信协议层面存在官方兼容依据。
所以,不能将此前的 gRPC 异常直接归因于这两个已经确认的客户端版本组合。
相关异常可能来自其他版本实验或不同的实际运行环境,需要另行调查。
4.5 CORS 与 Spring Security 的适配
升级 Spring Boot 2.5.14 的过程中,我遇到了服务无法正常启动的问题,并对 Console 和 Portal 的 CORS 配置进行了调整。
原有 Console 使用 CorsFilter Bean 配置跨域,其中同时存在:
java
config.addAllowedOrigin("*");
config.setAllowCredentials(true);升级后,我将这部分配置调整为 WebMvcConfigurer.addCorsMappings,使用 allowedOriginPatterns("*"),并保留允许凭证的配置。
与此同时,还调整了允许的 HTTP 方法、预检缓存时间,以及部分 Spring Security 配置。
这些变化能够从脱敏提交 UPG-01 的代码差异中确认。
Spring Framework 5.3 对 allowCredentials(true) 与通配来源配置增加了校验限制,两者不再允许直接通过 allowedOrigins("*") 组合使用。
这为当时的修改提供了明确的技术依据。
另外,Console 原有 Security 配置中,曾将 CorsFilter 插入 Spring Security 过滤器链。
升级提交移除了这部分旧 Filter 的注入及插入代码,同时取消了 CrossInterceptor 的组件注册。
这里的 CorsFilter 是过滤器,而 CrossInterceptor 是拦截器,两者属于不同的组件。
这说明当时进行的并不只是一次 CORS 方法名称替换,而是对原有 CORS 接入方式进行了一定调整。
Portal 后续也进行了对应修改:
diff
- .addAllowedOrigin("*")
+ .addAllowedOriginPattern("*")这项修改保留在提交 UPG-04 中。
根据我的回忆,当时进行这些修改的直接目的是解决服务无法正常启动的问题。
现存代码能够确认旧配置确实包含凭证与通配来源的组合,但原始异常堆栈没有完整保留,因此没有进一步还原具体异常触发位置。
对于本文而言,记录这次为适配 Spring Framework 5.3 而进行的 CORS 修改,就已经足够。
4.6 Portal 与 Elasticsearch 的适配
根据我的回忆,当时 Console 的适配推进得比较早。
后来继续处理 Portal 时,我遇到了 Elasticsearch 相关的问题。
现存提交 UPG-03 保留了 Elasticsearch 实体、字段映射、标识字段以及索引相关调整。
最初调查时,我注意到 Spring Boot 2.2.10 与 2.5.14 默认管理的 Spring Data Elasticsearch 版本不同。
前者为 3.2.10.RELEASE,后者为 4.2.11。
Spring Data Elasticsearch 官方迁移文档记录了从 3.2.x 到 4.x 期间的一系列变化,包括实体映射方式、查询接口及部分 API 调整。
同时,Spring Boot 2.2.10 默认管理的 Elasticsearch REST High Level Client 是 6.8.12,而 Spring Boot 2.5.14 默认管理的是 7.12.1。
但我在两个项目根 POM 中都找到了:
xml
<elasticsearch.version>7.6.2</elasticsearch.version>因此,仅根据 Spring Boot 默认依赖版本,并不能准确判断项目最终使用哪个 Elasticsearch 客户端。
通过 Maven Dependency Tree 确认实际版本
在本次复盘中,我分别在当前生产分支和 V14 分支执行了 Maven Dependency Tree。
结果如下:
| 组件 | 当前生产分支 | V14 实验分支 |
|---|---|---|
| Spring Boot Data ES Starter | 2.2.10.RELEASE | 2.5.14 |
| Spring Data Elasticsearch | 3.2.10.RELEASE | 4.2.11 |
| Elasticsearch REST High Level Client | 7.6.2 | 7.6.2 |
| Elasticsearch REST Client | 7.6.2 | 7.6.2 |
| Elasticsearch Core | 7.6.2 | 7.6.2 |
| Elasticsearch Transport Client | 7.6.2 | 本次依赖树未出现 |
当前生产分支的主要依赖关系为:
text
spring-boot-starter-data-elasticsearch:2.2.10.RELEASE
└── spring-data-elasticsearch:3.2.10.RELEASE
├── transport:7.6.2
└── elasticsearch-rest-high-level-client:7.6.2
└── elasticsearch-rest-client:7.6.2V14 的主要依赖关系为:
text
spring-boot-starter-data-elasticsearch:2.5.14
└── spring-data-elasticsearch:4.2.11
└── elasticsearch-rest-high-level-client:7.6.2
└── elasticsearch-rest-client:7.6.2这两份依赖树解决了此前最重要的版本疑问。
Spring Data Elasticsearch 确实从 3.2.10.RELEASE 升级到了 4.2.11,但 Elasticsearch REST 客户端仍然保持 7.6.2。
另外,当前生产分支的依赖树中存在 Transport Client,而 V14 的本次依赖树没有出现。
但依赖树包含 Transport Client,并不代表业务代码实际通过它访问 Elasticsearch。是否使用还需要通过具体客户端配置和调用代码判断。
为什么这个版本组合值得关注?
从 Spring Data Elasticsearch 官方版本对应关系来看:
| Spring Data Elasticsearch | 官方对应 Elasticsearch 版本 |
|---|---|
| 3.2.x | 6.8.12 |
| 4.0.x | 7.6.2 |
| 4.1.x | 7.9.3 |
| 4.2.x | 7.12.0 |
由此可见,当前生产分支的 Spring Data Elasticsearch 3.2.x 搭配客户端 7.6.2,本身就与官方版本对应表不同。
而 V14 的客户端 7.6.2,恰好是 Spring Data Elasticsearch 4.0.x 官方对应的版本,但 V14 实际采用的 Spring Data Elasticsearch 已经是 4.2.11。
因此,这次升级留下了一项比较明确的兼容性调查方向:
Spring Data Elasticsearch 4.2.11 与 Elasticsearch REST Client 7.6.2 之间,是否存在客户端 API 或运行行为方面的不兼容?
另外,Spring Data Elasticsearch 4.0 开始弃用基于 TransportClient 的相关支持,推荐向 REST 客户端迁移。
不过,4.x 开始弃用 TransportClient,并不等于所有 4.x 版本都已经彻底删除了该组件。
结合官方迁移文档,更值得关注的是:
- 从 3.2.x 到 4.0.x,实体映射、查询接口等方面发生了哪些变化?
- 从 4.0.x 到 4.1.x,再到 4.2.x,哪些 API 开始依赖更新版本的 Elasticsearch 客户端?
- 项目原有实体和索引代码是否需要适配这些变化?
从提交 UPG-03 保留的修改内容来看,这些方向都值得检查。
不过,依赖树只能证明 Maven 最终选择了哪些 JAR,不能证明当时具体发生了什么异常。
因此,我现在可以明确记录版本组合,但仍然不能将某个版本冲突写成已经确认的故障根因。
Elasticsearch 服务端版本
本次复盘中,我还检查了 Elasticsearch 服务端。
服务返回的信息显示:
text
Elasticsearch Server: 7.6.2
Build Type: Docker
Lucene: 8.4.0这里的版本来自本次实际检查,而不是当年的部署记录。
它与当前生产分支和 V14 的客户端版本一致,但不能仅凭这次响应,就倒推出 2025 年底实验时的服务端版本。
如果以后重新开展升级实验,应当结合当时或重新搭建的服务端环境,验证实际客户端版本与 Spring Data Elasticsearch 之间的兼容性。
4.7 不是所有同期修改都属于框架升级
Git 调查中还发现了一些 DTO、Lombok、序列化及 Maven 编译配置的修改。
例如 FIX-02,其提交说明中提到了方便升级 Spring Boot 2.5.x。
部分 DTO 调整了 Lombok 构造器注解,某些类型增加了 Serializable。
后续 FIX-03 又恢复了部分构造器注解。
这些修改确实存在,但并不足以证明 Spring Boot 2.5 与 Lombok 必然存在不兼容问题。
此外,项目在同一时期还进行过数据库访问层、连接池以及公共组件的治理。
这些工作可能改善系统维护条件,但不能因为时间相近,就全部归因于 Spring Boot 升级。
五、为什么最终没有进入生产?
5.1 局部修复和整体升级是两回事
回头看,这次实验中有一些修改确实可以独立验证。
例如 Feign 参数显式化。
它可以先在旧版本正常运行,再进入生产主线。
但整个 Spring Boot 版本切换并不是同一回事。
要真正切换至 2.5.14,还需要考虑应用自身的完整运行,以及与其他内部系统之间的交互。
根据我的回忆,当时 Console 大概率已经能够启动,也进行过部分接口调用验证。
但微服务之间的调用究竟验证到了什么程度,我现在没有把握确认。
因此,我不能把应用启动成功写成整个系统已经完成升级验收。
5.2 跨系统调用属于验证范围,但不能直接当成停止原因
项目需要与多个内部系统进行数据交互和服务调用。
这意味着,升级实验除了检查本项目内部功能,还需要考虑既有服务调用是否保持正常。
不过,Spring Boot 版本升级本身并不会自动改变既有的 Dubbo 协议或 Feign 接口契约。
真正需要关注的是框架和依赖组合变化可能引起的运行行为差异。
例如,Spring Cloud LoadBalancer 替换 Ribbon 后,服务选择和调用流程是否正常;Nacos 客户端和 Discovery 依赖变化后,服务注册与发现是否受到影响。
这些都属于可以设计测试进行验证的问题。
但目前没有完整的测试记录,能够证明当时具体是哪一项跨系统验证阻碍了升级。
所以,它们可以被列为升级需要覆盖的验证范围,却不能直接写成项目停止推进的原因。
5.3 V14 只是最后留存的实验分支
我并没有正式决定将 V14 作为升级实验的最终版本。
它只是当时最后保留下来的分支。
如果后来还有时间继续推进,我完全可能重新创建 V15、V16,或者使用其他方式继续实验。
但项目还有日常业务开发,我也在持续进行模块化和公共代码治理。
后来,这项升级实验没有再继续推进。
现在重新评价,Spring Boot 2.5.14 已经停止常规开源维护、长期迁移仍需继续升级,以及不同框架版本组合需要进一步验证,都是值得考虑的现实因素。
但我不能据此倒推出,这些就是当时停止实验的直接原因。
至于当时究竟是因为时间安排、版本选择、验证范围,还是继续投入的收益判断,现在已经很难准确区分。
因此,我更愿意将这次经历描述为:
一次逐渐搁置、最终没有进入生产的框架升级探索,而不是一次已经证明技术上不可行的升级失败。
六、与升级实验并行的内部模块治理
6.1 模块治理早于升级实验,并与之并行
这次调查还纠正了我对历史过程的一种猜测。
最初,我曾经怀疑,是不是因为 Spring Boot 升级涉及较多因素,后来才逐渐将更多精力投入内部模块调整。
但 Git 记录并不支持这种简单的先后关系。
早在 2025 年 11 月,项目就已经出现旧模块清理。
2025 年 12 月,又进行了 Maven 模块命名、目录迁移和公共组件整理。
同一时期,我也在开展 Spring Boot 升级探索。
这说明模块治理早于最终保留的升级实验,并且与升级工作存在时间上的重叠。
两项工作原本就在并行推进,而不是 Spring Boot 升级失败以后才开始模块治理。
6.2 2026 年 5~7 月的公共模块拆分
虽然内部治理早已开始,但 Git 记录显示,2026 年 5~7 月确实出现了一轮比较集中的公共模块拆分。
原有公共模块逐步整理为职责更明确的 Maven 模块,例如:
text
project-payment-commons
├── common-core
├── common-utils
├── common-mapper
├── common-biz
├── common-pojo
├── common-infra
├── common-integrations
└── common-slslogger我进行这种调整,并不是为了追求更多的 Maven 模块。
真正的目标是让代码归属与依赖方向更加明确。
例如:
DateUtils、FileUtil 及相关测试进入工具模块。
Mapper 接口、XML 等数据访问组件进入相应模块。
请求响应对象、DTO 等按照职责重新整理。
相关基础设施能力也根据实际职责进入对应模块。
这些修改都可以通过历史 POM 和文件迁移记录进行验证。
但模块数量增加,并不能自动证明架构质量已经提高。
真正需要关注的是代码职责是否更加清晰、依赖方向是否合理,以及相关功能能否继续通过验证。
6.3 为什么内部治理可以持续推进?
相比整套 Spring Boot 升级,内部模块治理通常更容易限定修改范围。
例如移动一个工具类,我可以明确原始位置、目标位置、相关依赖和对应测试。
完成迁移后,通过编译、UT 或必要的集成验证,确认原有行为没有变化,再将修改及时合入生产主线。
这样的工作更适合小步推进。
当然,这并不意味着模块拆分没有风险。
但相比需要考虑完整框架依赖组合的升级,部分内部调整能够建立相对明确的修改和验证边界。
从 Git 历史看,Spring Boot 升级实验没有继续推进之后,这类治理仍然持续开展,并在后续形成了比较集中的模块拆分。
但我不能据此推断自己当时作出过一次明确的“放弃框架升级、转向模块治理”的决策。
两项工作本来就同时存在,只是后来继续推进的程度不同。
6.4 UT/IT 是原有工程实践的逐步引入
内部治理还包括 JUnit 5 迁移、UT/IT 命名及 Maven 测试执行配置整理。
不过,UT/IT 并不是我在这次 Spring Boot 升级过程中才设计出来的。
这套测试分类和执行边界,我此前已经在其他项目中使用多年。
后来,只是在合适的时间逐步将它引入当前系统。
相关设计已经单独写在《别把所有测试都叫 Test:我如何用 UT、IT 和 SmokeIT 划分执行边界》中,本文不再展开。
这里仅说明,模块化治理不仅涉及代码目录和 Maven 结构,也需要考虑如何保留和组织相应的测试能力。
七、Git 还在,为什么实验过程仍然难以还原?
7.1 代码记录与实验记录并不是一回事
这次复盘中,最让我感到遗憾的事情,并不是 Spring Boot 最终没有升级成功。
而是重新查看历史时,我发现自己已经很难准确回答一些问题。
例如:
V2 为什么创建?
V3 为什么需要重新建立?
某次修改究竟针对什么异常?
当时只是编译成功,还是已经启动并调用过接口?
哪些修改提前进入了生产?
哪些修改只是实验过程中暂时采用的方案?
Git 中保留了部分代码和提交,却没有完整保留这些问题的答案。
尤其是多次 cherry-pick、重建分支以及删除早期分支之后,现存提交很难再还原全部实验过程。
这与我前面介绍的分支管理方式也有一定关系。
cherry-pick 能够帮助我控制代码集成范围,但默认不会保留一条容易直接追踪的原始提交关系。
重新建立实验分支能够使用较新的生产基线,却可能让早期实验过程随着旧分支清理而逐渐消失。
这不意味着应该简单否定原来的开发流程。
但它说明,我当时没有为这套流程建立足够完整的历史追踪机制。
7.2 如果重新设计 Git 记录方式
现在回头看,我认为还可以改进提交来源的追踪方式。
例如,Git 提供了:
bash
git cherry-pick -x <commit>对于没有冲突的 cherry-pick,-x 会在新提交的消息中追加来源提交的哈希。
这有助于以后查找不同分支之间的提交关系。
但它也有局限。
Git 官方文档说明,这种自动追加来源信息的行为针对无冲突的 cherry-pick。
而且,即使保留了来源哈希,如果原始提交失去所有引用并在仓库清理后被删除,也可能无法再直接查看原始提交内容。
对于需要长期保存的实验分支,我还可以考虑在删除分支前创建归档标签,例如:
bash
git tag archive/springboot2.5-v14 <commit>这样可以为对应提交保留 Git 引用,避免重要的历史提交仅仅因为分支被清理而失去引用。
如果需要调查不同哈希的等价提交,还可以尝试使用:
bash
git cherry
git patch-id另外,对于持续演进的升级实验,也可以考虑通过 rebase 将实验提交重新应用到较新的生产基线上,而不是每次都建立新的 V 号分支。
但 rebase 同样会改变被重写提交的哈希。
这些 Git 机制能够改善代码历史追踪,却不能自动恢复当时为什么创建分支、为什么修改依赖,以及具体完成了哪些验证。
因此,我认为真正缺少的并不是某一条 Git 命令,而是对实验过程的持续记录。
7.3 如果重新开展类似实验,需要记录什么?
现在回头看,我认为不需要为每次实验编写一篇长文档。
只需要保存少量关键信息:
| 记录内容 | 需要回答的问题 |
|---|---|
| 实验基线 | 本次基于哪个生产提交? |
| 建立原因 | 为什么创建或重建分支? |
| 修改内容 | 保留、增加或删除了哪些修改? |
| 问题现象 | 实际遇到了什么异常? |
| 验证结果 | 编译、启动、接口调用分别验证到什么程度? |
| 后续处理 | 修复进入生产、继续实验,还是暂时搁置? |
如果当时能够持续保存这些信息,今天就不需要再根据 V14 和少量 Git 提交,反复推断过去的开发过程。
分支版本号可以帮助区分不同阶段,但不能代替实验记录本身。
八、AI 如何帮助我重新发现这段工程历史?
8.1 这篇文章最初并不在我的计划中
这次复盘最初只是一次普通的 Wiki 和 Git 历史整理。
我原本希望回顾过去开发过哪些功能、完成过哪些工程改造,以及有哪些技术工作值得保留记录。
在这个过程中,AI 提醒我:
即使某次升级最终没有上线,探索过程中遇到的问题和已经完成的修改,仍然可能值得记录。
这让我重新想起了 Spring Boot 2.5.14 的 V14 分支。
如果没有这个提醒,我未必会专门回头调查这次实验。
所以,AI 在这篇文章中的第一个作用,并不是生成正文,而是提醒我发现一段值得重新审视的工程经历。
8.2 从手工 Git 调查,到让 Codex 直接分析仓库
最开始,我和 AI 的协作方式比较简单。
AI 根据已有信息提出调查思路,再给出 Git 命令。
我在本地执行命令,将输出结果复制回来,AI 再继续分析。
这种方式可以工作,但当需要检查的提交、分支和文件越来越多时,往返成本也逐渐增加。
后来,我改为让 Codex 直接读取本地 Git 仓库。
我要求它只进行只读调查,不修改代码、不切换分支、不执行构建,也不对仓库进行提交操作。
它可以直接搜索历史提交、检查 Diff、比较分支关系,并尝试识别 cherry-pick 或重新应用补丁产生的等价修改。
这让原来需要反复手工执行的调查工作,能够更加系统地进行。
但更有意思的事情,并不是 AI 找到了多少提交。
而是调查结果不断让我重新思考过去的经历。
8.3 AI 的提问,让我重新发现自己已经遗忘的细节
这次讨论中,最典型的问题是 V14。
最初,我们很容易把 V14 理解成十四轮技术问题排查。
但随着 AI 不断询问分支如何创建、为什么重建、兼容性修改如何进入生产,我开始联想到自己长期以来的开发习惯。
我平时就习惯小步开发、尽早集成,并且经常从最新生产基线创建新的 Feature 分支。
这让我意识到:
V14 可能并不意味着十四次技术失败,而是生产主线不断变化,导致升级实验分支被多次重新建立。
这个解释不是 AI 独立从 Git 中计算出来的。
它来自 AI 的追问、我的回忆,以及现存 Git 记录之间的相互印证。
另一个例子是模块化治理。
最初,我曾怀疑自己是不是在 Spring Boot 升级搁置后,才逐渐将更多精力投入内部模块调整。
但后来通过 Git 调查发现,模块治理实际上早于升级实验,两项工作一直存在交集。
这次,历史记录反过来纠正了我的推测。
还有 cherry-pick。
最初,AI 将我的操作理解成“从 Feature 中选择某几个提交发布”。
但这并不是我的真实意思。
我实际采用的是:将一个 Feature 所需的完整提交序列,按照顺序 cherry-pick 到目标分支,避免因为直接合并测试分支而带入其他不相关功能。
经过反复解释,最终才把这套开发流程还原清楚。
这些例子让我意识到:
AI 的价值不只是回答我已经提出的问题,有时还在于提出那些我原本没有意识到应该追问的问题。
某些事情并不是我完全不知道。
只是随着时间过去,我没有主动想起,也没有意识到它们可能影响对历史技术决策的理解。
而 AI 的追问,恰好帮助我重新建立了这些联系。
8.4 AI、个人回忆和历史记录,需要互相验证
这次复盘也让我更加清楚地认识到三种信息来源的区别。
第一种是 Git、Wiki 等历史记录。
它们能够证明某个文件发生过修改、某个依赖版本发生过变化,或者某个模块在什么时候被拆分。
但 Git 并不会自动告诉我,当时为什么作出这个决定。
第二种是我自己的回忆。
我能够解释当时为什么使用 cherry-pick、为什么偏好小步集成,以及为什么将能够兼容旧版本的修改提前纳入生产。
但对于已经过去较长时间的具体异常、实验顺序和停止原因,我也可能记不清楚。
第三种是 AI 的分析。
AI 能根据现有信息提出解释,发现资料之间的联系,还能提醒我去寻找新的证据。
但如果它误解了开发背景,也可能得出错误判断。
因此,我不会因为某个解释来自 AI,就认为它正确。
同样,我也不会因为某件事来自自己的回忆,就认为完全不需要验证。
我更希望采用这样的方式:
AI 提出问题和假设,我补充经历与判断,再通过历史记录寻找支持或反驳的证据。
如果证据不足,就保留推断性质,而不是强行得出确定结论。
8.5 AI 还能帮助组织 Git 提交
除了事后的历史调查,我后来在日常开发中还发现了 AI 的另一项实用能力。
以前开发一个功能时,我可能修改多个 Java 类、资源文件和测试代码。
等整个功能完成、验证通过后,再统一提交一次。
但后来使用 AI 辅助 Git 提交时,我发现,它有时能够根据代码之间的职责和依赖关系,将我原本准备一次提交的修改拆成多个逻辑清楚的提交。
例如,我曾开发过一个基于 Excel 的交易对账功能。
渠道方提供一份 Excel,通过 Type 字段区分不同交易类型,例如入账、VCC(虚拟信用卡支付)和 eCheck。
我需要解析这份 Excel,再分别查询自己系统中的交易记录,按业务类型进行比较,并识别双方数据中的差异。
从需求角度看,它可以被理解为一个完整的对账功能。
但从代码角度看,可能涉及渠道文件解析、内部数据查询、数据对象、比较逻辑以及对应测试。
以前我可能把这些修改统一提交。
而 AI 可以尝试将它们整理成更细的逻辑单元。
例如:
text
Commit 1:增加渠道 Excel 解析能力
Commit 2:增加内部交易数据查询能力
Commit 3:实现入账数据比对
Commit 4:实现 VCC 数据比对
Commit 5:实现 eCheck 数据比对这里只是为了说明拆分思路,不代表当时 AI 一定按照这个顺序提交。
真正有价值的是,AI 能够分析这些修改之间的依赖关系,并尝试形成职责相对清晰的提交结构。
但我并不认为提交越多越好。
如果拆分破坏了功能完整性,或者中间提交无法编译,那么这种拆分反而会增加维护成本。
我更关注的是:
每个提交是否具有明确的修改目的,以及相关依赖是否得到正确保留。
这种能力与我原有的小步开发、cherry-pick 集成习惯并不冲突。
AI 可以帮助我组织一次开发中的变更,而一个 Feature 需要进入测试或生产时,我仍然会按照正确顺序集成它所需的完整提交序列。
8.6 如果 AI 能参与开发过程记录,结果可能会不同
回到这次 Spring Boot 升级实验。
我现在最大的遗憾之一,是早期实验过程没有留下完整记录。
但如果把 AI 放到日常开发过程中,而不是等到事后才进行调查,情况可能会有所不同。
例如,每次重新建立升级分支时,让 AI 根据真实 Git Diff、编译日志、运行结果和我的说明,整理一份简短记录。
记录本次基于哪个生产提交,为什么重建分支,保留了哪些修改,解决了什么问题,还有哪些验证没有完成。
这些工作不需要 AI 替我作出技术决策。
它只需要把当时实际发生的事情整理下来,再由我检查和确认。
这样,即使后来删除了早期实验分支,仍然能够保留必要的技术背景。
所以,这次经历让我看到,AI 可以参与的工程工作其实是连续的:
从提出问题、分析方案,到辅助修改代码、组织提交,再到保存实验记录,以及事后的历史调查。
相比事后依靠大量 Git 提交推断过去发生了什么,我更希望把记录工作提前到开发过程中。
九、结语:一次没有上线的升级,留下了什么?
这次 Spring Boot 升级最终没有进入生产。
原有业务系统仍然保留 Spring Boot 2.2.10.RELEASE 的技术基线。
但重新查看历史后,我发现这次探索并不是完全没有留下成果。
部分兼容性修改已经提前进入生产主线。
最终实验分支保留了真实的依赖迁移和代码调整。
与此同时,原本就在推进的模块化和测试治理继续发展,项目内部的代码归属、依赖方向和验证方式也在逐步调整。
从技术角度看,这次经历让我更清楚地认识到:
框架升级不仅是修改 Maven 版本号,还需要理解依赖版本之间的关系,以及实际验证的范围。
这次复盘中的 Nacos 和 Elasticsearch 就是很好的例子。
最开始,我通过 POM 和 BOM 判断版本关系。
后来,实际执行 Maven Dependency Tree 才确认:
- Nacos Client 从
1.3.3变成了1.4.1。 - Spring Data Elasticsearch 从
3.2.10.RELEASE变成了4.2.11。 - Elasticsearch REST Client 仍然保持在
7.6.2。
这些结果不仅纠正了此前部分不准确的版本判断,也让后续调查有了更明确的技术方向。
但版本组合存在潜在兼容性风险,与当年实际发生过某个确定故障,并不是一回事。
从工程管理角度看,我也重新认识了 Git 的局限。
代码版本管理与实验过程记录,不能完全互相替代。
Git 可以告诉我最终保留了哪些修改,却不一定能解释每一次实验为什么开始、如何调整,以及为什么停止。
而从这次写作过程来看,我又获得了另一项认识。
最初,我并没有计划专门写一篇 Spring Boot 升级复盘。
只是整理 Wiki 和 Git 历史时,AI 提醒我,这段没有最终上线的经历也可以记录下来。
随后,AI 不断提出问题,我逐渐回忆起当时的开发习惯,再通过 Git 历史和 Maven 依赖解析验证、修正这些认识。
最终,我们并没有完整恢复当年的每一个实验步骤,也没有找到所有异常的根本原因。
但这并不妨碍我把目前能够确认的技术事实整理清楚。
能够证明的地方,就明确记录;有依据但无法完全确认的地方,就谨慎推断;缺少证据的地方,不强行补全。
我认为,这比为了让一篇技术文章看起来完整,而重新构造一段并不存在的故障排查过程更有意义。
如果以后重新开展类似的技术实验,我仍然会采用小步开发、独立验证、尽早集成的方式。
但我会尽量保留实验基线、问题现象、验证结果以及每次调整的原因。
AI 可以帮助我完成这些记录,也可以通过提问和调查,帮助我发现那些过去没有意识到值得记录的工程细节。
这就是这次未完成的 Spring Boot 升级,最终留给我的技术记录与工程认识。
附录一:关键 Git 提交记录
以下是本次复盘中使用的部分关键提交。
本表中的 Git 提交编号均已脱敏,仅用于关联正文中的技术改造与历史分析,不代表真实 Git Hash。
由于历史可能经过 cherry-pick、rebase 或重新应用补丁,提交日期不一定等于对应修改首次完成或实际验证的日期。
| 脱敏编号 | 可以确认的内容 |
|---|---|
BASE-01 | V14 核心升级补丁所基于的生产代码 |
UPG-01 | Spring Boot 2.5.14、Spring Cloud 2020.0.6、LoadBalancer 及 Console CORS/Security 等修改 |
UPG-02 | Nacos Discovery 依赖整理 |
UPG-03 | Elasticsearch 实体与索引相关调整 |
UPG-04 | Portal CORS 配置调整 |
FIX-01 | Feign 参数名称显式化,相关修改进入生产基线 |
FIX-02 | 与升级准备有关的 DTO、Lombok 调整 |
FIX-03 | 上述 DTO 调整的后续修正 |
MOD-01 | 早期 Maven 模块命名与结构整理 |
MOD-02 | 公共工具模块拆分 |
MOD-03 | Mapper 相关代码迁移 |
MOD-04 | 工具类及对应测试迁移 |
MOD-05 | 旧公共模块从根 Maven 模块列表中移除 |
TEST-01 | JUnit 5 迁移 |
这些提交主要支持文章中有关代码变化和模块演进的事实。
对于当时的具体动机、测试程度、分支重建原因和实验暂停过程,则仍需结合个人回忆判断。
附录二:Spring Boot 升级前后的依赖版本记录
为了方便以后重新调查或复现这次 Spring Boot 升级实验,我根据保留的 Maven POM 文件,以及本次重新执行的 Maven Dependency Tree,整理了升级前后的主要依赖版本。
本次对照使用以下依据:
- 当前生产分支:Spring Boot 2.2.10.RELEASE 对应的 Maven POM 和 Dependency Tree。
- 升级实验分支:Spring Boot 2.5.14 的 V14 分支,对应的 Maven POM 和 Dependency Tree。
- 检查日期:2026 年 10 月 15 日。
需要说明的是,当前生产分支来自后续持续维护的代码,已经经历了部分内部模块治理。因此,两个分支的 Maven 模块结构存在差异。
本次对照重点是框架和第三方依赖版本,而不是将两个分支全部代码结构差异视为升级带来的变化。
1. 核心框架版本
| 组件 | 当前生产分支 | V14 实验分支 |
|---|---|---|
| Java | 8 | 8 |
| Spring Boot | 2.2.10.RELEASE | 2.5.14 |
| Spring Cloud BOM | Hoxton.SR12 | 2020.0.6 |
| Spring Cloud Alibaba BOM | 未单独引入 | 2021.1 |
| Apache Dubbo | 2.7.8 | 2.7.8 |
| MapStruct | 1.3.1.Final | 1.4.1.Final |
| 负载均衡组件 | Ribbon Starter | LoadBalancer Starter |
本次实验保持 Java 8 和 Dubbo 2.7.8 不变,主要调整 Spring Boot、Spring Cloud 及相关依赖组合。
2. Nacos 实际依赖版本
以下版本来自两个分支的 Maven Dependency Tree。
| 组件 | 当前生产分支 | V14 实验分支 |
|---|---|---|
| Nacos Discovery Starter | 2.2.3.RELEASE | 2021.1 |
| Nacos Client | 1.3.3 | 1.4.1 |
| Nacos Common | 1.3.3 | 1.4.1 |
| Nacos API | 1.3.3 | 1.4.1 |
因此,Nacos Client 的实际依赖版本变化为:
1.3.3 → 1.4.1
本次版本对照以 Maven Dependency Tree 的实际解析结果为准。
此前在 POM 中出现过未影响最终依赖解析的版本属性配置,但它不代表实际加载的客户端版本,因此本文不再单独展开。
3. Spring Data Elasticsearch 与客户端版本
以下是本次复盘中最值得关注的一组依赖变化。
| 组件 | 当前生产分支 | V14 实验分支 |
|---|---|---|
| Spring Boot Data ES Starter | 2.2.10.RELEASE | 2.5.14 |
| Spring Data Elasticsearch | 3.2.10.RELEASE | 4.2.11 |
| Elasticsearch REST High Level Client | 7.6.2 | 7.6.2 |
| Elasticsearch REST Client | 7.6.2 | 7.6.2 |
| Elasticsearch Core | 7.6.2 | 7.6.2 |
| Elasticsearch Transport Client | 7.6.2 | 本次依赖树未出现 |
Spring Boot 默认依赖管理的版本与项目实际使用的版本还存在区别:
| 组件 | Spring Boot 2.2.10 默认值 | Spring Boot 2.5.14 默认值 |
|---|---|---|
| Spring Data Elasticsearch | 3.2.10.RELEASE | 4.2.11 |
| Elasticsearch REST High Level Client | 6.8.12 | 7.12.1 |
但项目根 POM 在两个分支中均保留:
xml
<elasticsearch.version>7.6.2</elasticsearch.version>本次 Dependency Tree 已确认,这一版本最终体现在实际解析出的 Elasticsearch REST 客户端依赖中。
因此,本次实验的实际组合为:
text
当前生产分支:
Spring Data Elasticsearch 3.2.10.RELEASE
Elasticsearch REST Client 7.6.2
V14:
Spring Data Elasticsearch 4.2.11
Elasticsearch REST Client 7.6.2Spring Data Elasticsearch 官方兼容性表中,4.2.x 对应 Elasticsearch 7.12.0。
V14 实际使用的客户端版本低于这个对应版本,因此有必要关注兼容性问题。
不过,仅凭版本差异不能直接判断当时发生了什么故障。
4. 其他主要第三方依赖
除核心框架及部分相关组件外,实验分支还保留了多项原有依赖版本。
| 组件 | 当前生产分支 | V14 实验分支 |
|---|---|---|
| MyBatis-Plus BOM | 3.5.15 | 3.5.15 |
| P6Spy | 3.9.1 | 3.9.1 |
| Springfox Swagger | 2.9.2 | 2.9.2 |
| Fastjson | 1.2.70 | 1.2.70 |
| Hutool | 5.6.5 | 5.6.5 |
| OkHttp | 3.14.9 | 3.14.9 |
| AWS Java SDK BOM | 1.11.1000 | 1.11.1000 |
| Apache Commons CSV | 1.9.0 | 1.9.0 |
这些数据主要来自新旧 POM 的声明或依赖管理配置,不代表已经逐一重新解析上述全部第三方组件的传递依赖。
5. 外部运行环境
部分组件不直接通过项目 POM 管理,但它们的版本同样可能影响兼容性分析。
| 组件 | 版本 | 说明 |
|---|---|---|
| JDK | Java 8 | 本次实验未升级 Java 运行时 |
| Nacos Server | 2.0.3 | 当时记录的独立服务端版本 |
| Elasticsearch Server | 7.6.2 | 本次复盘核实,升级实验时版本待确认 |
| 数据库服务端 | 未在本次对照中核实 | 以实际部署环境为准 |
其中,Nacos Server 与 Nacos Client 需要分开记录。
本次实验涉及 Nacos 客户端依赖调整,但没有证据表明同时升级了 Nacos Server。
6. 如何理解 POM、BOM 与 Dependency Tree?
这次复盘中,我最初主要通过 POM 属性和 BOM 版本判断依赖情况。
后来补充 Maven Dependency Tree,才得到了部分组件更加明确的版本结果。
因此,需要区分三种情况:
- 显式声明版本:POM 直接指定具体版本号。
- BOM 管理版本:由 Spring Boot、Spring Cloud 或其他 BOM 提供依赖管理。
- 实际解析版本:Maven 在当前项目、模块和依赖关系下最终选择的版本。
例如,Spring Boot 2.5.14 默认管理的 Elasticsearch REST High Level Client 是 7.12.1。
但 V14 最终解析出的版本为 7.6.2。
项目继承了 spring-boot-starter-parent,通过 elasticsearch.version 属性覆盖了继承体系中的默认值。
Nacos 的情况则提醒我,在 POM 中写入一个 Properties 版本属性,并不意味着它就会自动改变某个第三方依赖的最终版本。
只有属性真正被相应依赖声明或有效的版本管理配置使用时,它才会参与版本决定。
本次复盘中,实际的 Nacos Client 版本仍以 Maven Dependency Tree 为准。
此外,本次第一次检查 V14 时,依赖树中出现了后来模块治理产生的内部模块。
由于这些内部模块采用固定版本号,而不是 SNAPSHOT 版本,我怀疑 Maven 当时复用了本地仓库中来自其他分支的同版本构件。
随后使用 -am 让相关 Reactor 模块参与解析,才取得与 V14 源码结构一致的依赖路径。
这个经历也说明,调查历史分支时,除了核对 POM,还需要注意 Maven 是否实际使用了当前分支的内部模块。
当然,Dependency Tree 只能证明当前构建模型的依赖解析结果,不能自动证明这些组件在历史运行环境中完成了哪些测试。
如果以后需要更精确地复现某个历史版本,还应保存对应 Git 基线、Maven 配置、依赖解析结果及必要的运行记录。
附录三:官方技术资料
以下资料用于核对本文涉及的框架行为及版本对应关系。
- Spring Framework 5.3:CorsConfiguration API
- Spring Boot 2.2.10:Dependency Versions
- Spring Boot 2.5.14:Dependency Versions
- Spring Cloud Alibaba:版本说明
- Nacos 2.0:兼容性说明
- Spring Data Elasticsearch:3.2 到 4.0 迁移指南
- Spring Data Elasticsearch:4.0 到 4.1 迁移指南
- Spring Data Elasticsearch:4.1 到 4.2 迁移指南
- Spring Data Elasticsearch:版本对应关系
- Spring Boot 3.0:迁移指南
- Git:cherry-pick 官方文档
这些官方资料能够解释框架版本之间的技术差异,但不能替代当时实际运行环境的异常日志和测试结果。