容错能力进入 spring-core:Spring Boot 4 原生重试机制全解析

容错能力进入 spring-core:Spring Boot 4 原生重试机制全解析

本文是 Spring Boot 4 系列第 09 篇 | 基于 Spring Boot 4.1.0 + Spring Framework 7.0.8 源码 | 预计阅读 20 分钟

文末附「迁移决策树」和「源码位置汇总」,落地时可直接对照。


写在前面

Spring Boot 3.x 时代,想让一个 HTTP 调用失败后自动重试 3 次,得加 spring-retry 依赖、加 spring-aspects、启动类加 @EnableRetry、方法上加 @Retryable------四件事缺一不可。Spring Boot 4 把这件事简化了:重试 API 直接搬进了 spring-core,不需要额外依赖,不需要 AOP,new RetryTemplate(policy).invoke(() -> ...) 一行搞定。

这篇基于 Spring Boot 4.1.0 + Spring Framework 7.0.8 源码,讲清楚四件事:Spring 为什么把重试搬进 spring-core、新 API 怎么用(RetryPolicy + RetryTemplate + Retryable 函数式接口)、Spring Boot 4 如何用 RetryPolicySettings 把配置桥接进去、RabbitMQ / Kafka / Redis 的自动配置里已经怎么用了。

内容速览

  • spring-retry 依赖为什么可以删了
  • spring-core 中的重试三件套:RetryPolicyRetryTemplateRetryable
  • RetryPolicySettings 配置桥接层的设计
  • RabbitMQ / Kafka / Redis 三个模块的集成方式对比
  • 手写一个带重试的 HTTP 调用(含读操作和写操作的差异化策略)
  • 从配置到重试执行的完整调用链追踪
  • 当前限制:没有 @Retryable 注解、没有限流熔断

一、spring-retry 依赖为什么可以删了

先看 Spring Boot 3.x 时代怎么做重试:

xml 复制代码
<!-- pom.xml:引入独立的重试框架 -->
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>  <!-- 还得加 AOP 支持 -->
</dependency>
java 复制代码
// 启动类加 @EnableRetry
@SpringBootApplication
@EnableRetry  // 别忘了这个!
public class OrderServiceApplication { ... }

// 业务代码加 @Retryable
@Service
public class InventoryClient {
    @Retryable(maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2.0))
    public InventoryResponse queryInventory(Long productId) {
        return restClient.get()
            .uri("/inventory/{id}", productId)
            .retrieve()
            .body(InventoryResponse.class);
    }
}

三个问题:

  1. 额外依赖 :spring-retry 是一个独立项目,和 Spring Framework 分开发版、分开维护
  2. AOP 强耦合 :重试靠 @EnableRetry + AOP 切面织入,启动变慢还不好调试
  3. 注解碎片化 :一个方法上可能同时挂着 @Retryable@Transactional@Cacheable,多个切面套在一起,执行顺序看运气

Spring Framework 7.0 把重试能力搬进了 spring-core 模块(org.springframework.core.retry 包),spring-retry 不再是必选依赖。任何 Spring Boot 4 应用天然就有重试 API 可用------不需要加依赖、不需要 @EnableRetry、不需要 AOP。

版本快照:Spring Framework 7.0.8 的 spring-core 中包含了完整的 org.springframework.core.retry 包。Spring Boot 4.1.0 在此基础上新增了 RetryPolicySettings 配置桥接类(core/spring-boot/src/main/java/org/springframework/boot/retry/),并在 RabbitMQ / Kafka / Redis 的自动配置中全面采用新的重试 API。整个 Spring Boot 4 代码仓库中没有任何对 spring-retry 的依赖声明


二、spring-core 中的重试三件套

2.1 包结构一览

spring-core-7.0.8.jar 中解出的 org.springframework.core.retry 包:

ruby 复制代码
org.springframework.core.retry/
├── RetryPolicy              # 接口:决定"要不要重试"
├── RetryPolicy.Builder      # 构建器:链式创建 RetryPolicy
├── DefaultRetryPolicy       # 默认实现
├── RetryTemplate            # 核心执行器:真正执行重试逻辑的地方
├── Retryable<R>             # 函数式接口:R execute() throws Throwable
├── RetryOperations          # 操作接口:RetryTemplate 实现的契约
├── RetryListener            # 监听器接口:重试各阶段的回调
├── RetryState               # 状态标记接口
├── RetryException           # 重试失败异常
├── support/
│   ├── RetryTask            # 支持类
│   └── CompositeRetryListener  # 组合监听器

2.2 核心 API 逐个拆解

RetryPolicy:决定"该不该重试"
java 复制代码
public interface RetryPolicy {
    boolean shouldRetry(Throwable throwable);    // 核心判断:这个异常值得重试吗?(必须实现)
    default Duration getTimeout() { ... }        // 整个重试过程的最长时间(可选覆盖)
    default BackOff getBackOff() { ... }         // 退回策略:每次重试间隔多久(可选覆盖)

    // 快捷工厂方法
    static RetryPolicy withDefaults();           // 默认:最多 3 次尝试,固定 1s 延迟
    static RetryPolicy withMaxRetries(long max); // 指定最大尝试次数
    static RetryPolicy.Builder builder();                    // 完整构建器入口
}

getTimeout()getBackOff()default 方法------只需要实现 shouldRetry(Throwable) 就能得到一个最小化的 RetryPolicy。大多数场景用 builder()withDefaults() 已经够用。

RetryPolicy.Builder:链式构建
java 复制代码
// Builder 常量定义
Builder.DEFAULT_MAX_RETRIES = 3     // 默认最多重试 3 次
Builder.DEFAULT_DELAY       = 1000  // 默认延迟 1000ms
Builder.DEFAULT_MULTIPLIER  = 1.0   // 默认倍数(固定延迟)
Builder.DEFAULT_MAX_DELAY   = Long.MAX_VALUE  // 默认无上限(约 2.92 亿年)

// 链式 API
RetryPolicy policy = RetryPolicy.builder()
    .maxRetries(5)                              // 最多重试 5 次
    .delay(Duration.ofSeconds(1))               // 初始延迟 1s
    .multiplier(2.0)                             // 每次翻倍:1s → 2s → 4s → 8s
    .maxDelay(Duration.ofSeconds(30))            // 封顶 30s
    .jitter(Duration.ofMillis(500))              // ±500ms 随机抖动
    .includes(IOException.class, TimeoutException.class)  // 只在这些异常时重试
    .excludes(IllegalArgumentException.class)            // 排除这些异常
    .predicate(ex -> ex.getMessage().contains("retryable"))  // 自定义条件
    .build();

这个 Builder 把原来分散在 spring-retry 里好几个类(SimpleRetryPolicyExponentialBackOffPolicyExceptionClassifierRetryPolicy)的配置收拢到一处。

RetryTemplate:执行重试
java 复制代码
public class RetryTemplate implements RetryOperations {

    public RetryTemplate()                          // 空构造
    public RetryTemplate(RetryPolicy retryPolicy)   // 带策略构造

    // 核心方法:执行一个 Retryable 任务
    public <R> R execute(Retryable<R> retryable) throws RetryException;

    // 便利方法:传 Supplier(无需实现 Retryable 接口)
    public <R> R invoke(Supplier<R> supplier);
    public void invoke(Runnable runnable);

    // 设置监听器
    public void setRetryListener(RetryListener listener);
}

RetryTemplate 内部执行流程:

  1. 调用 retryable.execute() 执行业务逻辑
  2. 如果抛出异常 → 问 RetryPolicy.shouldRetry(exception)
  3. 如果 shouldRetry 返回 true → 从 RetryPolicy.getBackOff() 获取等待时间 → Thread.sleep → 回到步骤 1
  4. 如果 shouldRetry 返回 false 或超时或达到最大重试次数 → 抛出 RetryException(包装原始异常)
Retryable<R>:函数式接口(不是注解)

这是最容易误解的地方。很多文章提到 "Spring Boot 4 有了 @Retryable 注解",但在 Spring Framework 7.0.8 的 spring-core 中:

  • Retryable<R> 是一个函数式接口,不是注解
  • 只有一个方法:R execute() throws Throwable
  • 用它来包装"会被重试的代码块",以 Lambda 形式传入 RetryTemplate.execute()
java 复制代码
// 不是 @Retryable(maxAttempts = 3) 注解方式
// 而是函数式接口 + Lambda:
RetryTemplate template = new RetryTemplate(retryPolicy);
String result = template.execute(() -> {
    // 这里写可能失败的代码
    return restClient.get().uri("/api/data").retrieve().body(String.class);
});
// 或者更简洁的 invoke 方法:
String result = template.invoke(() -> callRemoteService());

为什么是函数式接口而不是注解?这反映了 Spring 团队对"容错"的定位调整:

  • 注解方式 (旧 spring-retry):依赖 AOP,重试逻辑在切面里,出错时调用栈很深
  • 函数式方式 (新 spring-core):重试逻辑在调用栈上明明白白,debug 时一目了然
  • 两者不互斥:spring-retry 项目仍然存在,如果需要声明式重试仍然可以用。但 Spring Framework 7 的 spring-core 选择"先做好基础设施层",注解方案可以后面再加
RetryListener:重试过程的回调
java 复制代码
public interface RetryListener {
    // 即将开始执行(整个重试流程的入口)
    default void onRetryableExecution(RetryPolicy policy, Retryable<?> retryable, RetryState retryState) {}
    // 每次重试前(含 RetryState 版本,用于获取当前重试次数)
    default void beforeRetry(RetryPolicy policy, Retryable<?> retryable, RetryState retryState) {}
    // 每次重试前(简化版)
    default void beforeRetry(RetryPolicy policy, Retryable<?> retryable) {}
    // 重试成功
    default void onRetrySuccess(RetryPolicy policy, Retryable<?> retryable, Object result) {}
    // 单次重试失败(但策略判定继续重试)
    default void onRetryFailure(RetryPolicy policy, Retryable<?> retryable, Throwable failure) {}
    // 策略判定不再重试(exhausted + interruption + timeout 三种结束原因)
    default void onRetryPolicyExhaustion(RetryPolicy policy, Retryable<?> retryable, RetryException retryException) {}
    default void onRetryPolicyInterruption(RetryPolicy policy, Retryable<?> retryable, RetryException retryException) {}
    default void onRetryPolicyTimeout(RetryPolicy policy, Retryable<?> retryable, RetryException retryException) {}
}

RetryState 携带当前重试过程的状态信息:

java 复制代码
public interface RetryState {
    int getRetryCount();                        // 当前已重试次数
    List<Throwable> getExceptions();            // 历史异常列表
    default Throwable getLastException();        // 最近一次异常
    default boolean isSuccessful();              // 是否成功
}

配合 CompositeRetryListener 可以串联多个监听器:

java 复制代码
CompositeRetryListener composite = new CompositeRetryListener(List.of(
    new MetricsRetryListener(),    // 记录重试次数到 Micrometer
    new LoggingRetryListener()     // 打印重试日志
));
template.setRetryListener(composite);

三、Spring Boot 的桥接层:RetryPolicySettings

Spring Boot 4.0 在 core/spring-boot 模块中新增了 org.springframework.boot.retry.RetryPolicySettings(@since 4.0.0,作者 Stephane Nicoll),职责是把"配置"翻译成 RetryPolicy

java 复制代码
// RetryPolicySettings 核心结构(源码位置:core/spring-boot/.../retry/RetryPolicySettings.java)
public final class RetryPolicySettings {

    // 类级别常量,直接引用 RetryPolicy.Builder 的默认值
    public static final long DEFAULT_MAX_RETRIES = RetryPolicy.Builder.DEFAULT_MAX_RETRIES;        // 3
    public static final Duration DEFAULT_DELAY
            = Duration.ofMillis(RetryPolicy.Builder.DEFAULT_DELAY);                                 // 1s
    public static final double DEFAULT_MULTIPLIER = RetryPolicy.Builder.DEFAULT_MULTIPLIER;         // 1.0
    public static final Duration DEFAULT_MAX_DELAY
            = Duration.ofMillis(RetryPolicy.Builder.DEFAULT_MAX_DELAY);                             // Long.MAX_VALUE ms

    private List<Class<? extends Throwable>> exceptionIncludes = new ArrayList<>();
    private List<Class<? extends Throwable>> exceptionExcludes = new ArrayList<>();
    private @Nullable Predicate<Throwable> exceptionPredicate;
    private Long maxRetries = DEFAULT_MAX_RETRIES;
    private Duration delay = DEFAULT_DELAY;
    private @Nullable Duration jitter;
    private Double multiplier = DEFAULT_MULTIPLIER;
    private Duration maxDelay = DEFAULT_MAX_DELAY;
    private @Nullable Function<Builder, RetryPolicy> factory;  // 扩展点

    public RetryPolicy createRetryPolicy() {
        PropertyMapper map = PropertyMapper.get();
        RetryPolicy.Builder builder = RetryPolicy.builder();
        map.from(this::getExceptionIncludes).to(builder::includes);
        map.from(this::getExceptionExcludes).to(builder::excludes);
        map.from(this::getExceptionPredicate).to(builder::predicate);
        map.from(this::getMaxRetries).to(builder::maxRetries);
        map.from(this::getDelay).to(builder::delay);
        map.from(this::getJitter).to(builder::jitter);
        map.from(this::getMultiplier).to(builder::multiplier);
        map.from(this::getMaxDelay).to(builder::maxDelay);
        return (this.factory != null) ? this.factory.apply(builder) : builder.build();
    }
}

createRetryPolicy() 的设计有两个亮点:

1. PropertyMapper 桥接 :用 Spring Boot 的 PropertyMapper API 把字段值"有条件地"映射到 Builder。PropertyMapper.get() 默认行为是"只有值不为 null 时才调用 setter",这让字段的默认值治理变得直观。

2. factory 扩展点:如果预置的 8 个字段还不够,可以注入自定义函数:

java 复制代码
RetryPolicySettings settings = new RetryPolicySettings();
settings.setDelay(Duration.ofSeconds(2));
settings.setMultiplier(2.0);
settings.setFactory((builder) -> builder
    .timeout(Duration.ofMinutes(1))  // 加一个整体超时
    .backOff(myCustomBackOff)        // 或者完全替换 BackOff 策略
    .build()
);
RetryPolicy policy = settings.createRetryPolicy();

RetryPolicySettings 自身不绑定 任何 application.yml 配置键(没有一个 spring.retry.* 的统一前缀)。它被设计为一个"可编程中间层":各模块(RabbitMQ、Kafka、Redis)在自己的 *Properties 中定义各自的配置结构,然后通过 RetryPolicySettings 统一转成 RetryPolicy。这么设计的原因是:不同中间件的重试语义不同(消息发送重试 vs 消息消费重试 vs 连接恢复重试),强行统一配置前缀反而会让用户困惑。


四、各模块的集成方式

4.1 RabbitMQ:最完整的重试集成

RabbitMQ 是 Spring Boot 4 中对新重试 API 集成度最高的模块,分两个层面:

Template 层(消息发送重试):

yaml 复制代码
# application.yml
spring:
  rabbitmq:
    template:
      retry:
        enabled: true
        max-retries: 5
        initial-interval: 2s
        multiplier: 2.0
        max-interval: 30s

源码链路(RabbitTemplateConfigurer.java:131-146):

java 复制代码
protected RetryTemplate createRetryTemplate(RabbitProperties.Retry properties) {
    // 1. 把 RabbitProperties.Retry 翻译成 RetryPolicySettings
    RabbitRetryTemplateSettings retrySettings = new RabbitRetryTemplateSettings(
        properties.initializeRetryPolicySettings());

    // 2. 回调所有 RabbitTemplateRetrySettingsCustomizer
    if (this.retrySettingsCustomizers != null) {
        for (RabbitTemplateRetrySettingsCustomizer customizer : this.retrySettingsCustomizers) {
            customizer.customize(retrySettings);
        }
    }

    // 3. 用 settings 创建 RetryPolicy
    RetryPolicy retryPolicy = retrySettings.getRetryPolicySettings().createRetryPolicy();

    // 4. 创建 RetryListener(支持多个,自动组合为 CompositeRetryListener)
    RetryListener retryListener = createRetryListener(retrySettings.getRetryListeners());

    // 5. 组装 RetryTemplate
    RetryTemplate retryTemplate = new RetryTemplate(retryPolicy);
    if (retryListener != null) {
        retryTemplate.setRetryListener(retryListener);
    }
    return retryTemplate;
}

然后通过 template.setRetryTemplate(createRetryTemplate(...)) 设置到 RabbitTemplate 上。

Listener 层(消息消费重试):

java 复制代码
// RabbitProperties.java
public static class ListenerRetry extends Retry {
    private boolean stateless = true;  // 无状态重试 vs 有状态重试
}

// AbstractRabbitListenerContainerFactoryConfigurer.java
RetryInterceptorBuilder.stateless()   // 或 .stateful()
    .retryPolicy(createRetryPolicy(retryConfig))
    .recoverer(messageRecoverer)
    .build();

自定义方式:

java 复制代码
// 自定义 Template 重试
@Bean
RabbitTemplateRetrySettingsCustomizer templateRetryCustomizer() {
    return (settings) -> {
        settings.getRetryPolicySettings().setMultiplier(1.5);
        settings.getRetryListeners().add(new MyLoggingRetryListener());
    };
}

// 自定义 Listener 重试(注意:参数直接是 RetryPolicySettings,不是 RabbitRetryTemplateSettings)
@Bean
RabbitListenerRetrySettingsCustomizer listenerRetryCustomizer() {
    return (settings) -> {
        settings.setMaxRetries(10L);
    };
}

4.2 Kafka:Retry Topic 的 BackOff 构建

Kafka 的重试是通过 Retry Topic 机制(失败消息投递到重试主题,延迟后重新消费),Spring Boot 4 用新的 RetryPolicy.Builder 来构建 BackOff 策略:

java 复制代码
// KafkaAutoConfiguration.java:236-244
static BackOff getBackOff(Backoff retryTopicBackoff) {
    PropertyMapper map = PropertyMapper.get();
    RetryPolicy.Builder builder = RetryPolicy.builder()
        .maxRetries(Long.MAX_VALUE);  // Kafka 重试由 topic 层控制总次数
    map.from(retryTopicBackoff.getDelay()).to(builder::delay);
    map.from(retryTopicBackoff.getMaxDelay())
        .when(Predicate.not(Duration::isZero)).to(builder::maxDelay);
    map.from(retryTopicBackoff.getMultiplier()).to(builder::multiplier);
    map.from(retryTopicBackoff.getJitter())
        .when(Predicate.not(Duration::isZero)).to(builder::jitter);
    return builder.build().getBackOff();  // 只取 BackOff,不取 RetryPolicy
}

配置:

yaml 复制代码
spring:
  kafka:
    retry:
      topic:
        enabled: true
        attempts: 5
        backoff:
          delay: 1s
          multiplier: 2.0
          max-delay: 10s

4.3 Redis:消息监听器恢复

Redis 的消息监听器在连接断开后需要"恢复"(recovery),同样用了新的重试 API:

java 复制代码
// RedisMessageListenerContainerConfigurer.java:66-74
static BackOff getRecoveryBackOff(Recovery recovery) {
    RetryPolicy.Builder builder = RetryPolicy.builder()
        .maxRetries(recovery.getMaxRetries());
    PropertyMapper map = PropertyMapper.get();
    map.from(recovery.getDelay()).to(builder::delay);
    map.from(recovery.getMaxDelay())
        .when(Predicate.not(Duration::isZero)).to(builder::maxDelay);
    map.from(recovery.getMultiplier()).to(builder::multiplier);
    map.from(recovery.getJitter())
        .when(Predicate.not(Duration::isZero)).to(builder::jitter);
    return builder.build().getBackOff();
}

4.4 集成全景对比

模块 配置前缀 RetryPolicySettings Customizer 生效位置
RabbitMQ Template spring.rabbitmq.template.retry.* 直接使用 RabbitTemplateRetrySettingsCustomizer RabbitTemplate.setRetryTemplate()
RabbitMQ Listener spring.rabbitmq.listener.retry.* 直接使用 RabbitListenerRetrySettingsCustomizer RetryInterceptorBuilder
Kafka spring.kafka.retry.topic.* 间接使用(仅取 BackOff) RetryTopicConfiguration
Redis spring.data.redis.listener.recovery.* 间接使用(仅取 BackOff) RedisMessageListenerContainer.setRecoveryBackoff()

五、实战:手写一个带重试的 HTTP 调用

假设有一个订单服务需要调用库存接口,网络抖动时应该自动重试。在 Spring Boot 4 中不需要任何额外依赖:

java 复制代码
package com.example.orderservice;

import org.springframework.core.retry.RetryPolicy;
import org.springframework.core.retry.RetryTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestClientException;

import java.time.Duration;

@Service
public class InventoryClient {

    private final RestClient restClient;
    private final RetryTemplate retryTemplate;

    public InventoryClient(RestClient.Builder restClientBuilder) {
        this.restClient = restClientBuilder
            .baseUrl("http://inventory-service:8080")
            .build();

        // 构建重试策略:网络异常重试 3 次,退避 1s→2s→4s
        // 注意:RestClient 将底层 I/O 异常包装为 RestClientException(RuntimeException)
        // 因此用 includes(RestClientException.class) 才能正确触发重试
        RetryPolicy retryPolicy = RetryPolicy.builder()
            .maxRetries(3)
            .delay(Duration.ofSeconds(1))
            .multiplier(2.0)
            .maxDelay(Duration.ofSeconds(10))
            .jitter(Duration.ofMillis(200))
            .includes(RestClientException.class)
            .build();

        this.retryTemplate = new RetryTemplate(retryPolicy);
    }

    /**
     * 查询库存------带自动重试(读操作幂等,安全重试)
     */
    public InventoryResponse queryInventory(Long productId) {
        return retryTemplate.invoke(() ->
            restClient.get()
                .uri("/api/inventory/{productId}", productId)
                .retrieve()
                .body(InventoryResponse.class)
        );
    }

    /**
     * 扣减库存------因为涉及写操作,要更谨慎
     */
    public DeductResponse deductInventory(DeductRequest request) {
        // 写操作:只重试超时类异常,避免重复扣减
        // predicate 检查异常链中是否包含连接超时(RestClient 将其包装为 RestClientException)
        RetryPolicy writeRetryPolicy = RetryPolicy.builder()
            .maxRetries(2)
            .delay(Duration.ofMillis(500))
            .multiplier(1.0)  // 固定延迟,不翻倍
            .predicate(ex -> {
                Throwable cause = ex;
                while (cause != null) {
                    if (cause instanceof java.net.SocketTimeoutException
                        || cause instanceof java.net.http.HttpTimeoutException) {
                        return true;
                    }
                    cause = cause.getCause();
                }
                return false;
            })
            .build();

        RetryTemplate writeTemplate = new RetryTemplate(writeRetryPolicy);

        return writeTemplate.invoke(() ->
            restClient.post()
                .uri("/api/inventory/deduct")
                .body(request)
                .retrieve()
                .body(DeductResponse.class)
        );
    }
}

对比一下 Spring Boot 3 需要什么:

  • Spring Boot 3: @EnableRetry + @Retryable(maxAttempts = 3, backoff = ...) + spring-retry 依赖 + spring-aspects 依赖
  • Spring Boot 4: 只需要 spring-core(已在 classpath 上)
维度 Spring Boot 3 (spring-retry) Spring Boot 4 (spring-core)
额外依赖 spring-retry + spring-aspects 无(spring-core 自带)
启用方式 @EnableRetry 注解 无需启用,直接 new RetryTemplate
使用方式 @Retryable 注解 + AOP 函数式 template.invoke(() -> ...)
调用栈 切面代理 → 反射 → 重试逻辑 直接调用,链更短
单测友好 需要 Spring 容器加载 纯 POJO,直接 new 测
自定义策略 RetryTemplate + RetryPolicy Bean RetryPolicy.builder() 一行链式调用

六、源码追踪:从配置到重试执行的完整调用链

以 RabbitMQ 发送消息重试为例,追踪完整调用链路:

scss 复制代码
1. application.yml
   spring.rabbitmq.template.retry.enabled = true
        │
2. RabbitProperties.Retry.enabled = true(属性绑定)
        │
3. RabbitTemplateConfigurer.configure(template, connectionFactory)
        │
4. templateProperties.getRetry().isEnabled() == true
        │
5. createRetryTemplate(templateProperties.getRetry())
        │
6. RabbitProperties.Retry.initializeRetryPolicySettings()
        │   └─ 把 maxRetries=3, initialInterval=1s, multiplier=1.0, maxInterval=10s
        │      映射到 RetryPolicySettings 的 maxRetries/delay/multiplier/maxDelay
        │
7. RetryPolicySettings.createRetryPolicy()
        │   └─ RetryPolicy.builder()
        │       .maxRetries(3).delay(1s).multiplier(1.0).maxDelay(10s)
        │       .build()
        │       生成的是 DefaultRetryPolicy + ExponentialBackOff(含 BackOff 计算逻辑)
        │
8. new RetryTemplate(retryPolicy)
        │
9. template.setRetryTemplate(retryTemplate)
        │
10. 业务代码调用 rabbitTemplate.convertAndSend(...)
        │
11. RabbitTemplate 在发送消息遇到可重试异常时
        │
12. retryTemplate.execute(retryable) → RetryPolicy.shouldRetry(ex) → BackOff 等待 → 重试

关键源码位置汇总:

文件位置 作用
RetryPolicy spring-core-7.0.8.jar!/org/springframework/core/retry/RetryPolicy 重试决策接口
RetryTemplate spring-core-7.0.8.jar!/org/springframework/core/retry/RetryTemplate 重试执行器
Retryable spring-core-7.0.8.jar!/org/springframework/core/retry/Retryable 函数式接口
RetryPolicySettings core/spring-boot/.../retry/RetryPolicySettings.java Boot 重试配置桥接
RabbitProperties.Retry module/spring-boot-amqp/.../RabbitProperties.java RabbitMQ 重试属性
RabbitTemplateConfigurer module/spring-boot-amqp/.../RabbitTemplateConfigurer.java RabbitMQ 模板配置
KafkaAutoConfiguration module/spring-boot-kafka/.../KafkaAutoConfiguration.java Kafka 重试主题配置
RedisMessageListenerContainerConfigurer module/spring-boot-data-redis/.../RedisMessageListenerContainerConfigurer.java Redis 恢复配置

七、当前限制与迁移决策

7.1 目前没有 @Retryable 注解

org.springframework.core.retry.Retryable<R> 是一个函数式接口 ,不是注解。如果期望类似旧 spring-retry 那样在方法上加 @Retryable(maxAttempts = 3) 就能自动重试------在 Spring Framework 7.0.8 中还不支持。

做法是显式创建 RetryTemplate 并用 Lambda 包装:

java 复制代码
// 可行
RetryTemplate template = new RetryTemplate(policy);
template.invoke(() -> someMethod());

// 尚不可行(Spring Framework 7.0.8 中没有这个注解)
@Retryable(maxAttempts = 3)
public void someMethod() { ... }

Spring 团队选择"先把基础 API 放进 spring-core":有了 RetryPolicy + RetryTemplate 基础设施,上层可以逐步叠加 AOP 注解或 Spring Boot Starter。这是"自下而上"的演进路线。

7.2 目前没有限流和熔断

Spring Framework 7.0.8 的 spring-core没有 Rate Limiter 和 Circuit Breaker 的实现。在整个 Spring Boot 4.1.0 代码仓库中,也没有 resilience4j 依赖声明。

如果需要完整的"重试 + 熔断 + 限流"能力栈,目前仍然需要引入 Resilience4j:

xml 复制代码
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
</dependency>

不过 RetryPolicy.builder() 的 API 设计已经为未来扩展留了空间------getBackOff() 返回的是 BackOff 接口,未来完全可以实现一个 RateLimitedBackOffCircuitBreakerBackOff 而不改变 API。

7.3 从 spring-retry 迁移的决策树

ruby 复制代码
你现在在用 spring-retry 吗?
│
├─ 只是简单的"这种异常重试 N 次"?
│   └─ 可以迁移到 spring-core RetryTemplate
│       省掉 spring-retry 依赖,调用栈更短
│
├─ 用了 @Retryable 注解,方法上挂了十几个?
│   └─ 暂时保留 spring-retry
│      等 Spring Framework 未来加注解支持再迁
│
└─ 用了 RetryTemplate + RecoveryCallback + RetryContext?
    └─ 需要评估------spring-core 的 Retryable 接口
        比 RetryCallback 简单,但少了 RetryContext

八、总结

Spring Framework 7.0 把重试搬进 spring-core,容错从"可选的扩展"变成了框架基础设施的一部分。

三个关键变化:

  1. 零依赖重试 :任何 Spring Boot 4 应用都自带 RetryPolicy + RetryTemplate,不需要再加 spring-retry
  2. API 现代化 :RetryPolicy.builder() 一条链式调用搞定策略配置,告别 SimpleRetryPolicy + ExponentialBackOffPolicy 的组合式痛苦
  3. 函数式执行 :template.invoke(() -> doSomething()) 替代了 AOP + 注解,调用栈更短,调试更直观

三个尚待补齐:

  1. 还没有 @Retryable 注解(Retryable 是函数式接口)
  2. 还没有限流和熔断的内置实现
  3. RetryPolicySettings 没有统一的 spring.retry.* 配置前缀

相关推荐
风卿1 小时前
知识库双路召回:BM25 关键词与语义向量 RRF 融合,附指标实测
后端
未秃头的程序猿1 小时前
虚拟线程上线一周后翻车了——pinning问题排查实录
java·后端·架构
wuminyu1 小时前
JDK21解决虚拟线程IO阻塞原理剖析
java·linux·c语言·jvm·c++
带刺的坐椅1 小时前
Solon AOT & Native:三段式编译,从 Java 到原生可执行文件
java·aot·solon·native
AI_paid_community1 小时前
如何使用 Claude 在 AI 时代快速入局新的行业?(经验贴)
前端·javascript·后端
AI多Agent协作实战派1 小时前
AI多Agent协作系统实战(三十六):代码里明明写了,编译完怎么没了?
后端
柠檬味拥抱1 小时前
代码看腻了,我让 Seed Evolving 把整个仓库搓成了一座能走进去的 3D 城市
后端
程序员良辰1 小时前
【TongWeb8】使用 Crontab 定时重启和检测 TongWeb 服务
java·中间件·tomcat
mit6.8242 小时前
archived
后端·python·flask