经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
5分钟攻略Spring-Retry框架实现经典重试场景
来源:cnblogs  作者:程序员济癫  时间:2023/12/22 16:21:51  对本文有异议

前言

今天分享干货,控制了篇幅,5分钟内就能看完学会。

主题是Spring-Retry框架的应用,做了一个很清晰的案例,代码可下载自测。

框架介绍

Spring-Retry框架是Spring自带的功能,具备间隔重试包含异常排除异常控制重试频率等特点,是项目开发中很实用的一种框架。

本篇所用框架的版本如下:

技术 版本
Java 17
SpringBoot 3.2
Spring-retry 2.0.4

正文

1、引入依赖

坑点:需要引入AOP,否则会抛异常。

  1. <!-- Spring-Retry -->
  2. <dependency>
  3. <groupId>org.springframework.retry</groupId>
  4. <artifactId>spring-retry</artifactId>
  5. </dependency>
  6. <!-- Spring-AOP -->
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-aop</artifactId>
  10. </dependency>

2、启动类注解

坑点:很容易一时疏忽忘记启动类开启@EnableRetry,大家别忘了哦。

  1. import org.springframework.boot.SpringApplication;
  2. import org.springframework.boot.autoconfigure.SpringBootApplication;
  3. import org.springframework.retry.annotation.EnableRetry;
  4. @SpringBootApplication
  5. @EnableRetry
  6. public class SpringRetryDemoApplication {
  7. public static void main(String[] args) {
  8. SpringApplication.run(SpringRetryDemoApplication.class, args);
  9. }
  10. }

3、模拟发短信

我们模拟一个发短信功能,根据随机数分别作为成功、失败、抛出各种异常的入口。

这里抛出几种异常的目的,是为了后面演示出重试注解参数产生的效果。

  1. import cn.hutool.core.util.RandomUtil;
  2. import lombok.extern.slf4j.Slf4j;
  3. /**
  4. * <p>
  5. * 短信服务工具类
  6. * </p>
  7. *
  8. * @author 程序员济癫
  9. * @since 2023-12-21 09:40
  10. */
  11. @Slf4j
  12. public class SmsUtil {
  13. /**
  14. * 发送短信
  15. */
  16. public static boolean sendSms() {
  17. // 使用随机数模拟重试场景
  18. int num = RandomUtil.randomInt(4);
  19. log.info("[SmsUtil][sendSms]>>>> random num = {}", num);
  20. return switch (num) {
  21. case 0 ->
  22. // 模拟发生参数异常
  23. throw new IllegalArgumentException("参数有误!");
  24. case 1 ->
  25. // 模拟发生数组越界异常
  26. throw new ArrayIndexOutOfBoundsException("数组越界!");
  27. case 2 ->
  28. // 模拟成功
  29. true;
  30. case 3 ->
  31. // 模拟发生空指针界异常
  32. throw new NullPointerException();
  33. default ->
  34. // 未成功则返回false
  35. false;
  36. };
  37. }
  38. }

4、Retry应用

我们单独写一个用于重试调用的组件类,用于业务类调用。

  1. import com.example.springretrydemo.util.SmsUtil;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.retry.annotation.Backoff;
  4. import org.springframework.retry.annotation.Recover;
  5. import org.springframework.retry.annotation.Retryable;
  6. import org.springframework.stereotype.Component;
  7. import java.time.LocalDateTime;
  8. import java.time.format.DateTimeFormatter;
  9. /**
  10. * <p>
  11. * 重试组件
  12. * </p>
  13. *
  14. * @author 程序员济癫
  15. * @since 2023-12-21 09:43
  16. */
  17. @Slf4j
  18. @Component
  19. public class RetryComponent {
  20. /**
  21. * 重试机制发送短信
  22. */
  23. @Retryable(
  24. retryFor = {IllegalArgumentException.class, ArrayIndexOutOfBoundsException.class},
  25. noRetryFor = {NullPointerException.class},
  26. maxAttempts = 4,
  27. backoff = @Backoff(delay = 2000L, multiplier = 2)
  28. )
  29. public boolean sendSmsRetry() {
  30. log.info("[RetryComponent][sendSmsRetry]>>>> 当前时间:{}", getNowTime());
  31. return SmsUtil.sendSms();
  32. }
  33. /**
  34. * 兜底方法,规则:
  35. * 1、超出了最大重试次数;
  36. * 2、抛出了不进行重试的异常;
  37. */
  38. @Recover
  39. public boolean recover() {
  40. log.info("[RetryComponent][recover]>>>> 短信发送次数过多,请稍后重试!");
  41. return false;
  42. }
  43. /**
  44. * 获取当前时间
  45. */
  46. private String getNowTime() {
  47. return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
  48. }
  49. }

@Retryable注解参数说明:

  • retryFor:此参数包含的异常会触发重试机制,多个异常则以数组形式定义。
  • noRetryFor:此参数包含的异常不会触发重试机制,多个异常则以数组形式定义。
  • maxAttempts:重试最大次数,不定义则默认3次。
  • backoff:定义补偿机制,delay-延迟时间(s),multiplier-重试时间的倍数(比如设置为2,重试4次的话,补偿机制就是分别间隔2s、4s、8s做重试)

@Recover注解说明:用于兜底,当 超出了最大重试次数抛出了不进行重试的异常 时,直接执行该注解声明的兜底方法。

PS:顺便提一句,如果是 SpringBoot2.x 的版本,这里@Retryable注解的retryFor参数对应的是includenoRetryFor参数对应的是exclude,可以直接点进去看源码便一目了然。


5、JunitTest测试

我们编写一个Junit测试类来测试重试的效果,并打印出结果信息。

  1. import com.example.springretrydemo.retry.RetryComponent;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.junit.jupiter.api.Test;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.boot.test.context.SpringBootTest;
  6. @Slf4j
  7. @SpringBootTest
  8. class SpringRetryDemoApplicationTests {
  9. @Autowired
  10. private RetryComponent retryComponent;
  11. @Test
  12. void sendSmsTest() {
  13. boolean ret = retryComponent.sendSmsRetry();
  14. log.info("sendSmsTest result = {}", ret);
  15. }
  16. }

6、效果

第1次测试时,可以看到,随机数刚好都是1,走的是数组越界异常。

而这个异常在retryFor中定义了,所以执行了4次,直到结束,最后进入了兜底方法。

同时,可以看到,执行4次的频率也和预想一样是2s、4s、8s。

image

第2次测试时,可以看到,随机数是3,走的是空指针异常。

而这个异常在noRetryFor中定义了,所以接下来直接进入了兜底方法。

image

第3次测试时,可以看到,第一次随机数是0,走的参数异常,在retryFor中,所以2s后继续重试。

然后随机数是2,表示业务成功,所以直接返回了true。

这个场景就很像大家经常遇见的补偿操作,第一次发生异常失败,第二次重试后又成功了。

image

总结

Spring-retry框架还是挺实用的,但不是万能的。

优点是,简化了重试逻辑,提供了现成的重试策略,具备一定灵活性。

缺点,也很明显,生产环境使用有风险,比如在复杂场景下配置的策略有问题,有可能会导致无限重试,这个后果不用说大家也能想象。

所以,使用这个框架,一定要明确好场景再使用,我这里不推荐复杂场景下使用,因为君子不立于危墙之下

好了,今天的知识点你学会了吗?

完整代码:戳这里 --> Gitee


喜欢请点赞+关注↑↑↑,持续分享干货哦~

原文链接:https://www.cnblogs.com/fulongyuanjushi/p/17920410.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号