Spring中的接口重试机制解析
作者:北漂码农有话说
背景
大家在做项目的时候,往往会遇到一些接口由于网络抖动等问题导致接口响应超时等,这时候我们会希望能够按照一定的规则进行接口 请求重试。
分析
一般情况下,以上描述的情况,我们可能需要后台的定时任务去重新发起调用,以达到目的,这样无疑会增加开发成本,并且还得考虑 请求报文的保存等等问题。
这时我们可以使用Spring提供的功能来完成这个需求。
实现
假设我们现在有一个接口,在这个接口流程中会出现一些异常,比如超时异常(数据库的异常、远程调用的异常等),出现这样的异常 我们就希望能够自动发起重新调用的功能。
创建工程
我们为了测试方便就直接创建一个简单的Spring Boot的工程就可以了。创建的时候引入如下依赖:
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> <!--AOP依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
说明:由于Spring重试机制的基于注解的AOP实现,所以我们需要映入AOP的依赖,我们是Spring Boot项目直接使用AOP的启动器就可以了。
启动注解
由于我们是继续Spring Boot来开发这部分代码,所以我们需要配置开启重试机制的的注解,代码如下:
@SpringBootApplication @EnableRetry public class SpringbootApplication { public static void main(String[] args) { SpringApplication.run(SpringbootApplication.class, args); } }
说明:Spring重试机制主要是使用注解@Retryable来实现的
@Retryable
该注解的源码如下:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Retryable { String recover() default ""; String interceptor() default ""; Class<? extends Throwable>[] value() default {}; Class<? extends Throwable>[] include() default {}; Class<? extends Throwable>[] exclude() default {}; String label() default ""; boolean stateful() default false; int maxAttempts() default 3; String maxAttemptsExpression() default ""; Backoff backoff() default @Backoff; String exceptionExpression() default ""; String[] listeners() default {}; }
说明:
- value:抛出指定异常才会重试
- include:和value一样,默认为空,当exclude也为空时,默认所有异常
- exclude:指定不处理的异常
- maxAttempts:最大重试次数,默认3次
- backoff:重试等待策略, 默认使用@Backoff,@Backoff的value默认为1000, 以毫秒为单位的延迟(默认 1000)
- multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为1.5,则第一次重试为2秒,第二次为3秒,第三次为4.5秒。
注解@Retryable切面类
该注解主要的切面拦截器如下代码,感兴趣的小伙伴可以自行查看源码,分析。
AnnotationAwareRetryOperationsInterceptor#invoke()
@Recover
Spring-Retry还提供了@Recover注解,用于@Retryable重试失败后处理方法。如果不需要回调方法,可以直接不写回调方法,那么实现的效果是,重试次数完了后,如果还是没成功没符合业务判断,就抛出异常。 可以看到传参里面写的是Exception e,这个是作为回调的标识(重试次数用完了,还是失败,我们抛出这个Exception e通知触发这个回调方法)。
注意事项:
1、方法的返回值必须与@Retryable方法一致
2、方法的第一个参数,必须是Throwable类型的,建议是与@Retryable配置的异常一致,其他的参数,需要哪个参数,写进去就可以了(@Recover方法中有的)
3、该回调方法与重试方法写在同一个实现类里面
4、由于是基于AOP实现,所以不支持类里自调用方法
5、如果重试失败需要给@Recover注解的方法做后续处理,那这个重试的方法不能有返回值,只能是void
6、方法内不能使用try catch,只能往外抛异常
7、@Recover注解来开启重试失败后调用的方法(注意,需跟重处理方法在同一个类中),此注解注释的方法参数一定要是@Retryable抛出的异常。
该注解的代码如下:
@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Import(RetryConfiguration.class) @Documented public @interface Recover { }
注解@Recover切面类
AnnotationAwareRetryOperationsInterceptor#getRecoverer()
写一个Demo 创建接口
首先我们创建一个接口。代码如下:
public interface RetryService { /** * 重试方法 * @param str * @return * @throws Exception */ String retry(String str) throws Exception; /** * 回调方法 * @param e * @param str * @return */ String recover(Exception e,String str); }
接口实现
上述接口的实现的代码如下:
@Service @Slf4j public class RetryServiceImpl implements RetryService { /** * 重试方法 * @param str * @return */ @Override @Retryable(value = Exception.class,maxAttempts = 5,backoff = @Backoff(delay = 2000,multiplier = 1.5)) public String retry(String str) throws Exception { log.info("Service 请求入参为:{}",str); log.info("进入测试方法,目前时间为:{}",new Date()); if ("succ".equals(str)){ return "succ"; }else { throw new Exception("异常了!"); } } /** * 重试次数完成后,回调的方法 * @param e * @param str * @return */ @Override @Recover public String recover(Exception e, String str) { log.info("异常出现后的回调操作,入参为:{},当前时间为:{}", str,LocalDate.now()); return null; } }
测试
我们使用postman进行测试
创建测试类
我们创建一个控制器来测试功能。代码如下:
@RestController @Slf4j public class RetryController { @Resource RetryService retryService; @GetMapping("/re") public String retry(@RequestParam("str") String str) throws Exception{ log.info("Controller 请求入参为:{}",str); return retryService.retry(str); } }
说明:我们的入参是一个字符串,若传入的字符串非succ那么手动抛出异常,我们观察日志,看是否框架自动发起了重试。
测试
我们启动Sping Boot项目,并且进行测试,来看一下效果。
使用postman请求我们的login方法,我们观察一下日志。请求地址localhost:8111/re,日志如下:
2023-07-17 21:05:19.488 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.controller.RetryController : Controller 请求入参为:111
2023-07-17 21:05:19.516 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : Service 请求入参为:111
2023-07-17 21:05:19.516 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : 进入测试方法,目前时间为:Mon Jul 17 21:05:19 CST 2023
2023-07-17 21:05:21.518 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : Service 请求入参为:111
2023-07-17 21:05:21.519 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : 进入测试方法,目前时间为:Mon Jul 17 21:05:21 CST 2023
2023-07-17 21:05:24.522 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : Service 请求入参为:111
2023-07-17 21:05:24.523 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : 进入测试方法,目前时间为:Mon Jul 17 21:05:24 CST 2023
2023-07-17 21:05:29.024 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : Service 请求入参为:111
2023-07-17 21:05:29.025 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : 进入测试方法,目前时间为:Mon Jul 17 21:05:29 CST 2023
2023-07-17 21:05:35.779 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : Service 请求入参为:111
2023-07-17 21:05:35.779 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : 进入测试方法,目前时间为:Mon Jul 17 21:05:35 CST 2023
2023-07-17 21:05:35.790 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : 异常出现后的回调操作,入参为:111,当前时间为:2023-07-17
可以看到,出现异常后框架自动发起了重试,在重试次数使用完成后,回调了异常处理的方法。
到此这篇关于Spring中的接口重试机制解析的文章就介绍到这了,更多相关Spring接口重试内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!