1 spring-retry是什么?
以往我们在进行网络请求的时候,需要考虑网络异常的情况,本文就介绍了利用spring-retry
是spring提供的一个重试框架,原本自己实现的重试机制,现在spring帮封装好提供更加好的编码体验。
2 使用步骤
2.1 引入maven库
代码如下(示例):
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.3.9.RELEASE</version> </dependency>
2.2 在spring启动类上开启重试功能
提示:添加@EnableRetry注解开启 @SpringBootApplication @EnableRetry public class SpringRetryDemoApplication { public static SpringRetryAnnotationService springRetryAnnotationService; public static SpringRetryImperativeService springRetryImperativeService; public static TranditionalRetryService tranditionalRetryService; @Autowired public void setSpringRetryAnnotationService(SpringRetryAnnotationService springRetryAnnotationService) { SpringRetryDemoApplication.springRetryAnnotationService = springRetryAnnotationService; } @Autowired public void setSpringRetryImperativeService(SpringRetryImperativeService springRetryImperativeService) { SpringRetryDemoApplication.springRetryImperativeService = springRetryImperativeService; } @Autowired public void setTranditionalRetryService(TranditionalRetryService tranditionalRetryService) { SpringRetryDemoApplication.tranditionalRetryService = tranditionalRetryService; } public static void main(String[] args) { SpringApplication.run(SpringRetryDemoApplication.class, args); springRetryAnnotationService.test(); springRetryImperativeService.test(); tranditionalRetryService.test(); } }
2.2 公共业务代码
@Service @Slf4j public class CommonService { public void test(String before) { for (int i = 0; i < 10; i++) { if (i == 2) { log.error("{}:有异常哦,我再试多几次看下还有没异常", before); throw new RuntimeException(); } log.info("{}:打印次数: {}", before, i + 1); } } public void recover(String before) { log.error("{}:还是有异常,程序有bug哦", before); } }
2.3 传统的重试做法
@Service @Slf4j public class TranditionalRetryService { @Autowired CommonService commonService; public void test() { // 定义重试次数以及重试时间间隔 int retryCount = 3; int retryTimeInterval = 3; for (int r = 0; r < retryCount; r++) { try { commonService.test("以前的做法"); } catch (RuntimeException e) { if (r == retryCount - 1) { commonService.recover("以前的做法"); return; } try { Thread.sleep(retryTimeInterval * 1000); } catch (InterruptedException ex) { ex.printStackTrace(); } } } } }
2.4 使用spring-retry的命令式编码
2.4.1 定义重试监听器
提示:监听重试过程的生命周期 @Slf4j public class MyRetryListener extends RetryListenerSupport { @Override public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) { log.info("监听到重试过程关闭了"); log.info("======================================================================="); } @Override public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) { log.info("监听到重试过程错误了"); } @Override public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) { log.info("======================================================================="); log.info("监听到重试过程开启了"); return true; } }
2.4.2 定义重试配置
提示:配置RetryTemplate重试模板类 @Configuration public class RetryConfig { @Bean public RetryTemplate retryTemplate() { // 定义简易重试策略,最大重试次数为3次,重试间隔为3s RetryTemplate retryTemplate = RetryTemplate.builder() .maxAttempts(3) .fixedBackoff(3000) .retryOn(RuntimeException.class) .build(); retryTemplate.registerListener(new MyRetryListener()); return retryTemplate; } }
2.4.3 命令式编码
@Service @Slf4j public class SpringRetryImperativeService { @Autowired RetryTemplate retryTemplate; @Autowired CommonService commonService; public void test() { retryTemplate.execute( retry -> { commonService.test("命令式"); return null; }, recovery -> { commonService.recover("命令式"); return null; } ); } }
2.5 使用spring-retry的注解式编码
@Service @Slf4j public class SpringRetryAnnotationService { @Autowired CommonService commonService; /** * 如果失败,定义重试3次,重试间隔为3s,指定恢复名称,指定监听器 */ @Retryable(value = RuntimeException.class, maxAttempts = 3, backoff = @Backoff(value = 3000L), recover = "testRecover", listeners = {"myRetryListener"}) public void test() { commonService.test("注解式"); } @Recover public void testRecover(RuntimeException runtimeException) { commonService.recover("注解式"); } }
3 SpringBoot整合spring-retry
我们使用SpringBoot来整合spring-retry组件实现重试机制。
3.1 添加@EnableRetry注解
在主启动类Application上添加@EnableRetry注解,实现对重试机制的支持
@SpringBootApplication @EnableRetry public class RetryApplication { public static void main(String[] args) { SpringApplication.run(RetryApplication.class, args); } }
注意:@EnableRetry也可以使用在配置类、ServiceImpl类、方法上
3.2 接口实现
注意:接口类一定不能少,在接口类中定义你需要实现重试的方法,否则可能会无法实现重试功能
我的测试接口类如下:
public interface RetryService { public String testRetry() throws Exception;}
3.3 添加@Retryable注解
我们针对需要实现重试的方法上添加@Retryable
注解,使该方法可以实现重试,这里我列出ServiceImpl
中的一个方法:
@Service public class RetryServiceImpl implements RetryService { @Override @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000,multiplier = 1.5)) public String testRetry() throws Exception { System.out.println("开始执行代码:"+ LocalTime.now()); int code = 0; // 模拟一直失败 if(code == 0){ // 这里可以使自定义异常,@Retryable中value需与其一致 throw new Exception("代码执行异常"); } System.out.println("代码执行成功"); return "success"; } }
说明:@Retryable配置元数据情况:
value :针对指定抛出的异常类型,进行重试,这里指定的是Exception
maxAttempts :配置最大重试次数,这里配置为3次(包含第一次和最后一次)
delay: 第一次重试延迟间隔,这里配置的是2s
multiplier :每次重试时间间隔是前一次几倍,这里是1.5倍
3.4 Controller测试代码
@RestController @RequestMapping("/test") public class TestController { // 一定要注入接口,通过接口去调用方法 @Autowired private RetryService retryService; @GetMapping("/retry") public String testRetry() throws Exception { return retryService.testRetry(); } }
3.5 发送请求
发送请求后,我们发现后台打印情况,确实重试了3次,并且在最后一次重试失败的情况下,才抛出异常,具体如下(可以注意下时间间隔)
3.6 补充:@Recover
一般情况下,我们重试最大设置的次数后,仍然失败抛出异常,我们会通过全局异常处理类进行统一处理,但是我们其实也可以自行处理,可以通过@Recover
注解来实现,具体如下:
@Service public class RetryServiceImpl implements RetryService { @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000,multiplier = 1.5)) public String testRetry() throws Exception { System.out.println("开始执行代码:"+ LocalTime.now()); int code = 0; if(code == 0){ // 这里可以使自定义异常,@Retryable中value需与其一致 throw new Exception("代码执行异常"); } System.out.println("代码执行成功"); return "success"; } /** * 最终重试失败处理 * @param e * @return */ @Recover public String recover(Exception e){ System.out.println("代码执行重试后依旧失败"); return "fail"; } }
1)@Recover的方法中的参数异常类型需要与重试方法中一致
2)该方法的返回值类型与重试方法保持一致
再次测试如下(发现不会再抛出异常):