
在日常开发中,我们经常会遇到这样的场景:调用第三方接口时因网络抖动超时、数据库操作因锁竞争失败、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:数组类型,指定哪些异常触发重试(推荐使用,语义更清晰);value:retryFor的别名,功能完全一致,只是命名不同。
默认值 :空数组,会触发所有Exception类型的异常重试。
示例 :仅当抛出RuntimeException和IOException时触发重试:
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=1000,multiplier=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
如果觉得这份基础知识点总结清晰,别忘了动动小手点个赞👍,再关注一下呀~ 后续还会分享更多有关面试问题的干货技巧,同时一起解锁更多好用的功能,少踩坑多提效!🥰 你的支持就是我更新的最大动力,咱们下次分享再见呀~🌟
