Spring中的接口重試機(jī)制解析
背景
大家在做項(xiàng)目的時(shí)候,往往會(huì)遇到一些接口由于網(wǎng)絡(luò)抖動(dòng)等問(wèn)題導(dǎo)致接口響應(yīng)超時(shí)等,這時(shí)候我們會(huì)希望能夠按照一定的規(guī)則進(jìn)行接口 請(qǐng)求重試。
分析
一般情況下,以上描述的情況,我們可能需要后臺(tái)的定時(shí)任務(wù)去重新發(fā)起調(diào)用,以達(dá)到目的,這樣無(wú)疑會(huì)增加開(kāi)發(fā)成本,并且還得考慮 請(qǐng)求報(bào)文的保存等等問(wèn)題。
這時(shí)我們可以使用Spring提供的功能來(lái)完成這個(gè)需求。
實(shí)現(xiàn)
假設(shè)我們現(xiàn)在有一個(gè)接口,在這個(gè)接口流程中會(huì)出現(xiàn)一些異常,比如超時(shí)異常(數(shù)據(jù)庫(kù)的異常、遠(yuǎn)程調(diào)用的異常等),出現(xiàn)這樣的異常 我們就希望能夠自動(dòng)發(fā)起重新調(diào)用的功能。
創(chuàng)建工程
我們?yōu)榱藴y(cè)試方便就直接創(chuàng)建一個(gè)簡(jiǎn)單的Spring Boot的工程就可以了。創(chuàng)建的時(shí)候引入如下依賴(lài):
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency> <!--AOP依賴(lài)--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
說(shuō)明:由于Spring重試機(jī)制的基于注解的AOP實(shí)現(xiàn),所以我們需要映入AOP的依賴(lài),我們是Spring Boot項(xiàng)目直接使用AOP的啟動(dòng)器就可以了。
啟動(dòng)注解
由于我們是繼續(xù)Spring Boot來(lái)開(kāi)發(fā)這部分代碼,所以我們需要配置開(kāi)啟重試機(jī)制的的注解,代碼如下:
@SpringBootApplication @EnableRetry public class SpringbootApplication { public static void main(String[] args) { SpringApplication.run(SpringbootApplication.class, args); } }
說(shuō)明:Spring重試機(jī)制主要是使用注解@Retryable來(lái)實(shí)現(xiàn)的
@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 {}; }
說(shuō)明:
- value:拋出指定異常才會(huì)重試
- include:和value一樣,默認(rèn)為空,當(dāng)exclude也為空時(shí),默認(rèn)所有異常
- exclude:指定不處理的異常
- maxAttempts:最大重試次數(shù),默認(rèn)3次
- backoff:重試等待策略, 默認(rèn)使用@Backoff,@Backoff的value默認(rèn)為1000, 以毫秒為單位的延遲(默認(rèn) 1000)
- multiplier(指定延遲倍數(shù))默認(rèn)為0,表示固定暫停1秒后進(jìn)行重試,如果把multiplier設(shè)置為1.5,則第一次重試為2秒,第二次為3秒,第三次為4.5秒。
注解@Retryable切面類(lèi)
該注解主要的切面攔截器如下代碼,感興趣的小伙伴可以自行查看源碼,分析。
AnnotationAwareRetryOperationsInterceptor#invoke()
@Recover
Spring-Retry還提供了@Recover注解,用于@Retryable重試失敗后處理方法。如果不需要回調(diào)方法,可以直接不寫(xiě)回調(diào)方法,那么實(shí)現(xiàn)的效果是,重試次數(shù)完了后,如果還是沒(méi)成功沒(méi)符合業(yè)務(wù)判斷,就拋出異常。 可以看到傳參里面寫(xiě)的是Exception e,這個(gè)是作為回調(diào)的標(biāo)識(shí)(重試次數(shù)用完了,還是失敗,我們拋出這個(gè)Exception e通知觸發(fā)這個(gè)回調(diào)方法)。
注意事項(xiàng):
1、方法的返回值必須與@Retryable方法一致
2、方法的第一個(gè)參數(shù),必須是Throwable類(lèi)型的,建議是與@Retryable配置的異常一致,其他的參數(shù),需要哪個(gè)參數(shù),寫(xiě)進(jìn)去就可以了(@Recover方法中有的)
3、該回調(diào)方法與重試方法寫(xiě)在同一個(gè)實(shí)現(xiàn)類(lèi)里面
4、由于是基于A(yíng)OP實(shí)現(xiàn),所以不支持類(lèi)里自調(diào)用方法
5、如果重試失敗需要給@Recover注解的方法做后續(xù)處理,那這個(gè)重試的方法不能有返回值,只能是void
6、方法內(nèi)不能使用try catch,只能往外拋異常
7、@Recover注解來(lái)開(kāi)啟重試失敗后調(diào)用的方法(注意,需跟重處理方法在同一個(gè)類(lèi)中),此注解注釋的方法參數(shù)一定要是@Retryable拋出的異常。
該注解的代碼如下:
@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Import(RetryConfiguration.class) @Documented public @interface Recover { }
注解@Recover切面類(lèi)
AnnotationAwareRetryOperationsInterceptor#getRecoverer()
寫(xiě)一個(gè)Demo 創(chuàng)建接口
首先我們創(chuàng)建一個(gè)接口。代碼如下:
public interface RetryService { /** * 重試方法 * @param str * @return * @throws Exception */ String retry(String str) throws Exception; /** * 回調(diào)方法 * @param e * @param str * @return */ String recover(Exception e,String str); }
接口實(shí)現(xiàn)
上述接口的實(shí)現(xiàn)的代碼如下:
@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 請(qǐng)求入?yún)椋簕}",str); log.info("進(jìn)入測(cè)試方法,目前時(shí)間為:{}",new Date()); if ("succ".equals(str)){ return "succ"; }else { throw new Exception("異常了!"); } } /** * 重試次數(shù)完成后,回調(diào)的方法 * @param e * @param str * @return */ @Override @Recover public String recover(Exception e, String str) { log.info("異常出現(xiàn)后的回調(diào)操作,入?yún)椋簕},當(dāng)前時(shí)間為:{}", str,LocalDate.now()); return null; } }
測(cè)試
我們使用postman進(jìn)行測(cè)試
創(chuàng)建測(cè)試類(lèi)
我們創(chuàng)建一個(gè)控制器來(lái)測(cè)試功能。代碼如下:
@RestController @Slf4j public class RetryController { @Resource RetryService retryService; @GetMapping("/re") public String retry(@RequestParam("str") String str) throws Exception{ log.info("Controller 請(qǐng)求入?yún)椋簕}",str); return retryService.retry(str); } }
說(shuō)明:我們的入?yún)⑹且粋€(gè)字符串,若傳入的字符串非succ那么手動(dòng)拋出異常,我們觀(guān)察日志,看是否框架自動(dòng)發(fā)起了重試。
測(cè)試
我們啟動(dòng)Sping Boot項(xiàng)目,并且進(jìn)行測(cè)試,來(lái)看一下效果。
使用postman請(qǐng)求我們的login方法,我們觀(guān)察一下日志。請(qǐng)求地址localhost:8111/re,日志如下:
2023-07-17 21:05:19.488 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.controller.RetryController : Controller 請(qǐng)求入?yún)椋?11
2023-07-17 21:05:19.516 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : Service 請(qǐng)求入?yún)椋?11
2023-07-17 21:05:19.516 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : 進(jìn)入測(cè)試方法,目前時(shí)間為: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 請(qǐng)求入?yún)椋?11
2023-07-17 21:05:21.519 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : 進(jìn)入測(cè)試方法,目前時(shí)間為: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 請(qǐng)求入?yún)椋?11
2023-07-17 21:05:24.523 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : 進(jìn)入測(cè)試方法,目前時(shí)間為: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 請(qǐng)求入?yún)椋?11
2023-07-17 21:05:29.025 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : 進(jìn)入測(cè)試方法,目前時(shí)間為: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 請(qǐng)求入?yún)椋?11
2023-07-17 21:05:35.779 INFO 2007 --- [nio-8111-exec-2] o.t.s.l.service.impl.RetryServiceImpl : 進(jìn)入測(cè)試方法,目前時(shí)間為: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 : 異常出現(xiàn)后的回調(diào)操作,入?yún)椋?11,當(dāng)前時(shí)間為:2023-07-17
可以看到,出現(xiàn)異常后框架自動(dòng)發(fā)起了重試,在重試次數(shù)使用完成后,回調(diào)了異常處理的方法。
到此這篇關(guān)于Spring中的接口重試機(jī)制解析的文章就介紹到這了,更多相關(guān)Spring接口重試內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java通過(guò)MyBatis框架對(duì)MySQL數(shù)據(jù)進(jìn)行增刪查改的基本方法
MyBatis框架由Java的JDBC API進(jìn)一步封裝而來(lái),在操作數(shù)據(jù)庫(kù)方面效果拔群,接下來(lái)我們就一起來(lái)看看Java通過(guò)MyBatis框架對(duì)MySQL數(shù)據(jù)進(jìn)行增刪查改的基本方法:2016-06-06java數(shù)據(jù)結(jié)構(gòu)-堆實(shí)現(xiàn)優(yōu)先隊(duì)列
通常都把隊(duì)列比喻成排隊(duì)買(mǎi)東西,大家都很守秩序,先排隊(duì)的人就先買(mǎi)東西。但是優(yōu)先隊(duì)列有所不同,它不遵循先進(jìn)先出的規(guī)則,而是根據(jù)隊(duì)列中元素的優(yōu)先權(quán),優(yōu)先權(quán)最大的先被取出,這篇文章主要介紹了java數(shù)據(jù)結(jié)構(gòu)-堆實(shí)現(xiàn)優(yōu)先隊(duì)列,感興趣的朋友一起看看吧2021-08-08java Bean與json對(duì)象間的轉(zhuǎn)換實(shí)例講解
在本篇文章里小編給大家整理的是關(guān)于java Bean與json間的轉(zhuǎn)換的實(shí)例內(nèi)容,有需要的朋友們吧可以學(xué)習(xí)參考下。2020-01-01Mybatis Generator逆向工程的使用詳細(xì)教程
這篇文章主要介紹了Mybatis Generator逆向工程的使用詳細(xì)教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06IDEA中make directory as的作用及說(shuō)明
這篇文章主要介紹了IDEA中make directory as的作用及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-09-09Spring?Cloud?Gateway?服務(wù)網(wǎng)關(guān)的部署與使用詳細(xì)講解
這篇文章主要介紹了Spring?Cloud?Gateway?服務(wù)網(wǎng)關(guān)的部署與使用詳細(xì)介紹,本文給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04Spring?createBeanInstance實(shí)例化Bean
這篇文章主要為大家介紹了Spring?createBeanInstance實(shí)例化Bean源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03