本文最后更新于:March 27, 2023 pm
                
              
            
            
              SpringBoot框架中有两个非常重要的策略:开箱即用和约定优于配置。其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。
目录
Spring-Retry是Spring提供的一个基于Spring的重试框架,某些场景需要对一些异常情况下的方法进行重试。
依赖
|  | <dependency><groupId>org.springframework.retry</groupId>
 <artifactId>spring-retry</artifactId>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>
 
 | 
启动声明
在启动类上添加@EnableRetry注解表示启用。
|  | package com.tothefor.retrydemo;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.retry.annotation.EnableRetry;
 
 @EnableRetry
 @SpringBootApplication
 public class RetryDemoApplication {
 
 public static void main(String[] args) {
 SpringApplication.run(RetryDemoApplication.class, args);
 }
 
 }
 
 
 | 
环境准备
一个服务接口及其实现类:
|  | package com.tothefor.retrydemo;
 
 
 
 
 
 
 public interface DemoService {
 void message();
 Integer getAll();
 }
 
 
 | 
实现类:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 | package com.tothefor.retrydemo;
 import org.springframework.stereotype.Service;
 
 
 
 
 
 
 
 @Service
 public class DemoServiceImpl implements DemoService {
 @Override
 public void message() {
 System.out.println("MapperImpl-message " + new Date());
 }
 
 @Override
 public Integer getAll() {
 System.out.println("MapperImpl-getAll " + new Date());
 return 1;
 }
 }
 
 
 | 
处理
以上是正常开发中的具体实现。为了测试重试机制,所以需要添加抛异常:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 
 | package com.tothefor.retrydemo;
 import org.springframework.stereotype.Service;
 
 
 
 
 
 
 
 @Service
 public class DemoServiceImpl implements DemoService {
 @Override
 public void message() {
 System.out.println("MapperImpl-message " + new Date());
 throw new RuntimeException("message Error");
 }
 
 @Override
 public Integer getAll() {
 System.out.println("MapperImpl-getAll " + new Date());
 if (true) {
 throw new RuntimeException("getAll Error");
 }
 return 1;
 }
 }
 
 
 | 
重试
当抛出异常时,可能是因为网络波动或者其他原因造成的,也许再重试几次就可以了。所以,接下来就通过Spring-Retry实现重试机制。
在需要重试的方法上添加注解@Retryable,它有几个参数:
- value:指定抛出的异常,只有抛出该异常才会重试。
- include:和value一样,默认为空,当exclude也为空时,默认所有异常。
- exclude:指定不处理的异常。
- maxAttempts:最大重试次数,默认3次。
- backoff:重试等待策略,默认使用- @Backoff,Backoff的参数有:- 
- value重试的间隔,默认为1000(单位毫秒)。
- multiplier(指定延迟倍数)默认为0.0D。即第一次重试与原本的请求间隔为value的值,第二次的重试与第一次重试的间隔时间为valuemultiplier1,第三次的重试与第二次的间隔为valuemultiplier2,以此类推。
 
如下实例:
方法处理
|  | @Retryable(value = Exception.class, maxAttempts = 4, backoff = @Backoff(delay = 2000, multiplier = 2))@Override
 public void message() {
 System.out.println("MapperImpl-message " + new Date());
 throw new RuntimeException("message Error");
 }
 
 | 
测试
|  | @Autowiredprivate DemoService demoService;
 
 @Test
 void contextLoads() {
 demoService.message();
 }
 
 | 
输出
|  | MapperImpl-message Mon Mar 27 14:13:30 CST 2023MapperImpl-message Mon Mar 27 14:13:32 CST 2023
 MapperImpl-message Mon Mar 27 14:13:36 CST 2023
 MapperImpl-message Mon Mar 27 14:13:44 CST 2023
 
 java.lang.RuntimeException: message Error
 
 | 
第一次为原本的请求,第二次与第一次之间的间隔为我们设置的2秒,第三次和第二次是4秒,然后是8秒。然后因为所有的重试均失败了,所以最后还是抛出了异常。
补偿措施
在上面的测试中,当所有重试均失败后还是抛出了异常。但是,可能我们在有些情况下并不希望抛出异常,所以,Spring-Retry还提供了@Recover注解,用于@Retryable重试失败后处理方法。
示例:
|  | @Retryable(value = Exception.class, maxAttempts = 4, backoff = @Backoff(delay = 2000, multiplier = 2))@Override
 public void message() {
 System.out.println("MapperImpl-message " + new Date());
 throw new RuntimeException("message Error");
 }
 
 @Recover
 public void messageTry(Exception e){
 System.out.println("MapperImpl-messageTry " + new Date());
 }
 
 | 
输出:
|  | MapperImpl-message Mon Mar 27 14:29:29 CST 2023MapperImpl-message Mon Mar 27 14:29:31 CST 2023
 MapperImpl-message Mon Mar 27 14:29:35 CST 2023
 MapperImpl-message Mon Mar 27 14:29:43 CST 2023
 MapperImpl-messageTry Mon Mar 27 14:29:43 CST 2023
 
 | 
指定补偿方法
上面只是测试了一个补偿方法,但是如果有多个补偿方法呢?如下示例:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 
 | package com.tothefor.retrydemo;
 import org.springframework.retry.annotation.Backoff;
 import org.springframework.retry.annotation.Recover;
 import org.springframework.retry.annotation.Retryable;
 import org.springframework.stereotype.Service;
 
 import java.util.Date;
 
 
 
 
 
 
 
 @Service
 public class DemoServiceImpl implements DemoService {
 
 @Retryable(value = Exception.class, maxAttempts = 4, backoff = @Backoff(delay = 2000, multiplier = 2))
 @Override
 public void message() {
 System.out.println("MapperImpl-message " + new Date());
 throw new RuntimeException("message Error");
 }
 
 @Recover
 public void messageTry(Exception e){
 System.out.println("MapperImpl-messageTry " + new Date());
 }
 
 @Recover
 public void getAllTry(RuntimeException e){
 System.out.println("MapperImpl-getAllTry " + new Date());
 }
 
 @Retryable(value = RuntimeException.class, maxAttempts = 4, backoff = @Backoff(delay = 2000, multiplier = 2))
 @Override
 public Integer getAll() {
 System.out.println("MapperImpl-getAll " + new Date());
 if (true) {
 throw new RuntimeException("getAll Error");
 }
 return 1;
 }
 }
 
 
 | 
测试:
|  | @Autowiredprivate DemoService demoService;
 
 @Test
 void contextLoads() {
 demoService.message();
 System.out.println(demoService.getAll());
 }
 
 | 
输出:
|  | MapperImpl-message Mon Mar 27 14:32:47 CST 2023MapperImpl-message Mon Mar 27 14:32:49 CST 2023
 MapperImpl-message Mon Mar 27 14:32:53 CST 2023
 MapperImpl-message Mon Mar 27 14:33:01 CST 2023
 MapperImpl-getAllTry Mon Mar 27 14:33:01 CST 2023
 MapperImpl-getAll Mon Mar 27 14:33:01 CST 2023
 MapperImpl-getAll Mon Mar 27 14:33:03 CST 2023
 MapperImpl-getAll Mon Mar 27 14:33:07 CST 2023
 MapperImpl-getAll Mon Mar 27 14:33:15 CST 2023
 
 org.springframework.retry.ExhaustedRetryException: Cannot locate recovery method; nested exception is java.lang.RuntimeException: getAll Error
 
 | 
可以看见,有一个方法并没有走对应的补偿方法,但是,如果将getAllTry()方法修改后一下:
|  | @Recoverpublic Integer getAllTry(RuntimeException e){
 System.out.println("MapperImpl-getAllTry " + new Date());
 return 2;
 }
 
 | 
就是正常输出:
|  | MapperImpl-message Mon Mar 27 14:37:41 CST 2023MapperImpl-message Mon Mar 27 14:37:43 CST 2023
 MapperImpl-message Mon Mar 27 14:37:47 CST 2023
 MapperImpl-message Mon Mar 27 14:37:55 CST 2023
 MapperImpl-messageTry Mon Mar 27 14:37:55 CST 2023
 MapperImpl-getAll Mon Mar 27 14:37:55 CST 2023
 MapperImpl-getAll Mon Mar 27 14:37:57 CST 2023
 MapperImpl-getAll Mon Mar 27 14:38:01 CST 2023
 MapperImpl-getAll Mon Mar 27 14:38:09 CST 2023
 MapperImpl-getAllTry Mon Mar 27 14:38:09 CST 2023
 2
 
 | 
因为找寻对应的补偿方法是通过方法返回值和异常决定的。当然我们也可以直接指定补偿方法,但是被指定的方法的返回值、方法参数和异常类型都必须和抛出异常的方法保持一致:
|  | @Recoverpublic Integer getAll22222Try(RuntimeException e,int code){
 System.out.println("MapperImpl-getAll22222Try " + new Date());
 return 4;
 }
 
 @Retryable(recover = "getAll22222Try",value = RuntimeException.class, maxAttempts = 4, backoff = @Backoff(delay = 2000, multiplier = 2))
 @Override
 public Integer getAll(int code) {
 System.out.println("MapperImpl-getAll " + new Date());
 if (true) {
 throw new RuntimeException("getAll Error");
 }
 return 1;
 }
 
 | 
但是,补偿方法中的异常类型可以是@Retryable中异常类型的父类,如:
|  | getAll22222Try(Exception e,int code)value = RuntimeException.class
 
 | 
最后
以上只是对Spring-Retry的一个简单使用,如果需要实现更加复杂的,就需要使用RetryTemplate了。Spring-Retry的官方地址:https://github.com/spring-projects/spring-retry