使用场景
在日常开发过程中,难免会与第三方接口发生交互,例如:短信发送、远程服务调用、争抢锁等场景,当正常调用发生异常时,例如:网络抖动,这些间歇性的异常在一段时候之后会自行恢复,程序为了更加健壮并且更不容易出现故障,需要重新触发业务操作,以防止间歇性的异常对程序照成的影响。
如何使用
1、 引入spring-retry
这里我们还引入了 aop 的依赖,因为 spring-retry 的原理就是基于 aop 来实现的
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2、开启spring-retry
在启动类增加注解@EnableRetry
@SpringBootApplication
@EnableRetry
public class MyApplication {
public static void main(String[] args) {
// 启动spring-boot-plus
ConfigurableApplicationContext context = SpringApplication.run(MyApplication.class, args);
// 打印项目信息
PrintApplicationInfo.print(context);
}
}
3、在方法上添加@Retryable
@Retryable( value = {RuntimeException.class}, maxAttempts = 5, backoff = @Backoff(delay = 1000, multiplier = 2))
public String testRetry() {
return "执行成功";
}
@Retryable 注解
-
value,可重试的异常类型。含义同include。默认为空(如果excludes也为空,则重试所有异常)
-
include:可重试的异常类型。默认为空(如果excludes也为空,则重试所有异常)
-
exclude:无需重试的异常类型。默认为空(如果includes也为空,则重试所有异常)
-
maxAttempts:最大重试次数(包括第一次失败),默认为3次
-
backoff:重试等待策略,下面会在@Backoff中介绍
-
recover:表示重试次数到达最大重试次数后的回调方法
@Backoff 注解
-
delay,重试之间的等待时间(以毫秒为单位)
-
maxDelay,重试之间的最大等待时间(以毫秒为单位)
-
multiplier,指定延迟的倍数
-
delayExpression,重试之间的等待时间表达式
-
maxDelayExpression,重试之间的最大等待时间表达式
-
multiplierExpression,指定延迟的倍数表达式
-
random,随机指定延迟时间
4、重试耗尽后回调
当重试耗尽时,RetryOperations可以将控制传递给另一个回调,即RecoveryCallback。Spring-Retry还提供了@Recover注解,用于@Retryable重试失败后处理方法。如果不需要回调方法,可以直接不写回调方法,重试耗尽实现的效果是,重试次数完了后,如果还是没成功没符合业务判断,就抛出异常。
@Recover
public String recoverTest(RuntimeException e) {
return "回调方法-" + e.getMessage();
}
- 方法的返回值必须与 @Retryable 方法一致
- 方法的第一个参数,必须是 Throwable 类型的,建议是与 @Retryable 配置的异常一致,其他的参数,需要哪个参数,写进去就可以了(@Recover 方法中有的)
- 该回调方法与重试方法写在同一个实现类里面
- 若同一个实现类中有多个回调方法,我们需要使用 recover 属性指定回调的方法名,如下面代码,指定回调的方法名是recoverTest1
@Retryable(recover = "recoverTest1", value = {RuntimeException.class}, maxAttempts = 5, backoff = @Backoff(delay = 1000, multiplier = 2))
public String testRetry() {
return "执行成功";
}
@Recover
public String recoverTest(RuntimeException e) {
return "回调方法-" + e.getMessage();
}
@Recover
public String recoverTest1(RuntimeException e) {
return "回调方法-" + e.getMessage();
}
注意事项
- 由于是基于 AOP 实现,所以不支持类里自调用方法
- 如果重试失败需要给 @Recover 注解的方法做后续处理,那这个重试的方法不能有返回值,只能是 void
- 方法内不能使用 try catch,只能往外抛异常
- @Recover 注解来开启重试失败后调用的方法(注意,需跟重处理方法在同一个类中),此注解注释的方法参数一定要是 @Retryable 抛出的异常,否则无法识别,可以在该方法中进行日志处理。
demo
1、controller
@Slf4j
@RestController
@RequestMapping("/partyManageInfo")
public class ManageInfoController extends BaseController {
private final ManageInfoServiceImpl manageInfoServiceImpl;
public ManageInfoController(ManageInfoServiceImpl manageInfoServiceImpl) {
this.manageInfoServiceImpl = manageInfoServiceImpl;
}
/**
* 修改党委管理干部信息
*/
@PostMapping("/test")
@ApiOperation(value = "测试重试", notes = "测试重试")
public ApiResult<Boolean> testRetry() {
manageInfoServiceImpl.testRetry();
return ApiResult.ok();
}
}
3、service
@Slf4j
@Service
public class ManageInfoServiceImpl{
int a = 0;
@Retryable( value = {RuntimeException.class}, maxAttempts = 5, backoff = @Backoff(delay = 1000, multiplier = 2))
public String testRetry() {
a++;
System.out.println(a + " - 调用时间是" + LocalTime.now());
if (a < 10) {
throw new RuntimeException("未满足条件");
}
return "执行成功";
}
@Recover
public String recoverTest(RuntimeException e) {
System.out.println("回调方法调用时间是-"+ LocalTime.now()+",异常信息-" + e.getMessage());
return "回调方法-" + e.getMessage();
}
}
3、调接口测试结果