升 Boot4 后,Boot 不再托管 spring-retry 版本。本稿讲怎么认、怎么显式钉版本或迁 Framework 原生重试、怎么验,不写 AMQP/Kafka Customizer 细节。
同系列:AMQP retry 属性怎么改、Kafka retry 属性怎么改、Session Mongo 怎么迁、CLASSIC 加载器怎么删------一句对照,不写进本稿交付。
Boot4 升后为什么 spring-retry 突然要自己写版本?
Spring Boot 4.0 Migration Guide 里 Dependency Management for Spring Retry 写得很直:
Dependency management for Spring Retry has been removed . If you still depend on Spring Retry, an explicit version is now required. Please consider moving your use of Spring Retry to Spring Framework.
也就是说:不是「禁止再用 spring-retry」 ,而是 Boot 不再在 BOM / 依赖管理里替你钉版本------portfolio 能力往 Framework 核心重试收之后,Boot 把这块托管拿掉了。以前靠 Boot BOM「偷」到 org.springframework.retry:spring-retry 版本的工程,升 4.0 后常见现象是:
| 现象 | 大致原因 |
|---|---|
Maven/Gradle 解析失败:spring-retry 无版本 |
Guide:explicit version required |
| 依赖树里版本漂移 / 被其它 BOM 顶掉 | Boot 不再统一托管 |
以为跟 AMQP RabbitRetryTemplateCustomizer 改名是同一坑 |
不是(见后文分列) |
以为跟 Kafka retry random→jitter 是同一坑 |
不是(见后文分列) |
本稿只盯 Boot 不再管 spring-retry 版本怎么认、怎么钉版本或迁 Framework;不把消息侧 Customizer / 属性文再写一遍。其它 Boot4 主题见文首内链。
认坑时先问一句:失败发生在「解析依赖」还是「改消息重试钩子」? 前者走本稿;后者去看 AMQP/Kafka 那两篇。混判最常见的代价是:Customizer 改完了,CI 仍报 spring-retry 缺版本。
先过一张迁移总览图:

图:旧 Boot BOM 托管 spring-retry → Boot4 依赖管理 Removed → 短期显式 version,或中期迁 Framework 原生重试 → 依赖树与编译冒烟。
和 AMQP / Kafka 的 retry 文是同一篇吗?
不是------同「retry」字样,主线不同,必须分列。
| 维度 | 本稿(Spring Retry 依赖管理) | AMQP(169730707) | Kafka(169145369) |
|---|---|---|---|
| Guide 落点 | Dependency Management for Spring Retry removed | Spring AMQP Retry Features · Customizer 改名 | Kafka retry 退避键等属性迁 |
| 你改什么 | pom/gradle 版本托管;或迁 Framework 注解/API | RabbitRetryTemplateCustomizer → Template/Listener Customizer |
消息消费侧 retry 属性 |
| 还能不能继续用旧库 | 短期可以,但须 显式 version | 旧 Customizer 接口要迁 | 属性键要迁 |
| 能否一套 MR「一把梭」 | 不建议 | 不建议 | 不建议 |
一句话:本稿管的是 「谁给你 spring-retry 的版本号」 ;那两篇管的是 消息中间件重试钩子/属性怎么改。升版清单里若写「凡带 retry 字样照 AMQP 文抄」,很容易漏掉 BOM 层解析失败。
旧写法长什么样(Boot3,勿再沿用)
Boot 3.x 常见心智是:业务或 starter 间接引入 spring-retry,自己不写 version ,指望 spring-boot-dependencies 管住。下面是失效示意------升 Boot4 后请按路径改,不要当可用模板。
旧依赖假设(示意 · 失效)
xml
<!-- Boot 3.x 常见:不写 version,靠 Boot BOM 托管 ------ 升 Boot4 后会解析失败或漂移 -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<!-- 无 <version>:Boot4 起不再由 Boot 依赖管理提供 -->
</dependency>
kotlin
// Gradle 旧假设示意(失效)
// implementation("org.springframework.retry:spring-retry") // 无 version
父 POM、BOM overlay、多模块里「以为 Boot 会一直带版本」的传递依赖,同样要扫------Guide 口径是 removed,不是「悄悄换了另一个坐标」。
短期怎么改:显式声明 version
若业务短期仍要 spring-retry(注解、RetryTemplate 旧 API、与存量 @Recover 等耦合深),Guide 允许继续用,但 必须自己钉版本:
xml
<!-- Boot4:仍依赖 spring-retry 时须显式 version -->
<!-- 版本号以你们与 Boot4 / Framework 的兼容矩阵为准,勿照抄第三方博文「最新号」 -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>${spring-retry.version}</version>
</dependency>
kotlin
// Gradle:显式 version(占位,勿编造具体号)
implementation("org.springframework.retry:spring-retry:${springRetryVersion}")
实务建议:
- 在父 POM /
gradle.properties集中定义${spring-retry.version},避免子模块各写各的。 - 升版 MR 里把「无 version 的 spring-retry」当成门禁项:
dependency:tree/dependencies搜一遍。 - 不要 指望再加一个「Boot Retry starter」就能恢复 BOM 托管------Guide 写的是依赖管理 removed,不是改 starter 名字。
- 若公司有内部 BOM(platform / bill-of-materials),确认里面是否仍声明
spring-retry;内部 BOM 有托管 ≠ Boot 官方又管回来了,两边版本策略要写进升版说明,避免「本地绿、流水线红」。 - 多模块工程优先在 dependencyManagement 钉一处,业务模块只写坐标不写号;Gradle 则用
platform/ 版本目录对齐同一来源。
短期钉版本能让构建先绿;中期仍建议按 Guide 评估迁 Framework。钉版本不等于「技术债清零」------只是把「谁负责选版本」从 Boot BOM 交回你们自己的兼容矩阵。
中期怎么改:迁 Spring Framework 原生重试
官方鼓励把 Spring Retry 的用法迁到 Spring Framework Resilience。证据核对过的落点:
| 项 | 值 |
|---|---|
| 启用 | @EnableResilientMethods(org.springframework.resilience.annotation,Since 7.0) |
| 声明式 | 同包 @Retryable;属性注意 maxRetries (不是旧 spring-retry 的 maxAttempts) |
| 次数含义 | total attempts = 1 + maxRetries(首次执行 + 重试次数) |
| 编程式 | org.springframework.core.retry.RetryTemplate |
声明式短例(import 齐全)
java
package com.example.retry;
import org.springframework.context.annotation.Configuration;
import org.springframework.resilience.annotation.EnableResilientMethods;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Service;
/**
* Boot4 / Framework 7:原生重试示意。
* 注意:@Retryable 用 maxRetries,不是旧 spring-retry 的 maxAttempts。
*/
@Configuration
@EnableResilientMethods
class ResilienceConfig {
}
@Service
class OrderRemoteClient {
@Retryable(maxRetries = 3)
public String fetchOrder(String id) {
// 调用下游;失败时按 maxRetries 重试
// total attempts = 1 + 3
return remoteCall(id);
}
private String remoteCall(String id) {
// ...
return id;
}
}
要点:
- 配置类上开
@EnableResilientMethods,业务方法上挂@Retryable(maxRetries = ...)。 - 从旧
@Retryable(maxAttempts = n)迁过来时,不要原样抄属性名 ------Framework 侧是maxRetries;次数语义按「1 + maxRetries」核对,避免「少重试一次 / 多重试一次」的静默偏差。 - 编程式场景用
org.springframework.core.retry.RetryTemplate(包名已是 Framework 核心,不是org.springframework.retry.support)。
关于旧 @Recover:不要按一对一搬家假设
旧 spring-retry 里常见 @Recover 做最终失败回退。证据结论:不要按「注解一对一搬家」来假设 。迁 Framework 时,失败回退请按 resilience 文档 与你们现有错误处理(显式 catch、补偿任务、消息 DLQ 等)单独设计,不要假设注解一对一搬家。
怎么快速核:依赖树里还有没有「靠 BOM 偷来的」spring-retry?
验收图:

图:对照 Guide → 搜无 version 残留 → 选显式版本或 Framework → 依赖树 + 编译/重试冒烟。
搜残留(示意命令)
bash
# 仓库全文
rg -n "spring-retry|org\\.springframework\\.retry" --glob '!**/target/**' --glob '!**/build/**'
# Maven 依赖树(看是否仍无托管版本或被其它 BOM 顶掉)
mvn -q dependency:tree -Dincludes=org.springframework.retry:spring-retry
# Gradle
./gradlew dependencies --configuration runtimeClasspath | rg spring-retry
冒烟 checklist
- 对照 Guide:能指到「Dependency Management for Spring Retry」段,确认 removed + explicit version + consider Framework。
- 构建 :无 version 的
spring-retry声明已消失;显式 version 或已删依赖并改 Framework。 - 编译 :相关模块
compile/testCompile通过。 - 行为 :选一条原先靠重试兜住的路径,确认重试次数与失败回退符合预期(迁 Framework 时尤其核对
maxRetries语义)。 - 边界 :本 MR 不 改 AMQP Customizer、不 改 Kafka retry 属性、不动 Session Mongo / CLASSIC。
补充一个最小「次数语义」手算:旧 spring-retry 里很多人习惯 maxAttempts = 3 表示一共试 3 次;Framework @Retryable(maxRetries = 3) 表示 额外重试 3 次 ,加上首次共 4 次。迁移表或单元测试里把「期望调用次数」写清楚,避免评审口头说「还是 3」实际却偏了一次。编程式 RetryTemplate 同样按 Framework 文档配置策略,不要把旧 SimpleRetryPolicy#setMaxAttempts 的数字无脑贴过去。
边界与勿踩
- 不要 把本稿写成 AMQP
RabbitRetryTemplateCustomizer迁改文------那是 169730707。 - 不要 写成 Kafka retry 属性迁改文------那是 169145369。
- 不要 抢昨日 Session Mongo 直持移除(169807868)、CLASSIC 加载器(169615117)。
- 不要 宣称旧
@Recover可在 Framework 侧原样照搬;回退路径需单独设计。 - 不要编造具体「官方推荐最新 spring-retry 版本号」------用属性占位,以兼容矩阵为准。
- 不要 把
@Retryable的maxAttempts原样抄到 Framework 注解上------应用maxRetries。 - 不要把 WebClient 超时配置扯进本稿主线(另文禁写项,此处不展开)。
- 真相源:Migration Guide、Framework Resilience。
和「半吊子升级」怎么划清
实务里最常见的失败不是「忘了写一个数字」,而是 只做了一半:
- 业务模块显式钉了 version,测试模块 / 插件模块仍靠「幻想中的 Boot BOM」;
- 或者:依赖树绿了,但代码仍混用旧
org.springframework.retry.annotation.Retryable与新org.springframework.resilience.annotation.Retryable,属性名抄错; - 或者:看见「retry」就去改 AMQP/Kafka Customizer,BOM 层解析失败原样留着;
- 或者:删了 spring-retry 依赖,却没开
@EnableResilientMethods,重试静默消失; - 或者:把
maxAttempts=3改成maxRetries=3却没核对「1 + maxRetries」语义,线上重试次数偏了。
还有两类更容易被评审漏掉:
- 只改了文档、没改构建 :README / 内部 Wiki 写了「Boot4 要显式版本」,但父 POM 的
dependencyManagement里仍没有spring-retry,子模块也没写<version>------CI 一换干净缓存就红。 - 只迁了注解、没迁编程式入口 :方法上换成了 Framework
@Retryable,角落里还有org.springframework.retry.support.RetryTemplate手工 new;两套重试栈并存,排障时日志对不上次数。
门禁上建议把「搜无 version 的 spring-retry」和「确认迁路径已落地(显式版本 或 Framework)」写成同级检查;再加一条「旧包名 org.springframework.retry 与新包名 org.springframework.resilience / org.springframework.core.retry 是否 intentional 共存」。门禁不过就别合主分支。
FAQ
Boot4 升后为什么 spring-retry 突然要自己写版本?
Migration Guide:Dependency management for Spring Retry has been removed ;仍依赖则 explicit version is now required。
和 AMQP / Kafka 的 retry 属性迁移是同一篇文章吗?
不是。本稿是 BOM/依赖管理;那两篇是消息侧 Customizer / 属性。分列,见 169730707、169145369。
还能继续用 spring-retry 吗?还是必须立刻删掉?
还能用。Guide 要求显式版本,并 鼓励 迁 Framework;不是「升 Boot4 当天必须删库」。
迁到 Spring Framework 原生重试要开什么开关?
@EnableResilientMethods;方法上 @Retryable(maxRetries = ...)。文档见 Framework resilience。编程式用 org.springframework.core.retry.RetryTemplate。
怎么快速核依赖树里还有没有靠 Boot BOM 偷来的 spring-retry?
dependency:tree / dependencies 搜 org.springframework.retry:spring-retry;再全文搜无 version 的坐标声明。
旧的 @Recover 怎么办?
不要按注解一对一搬家来假设。按 Framework 文档与现有失败处理单独设计回退路径。
小结 checklist
- 认清 Removed:Boot 不再托管 spring-retry 依赖管理
- 短期:pom/gradle 显式 version(以兼容矩阵为准,勿瞎编最新号)
- 中期:迁 Framework ---
@EnableResilientMethods+@Retryable(maxRetries=...);或RetryTemplate - 核对次数语义:total attempts = 1 + maxRetries(非旧 maxAttempts 原样照搬)
- 勿假设旧
@Recover可原样照搬到 Framework - 与 AMQP(169730707)、Kafka(169145369)分列;勿抢 Session Mongo(169807868)、CLASSIC(169615117)
- 验收:对照 Guide → 清无 version 残留 → 依赖树 + 编译/重试冒烟
- 勿把 WebClient 超时扯进主线;勿编造证据外类名/版本号
- 真相源:Migration Guide、Framework resilience 文档
按「对照 Guide → 显式钉版本或迁 Framework → 依赖树干净 → 编译/重试冒烟」改完,这类踩坑通常一次就能闭环。别在「是不是某个 starter 名字变了」上反复猜。其它 Boot4 能力见前述内链,本文不展开。