Spring Retryable 注解完全指南:从入门到精通,让接口容错更简单

在日常开发中,我们经常会遇到这样的场景:调用第三方接口时因网络抖动超时、数据库操作因锁竞争失败、Redis 连接临时断开...... 这些偶发性的问题,往往只需要重新调用一次就能成功。如果手动写for循环实现重试逻辑,不仅代码冗余、可读性差,还容易遗漏边界条件。

Spring 框架提供的@Retryable注解,能以极简的注解方式实现重试逻辑,无需编写复杂的循环和判断,让接口容错能力的开发效率提升数倍。本文将从 "为什么需要重试" 出发,由浅入深讲解@Retryable的核心用法、高级特性、避坑指南,并结合真实业务场景给出可直接落地的代码示例,让你彻底掌握这个实用的注解。

一、为什么需要重试?先看两个真实场景

在讲解@Retryable之前,我们先明确 "重试" 的价值 ------ 它解决的是偶发性、非必现的临时故障,而非业务逻辑错误。

场景 1:调用第三方 AI 图片生成接口

比如调用 Doubao-Seedream-4.5 生成图片时,偶尔会因网络波动导致接口超时:

java 复制代码
// 无重试的原始代码
public String generateImage(String prompt) {
    // 调用第三方接口,偶发超时
    return arkService.generateImages(buildRequest(prompt)).getData().get(0).getUrl();
}

如果不加重试,一次网络抖动就会导致接口调用失败,用户体验差;如果手动写重试:

java 复制代码
// 手动重试:代码冗余,可读性差
public String generateImage(String prompt) {
    int maxAttempts = 3; // 最多重试3次
    int attempt = 0;
    while (attempt < maxAttempts) {
        try {
            return arkService.generateImages(buildRequest(prompt)).getData().get(0).getUrl();
        } catch (Exception e) {
            attempt++;
            if (attempt >= maxAttempts) {
                throw new RuntimeException("图片生成失败");
            }
            // 延迟1秒重试
            try {
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                Thread.currentThread().interrupt();
            }
        }
    }
    return null;
}

手动重试的问题:代码嵌套深、重试次数 / 延迟时间硬编码、异常类型无法精准控制、无统一的失败恢复逻辑。

场景 2:数据库批量插入

数据库批量插入时,偶尔因表锁导致插入失败:

java 复制代码
// 无重试,一次锁竞争就失败
public void batchInsert(List<User> userList) {
    jdbcTemplate.batchUpdate("INSERT INTO user (name, age) VALUES (?, ?)", 
        new BatchPreparedStatementSetter() {
            // ... 赋值逻辑
        });
}

这类临时的锁竞争问题,重试 1-2 次就能解决,但手动写重试会让业务代码变得臃肿。

@Retryable注解的核心价值就是:以声明式的方式替代手动重试逻辑,让代码聚焦业务,同时兼顾灵活性和可维护性

二、Spring Retry 基础:核心概念与环境准备

1. 什么是 Spring Retry?

Spring Retry 是 Spring 框架提供的一个轻量级重试框架,核心能力包括:

  • 基于注解 / 编程式的重试控制;
  • 可配置的重试次数、延迟策略;
  • 支持按异常类型精准触发重试;
  • 重试失败后的恢复机制;
  • 可选的熔断器(Circuit Breaker)扩展。

@Retryable是 Spring Retry 的核心注解,通过标注在方法上,即可为方法添加重试能力,无需修改方法内部逻辑。

2. 环境准备:三步搞定依赖与配置

步骤 1:引入 Maven 依赖

Spring Retry 需要两个核心依赖:spring-retry(重试核心)和aspectjweaver(AOP 切面支持,注解的底层实现)。

如果是 Spring Boot 项目,添加以下依赖到pom.xml

java 复制代码
<!-- Spring Retry核心依赖 -->
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>2.0.6</version> <!-- 推荐使用最新稳定版 -->
</dependency>
<!-- AOP切面支持(@Retryable依赖AOP实现) -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.20.1</version>
</dependency>

注:Spring Boot 2.x/3.x 均兼容该依赖,无需额外适配。

步骤 2:启用重试功能

在 Spring Boot 启动类上添加@EnableRetry注解,开启重试框架的自动配置:

java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.retry.annotation.EnableRetry;

@SpringBootApplication
@EnableRetry // 启用重试功能
public class RetryDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(RetryDemoApplication.class, args);
    }
}
步骤 3:确认 Spring 版本兼容

Spring Retry 2.x 兼容 Spring 5.x/6.x,Spring Boot 2.4 + 以上版本无需额外调整;如果是老版本 Spring Boot(2.0-2.3),建议使用 Spring Retry 1.3.x 版本。

三、@Retryable 入门:第一个重试示例

我们先写一个极简的示例,模拟 "调用第三方接口偶发失败" 的场景,体验@Retryable的核心用法。

1. 业务场景模拟

创建一个ThirdPartyService类,模拟调用第三方接口,随机抛出异常(模拟偶发故障):

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

import java.util.Random;

@Slf4j
@Service
public class ThirdPartyService {

    // 模拟第三方接口调用:随机抛出异常
    @Retryable // 核心注解:为该方法添加重试能力
    public String callThirdPartyApi(String param) {
        log.info("开始调用第三方接口,参数:{}", param);
        // 随机生成0-2的数字,模拟33%的失败概率
        int random = new Random().nextInt(3);
        if (random != 0) {
            log.error("第三方接口调用失败,随机数:{}", random);
            throw new RuntimeException("第三方接口临时不可用");
        }
        log.info("第三方接口调用成功,参数:{}", param);
        return "success-" + param;
    }
}

2. 测试重试效果

创建测试类,调用该方法,观察日志输出:

java 复制代码
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class RetryDemoTest {

    @Autowired
    private ThirdPartyService thirdPartyService;

    @Test
    public void testRetryable() {
        String result = thirdPartyService.callThirdPartyApi("test123");
        System.out.println("最终调用结果:" + result);
    }
}

3. 运行结果分析

执行测试方法,日志可能如下(因随机数不同,结果有差异):

bash 复制代码
2026-01-02 10:00:00.123  INFO 12345 --- [main] c.e.r.service.ThirdPartyService          : 开始调用第三方接口,参数:test123
2026-01-02 10:00:00.124 ERROR 12345 --- [main] c.e.r.service.ThirdPartyService          : 第三方接口调用失败,随机数:1
2026-01-02 10:00:00.125  INFO 12345 --- [main] c.e.r.service.ThirdPartyService          : 开始调用第三方接口,参数:test123
2026-01-02 10:00:00.126 ERROR 12345 --- [main] c.e.r.service.ThirdPartyService          : 第三方接口调用失败,随机数:2
2026-01-02 10:00:00.127  INFO 12345 --- [main] c.e.r.service.ThirdPartyService          : 开始调用第三方接口,参数:test123
2026-01-02 10:00:00.128  INFO 12345 --- [main] c.e.r.service.ThirdPartyService          : 第三方接口调用成功,参数:test123
最终调用结果:success-test123

核心现象

  • 方法第一次调用失败后,自动重试了 2 次(默认最大重试次数是 3 次,包括第一次调用);
  • 重试无延迟(默认退避策略是立即重试);
  • 只要有一次调用成功,就返回结果,不再重试;
  • 如果 3 次都失败,会抛出最终的异常。

四、@Retryable 核心参数详解:精准控制重试逻辑

上面的示例只用了@Retryable的默认配置,实际开发中需要根据业务场景精准控制重试规则。@Retryable提供了多个核心参数,我们逐个讲解,结合示例让你理解每个参数的作用。

1. retryFor /value:指定触发重试的异常类型

  • retryFor:数组类型,指定哪些异常触发重试(推荐使用,语义更清晰);
  • valueretryFor的别名,功能完全一致,只是命名不同。

默认值 :空数组,会触发所有Exception类型的异常重试。

示例 :仅当抛出RuntimeExceptionIOException时触发重试:

java 复制代码
import org.springframework.retry.annotation.Retryable;
import java.io.IOException;

@Retryable(retryFor = {RuntimeException.class, IOException.class})
public String callThirdPartyApi(String param) throws IOException {
    log.info("开始调用第三方接口,参数:{}", param);
    int random = new Random().nextInt(3);
    if (random == 1) {
        throw new RuntimeException("运行时异常");
    } else if (random == 2) {
        throw new IOException("IO异常");
    }
    return "success-" + param;
}

关键点

  • 只有抛出指定的异常类型(包括子类),才会触发重试;
  • 如果抛出未指定的异常(比如NullPointerException),不会重试,直接抛出。

2. noRetryFor:指定不触发重试的异常类型

retryFor相反,noRetryFor指定哪些异常不触发重试 ,优先级高于retryFor

示例 :除了NullPointerException,其他RuntimeException都触发重试:

java 复制代码
@Retryable(
    retryFor = {RuntimeException.class},
    noRetryFor = {NullPointerException.class}
)
public String callThirdPartyApi(String param) {
    log.info("开始调用第三方接口,参数:{}", param);
    int random = new Random().nextInt(3);
    if (random == 1) {
        throw new RuntimeException("普通运行时异常:触发重试");
    } else if (random == 2) {
        throw new NullPointerException("空指针异常:不触发重试");
    }
    return "success-" + param;
}

测试结果:

  • 抛出RuntimeException时,会重试;
  • 抛出NullPointerException时,直接失败,不重试。

3. maxAttempts:最大重试次数

指定包括第一次调用在内的总尝试次数,默认值是 3 次(即最多重试 2 次)。

示例:设置最大重试次数为 5 次(1 次原始调用 + 4 次重试):

java 复制代码
@Retryable(
    retryFor = {RuntimeException.class},
    maxAttempts = 5 // 总尝试次数5次
)
public String callThirdPartyApi(String param) {
    log.info("第{}次调用第三方接口", ++count); // count是类内计数器
    throw new RuntimeException("调用失败");
}

日志会输出 "第 1/2/3/4/5 次调用",5 次失败后抛出异常。

4. backoff:退避策略(重试延迟)

backoff@Backoff注解,用于配置重试的延迟策略,解决 "立即重试可能加重服务压力" 的问题。@Backoff的核心参数:

  • delay:重试延迟时间,单位毫秒,默认 0(立即重试);
  • maxDelay:最大延迟时间,单位毫秒(防止延迟过长);
  • multiplier:延迟倍数(指数退避),比如delay=1000multiplier=2,则第一次重试延迟 1 秒,第二次 2 秒,第三次 4 秒;
  • random:是否随机延迟(true/false),默认 false。
示例 1:固定延迟重试

每次重试延迟 1 秒:

java 复制代码
@Retryable(
    retryFor = {RuntimeException.class},
    maxAttempts = 3,
    backoff = @Backoff(delay = 1000) // 延迟1秒重试
)
public String callThirdPartyApi(String param) {
    log.info("调用第三方接口,时间:{}", System.currentTimeMillis());
    throw new RuntimeException("调用失败");
}

日志时间戳会间隔约 1 秒,比如:

bash 复制代码
10:00:00.000 调用第三方接口
10:00:01.000 调用第三方接口(第一次重试,延迟1秒)
10:00:02.000 调用第三方接口(第二次重试,延迟1秒)
示例 2:指数退避重试

延迟时间按倍数递增,最大延迟不超过 5 秒:

bash 复制代码
@Retryable(
    retryFor = {RuntimeException.class},
    maxAttempts = 4,
    backoff = @Backoff(delay = 1000, multiplier = 2, maxDelay = 5000)
)
public String callThirdPartyApi(String param) {
    log.info("调用第三方接口,时间:{}", System.currentTimeMillis());
    throw new RuntimeException("调用失败");
}

延迟逻辑:

  • 第 1 次重试:延迟 1 秒(1000ms * 1);
  • 第 2 次重试:延迟 2 秒(1000ms * 2);
  • 第 3 次重试:延迟 4 秒(1000ms * 4);
  • 若有第 4 次重试:延迟 5 秒(达到 maxDelay,不再递增)。
示例 3:随机延迟重试

延迟时间在 0-2 秒之间随机:

java 复制代码
@Retryable(
    retryFor = {RuntimeException.class},
    backoff = @Backoff(delay = 0, maxDelay = 2000, random = true)
)
public String callThirdPartyApi(String param) {
    log.info("调用第三方接口,时间:{}", System.currentTimeMillis());
    throw new RuntimeException("调用失败");
}

每次重试的延迟时间是 0-2000ms 之间的随机值,避免集中重试导致的服务压力。

5. exceptionExpression:表达式判断是否重试

通过 SpEL 表达式动态判断是否触发重试,比retryFor更灵活,支持基于异常信息、方法参数等判断。

示例:仅当异常消息包含 "临时不可用" 时触发重试:

java 复制代码
@Retryable(
    retryFor = {RuntimeException.class},
    exceptionExpression = "#{message.contains('临时不可用')}"
)
public String callThirdPartyApi(String param) {
    log.info("调用第三方接口");
    // 模拟不同异常消息
    int random = new Random().nextInt(2);
    if (random == 0) {
        throw new RuntimeException("第三方接口临时不可用"); // 触发重试
    } else {
        throw new RuntimeException("第三方接口永久不可用"); // 不触发重试
    }
}

SpEL 表达式说明

  • #代表异常对象;
  • message是异常的getMessage()方法;
  • 支持更复杂的表达式,比如#{#root.cause != null && #root.cause instanceof IOException}

6. label:重试标识(用于监控)

为重试方法添加标识,便于监控和统计(比如结合 Spring Boot Actuator 查看重试次数)

java 复制代码
@Retryable(
    retryFor = {RuntimeException.class},
    label = "third-party-api-retry"
)
public String callThirdPartyApi(String param) {
    // ...
}

五、@Recover:重试失败后的恢复机制

当重试次数达到maxAttempts仍失败时,@Retryable会抛出最终的异常。如果希望重试失败后执行 "兜底逻辑"(比如返回默认值、记录告警、降级处理),可以使用@Recover注解。

1. @Recover 核心规则

  • @Recover标注的方法必须和@Retryable方法在同一个类中;
  • 恢复方法的第一个参数必须是重试抛出的异常类型(或其父类);
  • 恢复方法的返回值必须和 @Retryable 方法一致
  • 恢复方法的其他参数必须和 @Retryable 方法的参数一一对应
  • 一个@Retryable方法可以对应多个@Recover方法(按异常类型匹配)。

2. @Recover 基础示例

为上面的第三方接口调用添加恢复方法:

java 复制代码
@Slf4j
@Service
public class ThirdPartyService {

    // 重试方法
    @Retryable(
        retryFor = {RuntimeException.class},
        maxAttempts = 3,
        backoff = @Backoff(delay = 1000)
    )
    public String callThirdPartyApi(String param) {
        log.info("调用第三方接口,参数:{}", param);
        throw new RuntimeException("第三方接口临时不可用");
    }

    // 恢复方法:匹配RuntimeException异常
    @Recover
    public String recover(RuntimeException e, String param) {
        log.error("第三方接口重试3次均失败,参数:{},异常:{}", param, e.getMessage());
        // 兜底逻辑:返回默认值
        return "default-" + param;
    }
}

测试结果:

3. 多异常类型的恢复方法

如果@Retryable方法可能抛出多种异常,可以为不同异常配置不同的恢复方法:

java 复制代码
// 重试方法:支持两种异常
@Retryable(retryFor = {RuntimeException.class, IOException.class})
public String callThirdPartyApi(String param) throws IOException {
    int random = new Random().nextInt(2);
    if (random == 0) {
        throw new RuntimeException("运行时异常");
    } else {
        throw new IOException("IO异常");
    }
}

// 恢复方法1:处理RuntimeException
@Recover
public String recover(RuntimeException e, String param) {
    log.error("运行时异常恢复,参数:{}", param);
    return "default-runtime-" + param;
}

// 恢复方法2:处理IOException
@Recover
public String recover(IOException e, String param) {
    log.error("IO异常恢复,参数:{}", param);
    return "default-io-" + param;
}

Spring 会根据实际抛出的异常类型,匹配对应的恢复方法。

六、@Retryable 高级用法:自定义重试策略

如果@Retryable的默认参数无法满足复杂业务需求(比如 "根据异常类型动态调整重试次数"),可以自定义重试策略和退避策略。

1. 自定义重试策略(RetryPolicy)

实现RetryPolicy接口,自定义 "是否触发重试" 的逻辑。比如:"RuntimeException 重试 5 次,IOException 重试 3 次"。

步骤 1:实现 RetryPolicy 接口
java 复制代码
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryPolicy;
import org.springframework.retry.context.RetryContextSupport;

public class CustomRetryPolicy implements RetryPolicy {

    // 最大重试次数:RuntimeException=5次,IOException=3次
    private final int maxRuntimeAttempts = 5;
    private final int maxIoAttempts = 3;

    @Override
    public boolean canRetry(RetryContext context) {
        // 获取异常类型
        Throwable throwable = context.getLastThrowable();
        if (throwable == null) {
            return true; // 第一次调用,无异常,允许执行
        }
        // 获取已重试次数
        int attempts = context.getRetryCount();
        // 根据异常类型判断是否继续重试
        if (throwable instanceof RuntimeException) {
            return attempts < maxRuntimeAttempts;
        } else if (throwable instanceof IOException) {
            return attempts < maxIoAttempts;
        }
        return false; // 其他异常不重试
    }

    @Override
    public RetryContext open(RetryContext parent) {
        return new RetryContextSupport(parent);
    }

    @Override
    public void close(RetryContext context) {}

    @Override
    public void registerThrowable(RetryContext context, Throwable throwable) {
        ((RetryContextSupport) context).registerThrowable(throwable);
    }
}
步骤 2:配置自定义重试策略

创建配置类,将自定义策略注册为 Bean:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.policy.RetryPolicy;

@Configuration
@EnableRetry
public class RetryConfig {

    @Bean
    public RetryPolicy customRetryPolicy() {
        return new CustomRetryPolicy();
    }
}
步骤 3:在 @Retryable 中引用
java 复制代码
@Retryable(retryPolicy = "customRetryPolicy") // 引用自定义策略Bean名
public String callThirdPartyApi(String param) throws IOException {
    int random = new Random().nextInt(2);
    if (random == 0) {
        throw new RuntimeException("运行时异常"); // 重试5次
    } else {
        throw new IOException("IO异常"); // 重试3次
    }
}

2. 自定义退避策略(BackOffPolicy)

实现BackOffPolicy接口,自定义重试延迟逻辑。比如:"RuntimeException 延迟 2 秒,IOException 延迟 1 秒"。

java 复制代码
import org.springframework.retry.BackOffContext;
import org.springframework.retry.BackOffInterruptedException;
import org.springframework.retry.BackOffPolicy;
import org.springframework.retry.RetryContext;

public class CustomBackOffPolicy implements BackOffPolicy {

    @Override
    public BackOffContext start(RetryContext context) {
        return null;
    }

    @Override
    public void backOff(BackOffContext backOffContext) throws BackOffInterruptedException {
        // 获取异常类型
        Throwable throwable = ((RetryContext) backOffContext).getLastThrowable();
        try {
            if (throwable instanceof RuntimeException) {
                Thread.sleep(2000); // 延迟2秒
            } else if (throwable instanceof IOException) {
                Thread.sleep(1000); // 延迟1秒
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new BackOffInterruptedException(e);
        }
    }
}

配置并引用方式和自定义重试策略一致,这里不再赘述。

七、@Retryable 避坑指南:常见问题与注意事项

1. 重试不生效的常见原因

原因 1:未添加 @EnableRetry 注解

这是最常见的错误!如果启动类没有@EnableRetry@Retryable注解会被忽略,重试逻辑不会生效。

原因 2:方法内部调用(AOP 失效)

Spring Retry 基于 AOP 实现,AOP 的核心是动态代理,因此内部调用不会触发重试

java 复制代码
@Service
public class ThirdPartyService {

    public void outerMethod() {
        // 内部调用:重试不生效!
        callThirdPartyApi("test");
    }

    @Retryable
    public String callThirdPartyApi(String param) {
        // ...
    }
}

解决方案

  • 将方法放到不同的类中;
  • 自注入 Bean,通过 Bean 调用(不推荐,可能导致循环依赖);
  • 使用 AopContext.currentProxy () 获取代理对象(需开启 exposeProxy)。
原因 3:异常类型不匹配

比如@Retryable(retryFor = {IOException.class}),但方法抛出RuntimeException,此时不会触发重试。

原因 4:恢复方法签名错误

@Recover方法的参数 / 返回值和@Retryable方法不匹配,会导致恢复方法不执行,直接抛出异常。

2. 必须保证重试方法的幂等性

这是重试最核心的注意事项!如果方法不具备幂等性,重试会导致数据不一致

比如:

  • 禁止对 "创建订单""扣减库存" 等非幂等操作添加重试;
  • 对 "查询数据""调用第三方只读接口""幂等的更新操作"(比如根据唯一 ID 更新)可以放心重试;
  • 如果必须对非幂等操作重试,需先实现幂等性(比如添加分布式锁、唯一请求 ID)。

3. 重试次数和延迟要合理

  • 重试次数不宜过多(建议 3-5 次),过多重试会增加接口响应时间,加重服务压力;
  • 延迟时间不宜过短(建议 1-3 秒),立即重试可能无法解决 "临时不可用" 的问题;
  • 高并发场景下,建议使用指数退避 + 随机延迟,避免集中重试。

4. 避免重试嵌套

不要在@Retryable方法中调用另一个@Retryable方法,会导致重试逻辑混乱,难以排查问题。

八、实战案例:结合 AI 图片生成接口的重试实现

我们结合之前的业务场景,实现一个生产级的 AI 图片生成接口,集成@Retryable重试、参数校验、日志追踪、限流等能力。

1. 完整代码实现

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.google.common.util.concurrent.RateLimiter;

import java.util.Objects;
import java.util.Random;

@Slf4j
@Service
public class ChatImageService {

    // 限流:每秒最多10次调用,避免平台限流
    private final RateLimiter rateLimiter = RateLimiter.create(10.0);

    // 模拟ArkService(实际项目中注入)
    private final ArkService arkService = new ArkService();

    /**
     * AI图片生成接口:集成重试、限流、参数校验
     * @param prompt 图片提示词
     * @param size 图片尺寸(2K/4K)
     * @return 图片URL
     */
    @Retryable(
        retryFor = {ArkApiException.class}, // 仅对ArkAPI异常重试
        maxAttempts = 3, // 总尝试3次
        backoff = @Backoff(delay = 1000, multiplier = 1.5), // 延迟1秒,指数递增(1s→1.5s→2.25s)
        label = "chat-image-generate-retry"
    )
    public String generateImage(String prompt, String size) {
        // 1. 限流控制
        if (!rateLimiter.tryAcquire(1)) {
            throw new ArkApiException("请求过于频繁,请稍后重试");
        }

        // 2. 参数校验
        if (!StringUtils.hasText(prompt) || prompt.length() > 2000) {
            throw new IllegalArgumentException("提示词不能为空且长度不超过2000字符");
        }
        if (!"2K".equals(size) && !"4K".equals(size)) {
            throw new IllegalArgumentException("图片尺寸仅支持2K/4K");
        }

        // 3. 生成请求参数
        GenerateImagesRequest request = GenerateImagesRequest.builder()
                .model("doubao-seedream-4-5-251128")
                .prompt(prompt)
                .size(size)
                .build();

        // 4. 调用Ark API(模拟偶发失败)
        log.info("开始调用Doubao-Seedream-4.5接口,提示词:{},尺寸:{}", prompt, size);
        ImagesResponse response = arkService.generateImages(request);

        // 5. 响应校验
        if (Objects.isNull(response) || Objects.isNull(response.getData()) || response.getData().isEmpty()) {
            throw new ArkApiException("图片生成响应为空");
        }

        return response.getData().get(0).getUrl();
    }

    /**
     * 重试失败后的恢复方法
     */
    @Recover
    public String recover(ArkApiException e, String prompt, String size) {
        log.error("图片生成重试3次均失败,提示词:{},尺寸:{},异常:{}", prompt, size, e.getMessage());
        // 兜底逻辑:返回默认图片URL
        return "https://example.com/default-image.jpg";
    }

    /**
     * 自定义异常:Ark API调用异常
     */
    public static class ArkApiException extends RuntimeException {
        public ArkApiException(String message) {
            super(message);
        }
    }

    /**
     * 模拟ArkService(实际项目中使用官方SDK)
     */
    static class ArkService {
        public ImagesResponse generateImages(GenerateImagesRequest request) {
            // 模拟30%的失败概率
            int random = new Random().nextInt(10);
            if (random < 3) {
                throw new ArkApiException("Ark API临时不可用");
            }
            // 模拟成功响应
            ImagesResponse response = new ImagesResponse();
            response.setData(Arrays.asList(new ImageData("https://example.com/image-" + System.currentTimeMillis() + ".jpg")));
            return response;
        }
    }

    // 模拟请求/响应实体类
    static class GenerateImagesRequest {
        private String model;
        private String prompt;
        private String size;

        public static GenerateImagesRequestBuilder builder() {
            return new GenerateImagesRequestBuilder();
        }

        // 省略getter/setter和Builder类
    }

    static class ImagesResponse {
        private List<ImageData> data;

        // 省略getter/setter
    }

    static class ImageData {
        private String url;

        public ImageData(String url) {
            this.url = url;
        }

        // 省略getter/setter
    }
}

2. 代码核心亮点

  • 精准重试 :仅对ArkApiException(第三方接口异常)重试,参数异常不重试;
  • 合理退避:指数退避策略,避免集中重试;
  • 限流控制:结合 Guava RateLimiter,避免触发平台限流;
  • 参数校验:前置拦截非法请求,减少无效重试;
  • 恢复机制:重试失败后返回默认图片,提升用户体验;
  • 日志完善:记录关键参数和异常信息,便于排查问题;
  • 幂等安全:图片生成是只读操作,具备幂等性,可放心重试。

九、总结

1. @Retryable 核心价值

  • 简化代码:以声明式注解替代繁琐的手动重试逻辑,代码更简洁、可读性更高;
  • 灵活配置:支持按异常类型、表达式、自定义策略精准控制重试;
  • 提升可用性:对临时故障自动重试,降低接口失败率;
  • 易于维护:重试规则集中配置,修改无需改动业务逻辑。

2. 适用场景

  • 调用第三方 API(如支付、AI 生成、短信发送);
  • 数据库 / 缓存操作(如锁竞争、连接临时断开);
  • 网络请求(如 HTTP 调用超时、TCP 连接异常);
  • 所有具备幂等性的偶发故障场景。

3. 扩展

  • 结合熔断器(Circuit Breaker):当重试失败率过高时,暂时关闭重试,避免雪崩;
  • 结合监控:通过 Spring Boot Actuator 监控重试次数、失败率,及时发现问题;
  • 配置化管理:将重试次数、延迟时间等配置到 Nacos/Apollo,无需重启应用即可调整。

@Retryable是 Spring 生态中提升接口容错能力的 "利器",掌握它的核心用法和避坑要点,能让你的代码在面对偶发故障时更健壮、更可靠。记住:重试不是 "万能药",只有结合幂等性、合理的重试策略和完善的监控,才能真正发挥它的价值。

END

如果觉得这份基础知识点总结清晰,别忘了动动小手点个赞👍,再关注一下呀~ 后续还会分享更多有关面试问题的干货技巧,同时一起解锁更多好用的功能,少踩坑多提效!🥰 你的支持就是我更新的最大动力,咱们下次分享再见呀~🌟

相关推荐
小猪配偶儿_oaken12 小时前
SpringBoot实现单号生成功能(Java&若依)
java·spring boot·okhttp
宋情写12 小时前
JavaAI04-RAG
java·人工智能
毕设源码-钟学长12 小时前
【开题答辩全过程】以 中医健康管理系统为例,包含答辩的问题和答案
java
susu108301891112 小时前
docker部署 Java 项目jar
java·docker·jar
Haooog12 小时前
LangChain4j 学习
java·学习·大模型·langchain4j
爬山算法12 小时前
Hibernate(25)Hibernate的批量操作是什么?
java·后端·hibernate
2501_9418752812 小时前
从日志语义到可观测性的互联网工程表达升级与多语言实践分享随笔
java·前端·python
高山上有一只小老虎12 小时前
小红的字符串
java·算法
此剑之势丶愈斩愈烈12 小时前
mybatis-plus乐观锁
开发语言·python·mybatis