3

SpringBoot 下使用 @Retryable 自动重试注解

 1 year ago
source link: https://maxqiu.com/article/detail/144
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

SpringBoot 下使用 @Retryable 自动重试注解

SpringBoot 下使用 @Retryable 自动重试注解

2022/10/08  Java  SpringBoot

当系统中调用一些第三方服务时(如使用 http 请求),如果第三方服务不是很稳定(比如网络波动),可以使用 SpringBoot 的自动重试功能

以下代码以 SpringBoot 2.7.3 为例

pom.xml 中引入如下依赖



  1. <!-- 引入重试 -->
  2. <dependency>
  3. <groupId>org.springframework.retry</groupId>
  4. <artifactId>spring-retry</artifactId>
  5. </dependency>
  6. <!-- 额外添加aspectj -->
  7. <dependency>
  8. <groupId>org.aspectj</groupId>
  9. <artifactId>aspectjweaver</artifactId>
  10. </dependency>

必须要引入 aspectj ,否则会显示 NoClassDefFoundError 异常

Application 启动类

在启动类中,需要添加 @EnableRetry 注解



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

service 服务类

主要使用以下三个注解,具体配置项见代码注解

  • @Retryable:指定哪个方法执行重试
  • @Backoff:延迟配置
  • @Recover:最终回调处理


  1. import java.net.http.HttpConnectTimeoutException;
  2. import java.time.LocalTime;
  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.Service;
  7. import lombok.extern.slf4j.Slf4j;
  8. @Service
  9. @Slf4j
  10. public class RetryService {
  11. /**
  12. * 需要执行重试的方法
  13. */
  14. @Retryable(
  15. // include:同value,当执行重试的异常类型(可以多个)
  16. // include = Exception.class,
  17. // exclude:要排除的异常类型(可以多个)
  18. // exclude = {},
  19. // 当 include 和 exclude 均为空时,所有异常均重试
  20. // maxAttempts:最大重试次数(包括第一次失败)
  21. maxAttempts = 5,
  22. // 重试配置
  23. backoff = @Backoff(
  24. // 延迟时间,单位:毫秒
  25. delay = 1000,
  26. // 最大延迟时间(默认值为0即不启用,若小于delay值则为3000),单位:毫秒
  27. maxDelay = 3000,
  28. // 相对上一次延迟时间的倍数(比如2:第一次1000毫秒,第二次2000毫秒,第三次4000毫秒...)
  29. multiplier = 2))
  30. public int retry(Integer code, String name) throws Exception {
  31. log.info("retryTest被调用,时间:{}", LocalTime.now());
  32. if (code == 1) {
  33. // 此处随意使用了一个检查异常
  34. throw new HttpConnectTimeoutException("抛出自定义异常信息!");
  35. }
  36. // 这里可能会产生非检查异常
  37. int i = 2 / code;
  38. log.info("retryTest调用成功!");
  39. return i;
  40. }
  41. /**
  42. * 使用 @Recover 注解做最终失败处理(可以针对不同的异常定义多个最终失败处理)<br>
  43. * 第一个参数:需要处理的异常类型<br>
  44. * 后面的参数:(可选)与重试方法相同顺序和类型的参数<br>
  45. * 返回值类型:必须与重试方法的类型相同
  46. */
  47. @Recover
  48. public int recover(HttpConnectTimeoutException e, Integer code, String name) {
  49. log.error("HttpConnectTimeoutException回调方法执行!");
  50. log.error("异常信息:{}", e.getMessage());
  51. log.error("参数code:{}", code);
  52. log.error("参数name:{}", name);
  53. return 1;
  54. }
  55. /**
  56. * 建议定义一个所有 Exception 异常处理,用于处理非检查异常
  57. */
  58. @Recover
  59. public int recover(Exception e) {
  60. log.error("Exception回调方法执行!");
  61. log.error("异常信息:{}", e.getMessage());
  62. return 1;
  63. }
  64. }

编写测试类



  1. import javax.annotation.Resource;
  2. import org.junit.jupiter.api.Test;
  3. import org.springframework.boot.test.context.SpringBootTest;
  4. import com.maxqiu.demo.service.RetryService;
  5. import lombok.extern.slf4j.Slf4j;
  6. @SpringBootTest
  7. class RetryServiceTest {
  8. @Resource
  9. private RetryService retryService;
  10. @Test
  11. void retryTest() {
  12. try {
  13. log.info("调用方法,参数:{}", 1);
  14. log.info("返回结果:{}\n", retryService.retry(1, "张三"));
  15. log.info("返回结果:{}\n", retryService.retry(0, "张三"));
  16. log.info("返回结果:{}\n", retryService.retry(2, "张三"));
  17. } catch (Exception e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }


  1. 2022-10-08 22:04:21.976 INFO 14572 --- [ main] com.maxqiu.demo.RetryServiceTest : 调用方法,参数:1
  2. 2022-10-08 22:04:22.012 INFO 14572 --- [ main] com.maxqiu.demo.service.RetryService : 方法被调用,时间:22:04:22.012843100
  3. 2022-10-08 22:04:23.022 INFO 14572 --- [ main] com.maxqiu.demo.service.RetryService : 方法被调用,时间:22:04:23.022396600
  4. 2022-10-08 22:04:25.027 INFO 14572 --- [ main] com.maxqiu.demo.service.RetryService : 方法被调用,时间:22:04:25.027826100
  5. 2022-10-08 22:04:29.038 INFO 14572 --- [ main] com.maxqiu.demo.service.RetryService : 方法被调用,时间:22:04:29.038549100
  6. 2022-10-08 22:04:34.051 INFO 14572 --- [ main] com.maxqiu.demo.service.RetryService : 方法被调用,时间:22:04:34.051500400
  7. 2022-10-08 22:04:34.051 ERROR 14572 --- [ main] com.maxqiu.demo.service.RetryService : HttpConnectTimeoutException回调方法执行!
  8. 2022-10-08 22:04:34.052 ERROR 14572 --- [ main] com.maxqiu.demo.service.RetryService : 异常信息:抛出自定义异常信息!
  9. 2022-10-08 22:04:34.052 ERROR 14572 --- [ main] com.maxqiu.demo.service.RetryService : 参数code:1
  10. 2022-10-08 22:04:34.052 ERROR 14572 --- [ main] com.maxqiu.demo.service.RetryService : 参数name:张三
  11. 2022-10-08 22:04:34.052 INFO 14572 --- [ main] com.maxqiu.demo.RetryServiceTest : 返回结果:指定异常处理
  12. 2022-10-08 22:04:34.052 INFO 14572 --- [ main] com.maxqiu.demo.service.RetryService : 方法被调用,时间:22:04:34.052502
  13. 2022-10-08 22:04:35.053 INFO 14572 --- [ main] com.maxqiu.demo.service.RetryService : 方法被调用,时间:22:04:35.053312200
  14. 2022-10-08 22:04:37.068 INFO 14572 --- [ main] com.maxqiu.demo.service.RetryService : 方法被调用,时间:22:04:37.068952200
  15. 2022-10-08 22:04:41.069 INFO 14572 --- [ main] com.maxqiu.demo.service.RetryService : 方法被调用,时间:22:04:41.069102200
  16. 2022-10-08 22:04:46.080 INFO 14572 --- [ main] com.maxqiu.demo.service.RetryService : 方法被调用,时间:22:04:46.080276100
  17. 2022-10-08 22:04:46.081 ERROR 14572 --- [ main] com.maxqiu.demo.service.RetryService : Exception回调方法执行!
  18. 2022-10-08 22:04:46.081 ERROR 14572 --- [ main] com.maxqiu.demo.service.RetryService : 异常信息:/ by zero
  19. 2022-10-08 22:04:46.081 INFO 14572 --- [ main] com.maxqiu.demo.RetryServiceTest : 返回结果:所有异常处理
  20. 2022-10-08 22:04:46.081 INFO 14572 --- [ main] com.maxqiu.demo.service.RetryService : 方法被调用,时间:22:04:46.081032900
  21. 2022-10-08 22:04:46.081 INFO 14572 --- [ main] com.maxqiu.demo.service.RetryService : 方法调用成功!,结果:1
  22. 2022-10-08 22:04:46.082 INFO 14572 --- [ main] com.maxqiu.demo.RetryServiceTest : 返回结果:SUCCESS
  1. 在发生异常时,方法被调用了 5 次,说明重试生效了
  2. 方法重试的时间间隔逐渐增大且不超过 5s ,说明延迟配置生效了
  3. 指定的检查异常和非检查异常都可以捕获,说明回调生效了

@Retryable 修饰的方法被同一个类的其他普通方法调用时不会生效,比如下文中调用 callInSameClass 会直接抛出异常:



  1. @Service
  2. @Slf4j
  3. public class RetryService {
  4. /**
  5. * 如果在同一个类中调用重试方法,重试注解不会生效
  6. */
  7. public String callInSameClass(int code, String name) throws Exception {
  8. return retry(code, name);
  9. }
  10. /**
  11. * 需要执行重试的方法
  12. */
  13. @Retryable
  14. public String retry(Integer code, String name) throws Exception {
  15. ...
  16. }
  17. }


  1. 2022-10-08 22:05:10.956 INFO 15332 --- [ main] com.maxqiu.demo.RetryServiceTest : 调用方法,参数:1
  2. 2022-10-08 22:05:10.976 INFO 15332 --- [ main] com.maxqiu.demo.service.RetryService : 方法被调用,时间:22:05:10.976267400
  3. java.net.http.HttpConnectTimeoutException: 抛出自定义异常信息!

正确的调用方式应该是在另一个类中调用重试方法



  1. import javax.annotation.Resource;
  2. import org.springframework.stereotype.Service;
  3. @Service
  4. public class CallService {
  5. @Resource
  6. private RetryService retryService;
  7. /**
  8. * 如果在另一个类中调用重试方法,可以生效
  9. */
  10. public String callInOtherClass(int code, String name) throws Exception {
  11. return retryService.retry(code, name);
  12. }
  13. }

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK