本文最后更新于: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(); }
|
实现类:
1 2 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; } }
|
处理
以上是正常开发中的具体实现。为了测试重试机制,所以需要添加抛异常:
1 2 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"); }
|
测试
| @Autowired private DemoService demoService;
@Test void contextLoads() { demoService.message(); }
|
输出
| MapperImpl-message Mon Mar 27 14:13:30 CST 2023 MapperImpl-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 2023 MapperImpl-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
|
指定补偿方法
上面只是测试了一个补偿方法,但是如果有多个补偿方法呢?如下示例:
1 2 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; } }
|
测试:
| @Autowired private DemoService demoService;
@Test void contextLoads() { demoService.message(); System.out.println(demoService.getAll()); }
|
输出:
| MapperImpl-message Mon Mar 27 14:32:47 CST 2023 MapperImpl-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()方法修改后一下:
| @Recover public Integer getAllTry(RuntimeException e){ System.out.println("MapperImpl-getAllTry " + new Date()); return 2; }
|
就是正常输出:
| MapperImpl-message Mon Mar 27 14:37:41 CST 2023 MapperImpl-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
|
因为找寻对应的补偿方法是通过方法返回值和异常决定的。当然我们也可以直接指定补偿方法,但是被指定的方法的返回值、方法参数和异常类型都必须和抛出异常的方法保持一致:
| @Recover public 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