Spring?Retry實(shí)現(xiàn)重試機(jī)制的示例詳解
大家好,我是小趴菜,在工作中,我們經(jīng)常會碰到需要調(diào)用遠(yuǎn)程方法的業(yè)務(wù),這時候,如果超時了,或者異常了,我們都會讓其重試幾次,達(dá)到一定的重試次數(shù)以后,就返回異常信息,今天我們就來了解下Spring-Retry的用法以及實(shí)現(xiàn)原理是怎么樣的
Spring-Retry用法
因?yàn)镾pring-Retry是基于Spring AOP機(jī)制實(shí)現(xiàn)的,所以需要引入AOP依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-retry</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
</project>啟動類
@RestController
//開啟Spring-Retry重試機(jī)制
@EnableRetry
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
@Resource
private RetryService retryService;
@GetMapping("/test")
public String test(@RequestParam("code") Integer code) throws Exception{
retryService.retry(code);
return "ok";
}
}package com.coco.service.impl;
import com.coco.service.RetryService;
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.io.IOException;
@Service
public class RetryServiceImpl implements RetryService {
/**
* value:拋出指定異常才會重試
* include:和value一樣,默認(rèn)為空,當(dāng)exclude也為空時,默認(rèn)所有異常
* exclude:指定不處理的異常
* maxAttempts:最大重試次數(shù),默認(rèn)3次
* backoff:重試等待策略,
* 默認(rèn)使用@Backoff,@Backoff的value默認(rèn)為1000L,我們設(shè)置為2000; 以毫秒為單位的延遲(默認(rèn) 1000)
* multiplier(指定延遲倍數(shù))默認(rèn)為0,表示固定暫停1秒后進(jìn)行重試,如果把multiplier設(shè)置為1.5,則第一次重試為2秒,第二次為3秒,第三次為4.5秒。
*/
@Retryable(value = RuntimeException.class,maxAttempts = 3,backoff = @Backoff(delay = 2000,multiplier = 1.5))
@Override
public void retry(int code) throws Exception {
System.out.println("retry被調(diào)用了");
if (code==0){
throw new IOException("調(diào)用失敗,重試");
}
System.out.println("調(diào)用成功");
}
/**
* Spring-Retry還提供了@Recover注解,用于@Retryable重試失敗后處理方法。
* 如果不需要回調(diào)方法,可以直接不寫回調(diào)方法,那么實(shí)現(xiàn)的效果是,重試次數(shù)完了后,如果還是沒成功沒符合業(yè)務(wù)判斷,就拋出異常。
* 可以看到傳參里面寫的是 Exception e,這個是作為回調(diào)的接頭暗號(重試次數(shù)用完了,還是失敗,我們拋出這個Exception e通知觸發(fā)這個回調(diào)方法)。
* 注意事項(xiàng):
* 方法的返回值必須與@Retryable方法一致
* 方法的第一個參數(shù),必須是Throwable類型的,建議是與@Retryable配置的異常一致,其他的參數(shù),需要哪個參數(shù),寫進(jìn)去就可以了(@Recover方法中有的)
* 該回調(diào)方法與重試方法寫在同一個實(shí)現(xiàn)類里面
*
* 由于是基于AOP實(shí)現(xiàn),所以不支持類里自調(diào)用方法
* 如果重試失敗需要給@Recover注解的方法做后續(xù)處理,那這個重試的方法不能有返回值,只能是void
* 方法內(nèi)不能使用try catch,只能往外拋異常
* @Recover注解來開啟重試失敗后調(diào)用的方法(注意,需跟重處理方法在同一個類中),此注解注釋的方法參數(shù)一定要是@Retryable拋出的異常,否則無法識別,可以在該方法中進(jìn)行日志處理。
*/
@Recover
public void recover(Exception e, int code){
System.out.println("回調(diào)方法執(zhí)行?。。?!");
//記日志到數(shù)據(jù)庫 或者調(diào)用其余的方法
System.out.println("異常信息:"+e.getMessage());
}
}啟動項(xiàng)目,瀏覽器訪問 http://localhost:8080/test?code=0 即可看到效果了
其實(shí)Spring-Retry的用法還是很簡單的,接下來我們來分析下它的底層是如何實(shí)現(xiàn)的
Spring-Retry底層實(shí)現(xiàn)原理
其實(shí)當(dāng)你要去查看一個框架的底層實(shí)現(xiàn)原理的時候,最難的就是找入口,你首先要找到該從哪里開始分析,這是最難。在這里我分享二個我看源碼的小技巧
首先看注解,比如我們這里的啟動類上的@EnableRetry
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@EnableAspectJAutoProxy(proxyTargetClass = false)
//注解里我們尤其要關(guān)注@Import注解,因?yàn)檫@是Spring將一個Bean注入到容器中的
@Import(RetryConfiguration.class)
@Documented
public @interface EnableRetry {
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed to
* standard Java interface-based proxies. The default is {@code false}.
* @return whether to proxy or not to proxy the class
*/
boolean proxyTargetClass() default false;
}RetryConfiguration.class實(shí)現(xiàn)了InitializingBean接口,那么在這個類初始化之后就會調(diào)用afterPropertiesSet()方法

但是看了這個方法之后,我們也很難找到入口的地方,唯一能看到的就是構(gòu)建AOP的切面和通知
@Override
public void afterPropertiesSet() throws Exception {
this.retryContextCache = findBean(RetryContextCache.class);
this.methodArgumentsKeyGenerator = findBean(MethodArgumentsKeyGenerator.class);
this.newMethodArgumentsIdentifier = findBean(NewMethodArgumentsIdentifier.class);
this.retryListeners = findBeans(RetryListener.class);
this.sleeper = findBean(Sleeper.class);
Set<Class<? extends Annotation>> retryableAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>(1);
retryableAnnotationTypes.add(Retryable.class);
//構(gòu)建AOP切面和通知
this.pointcut = buildPointcut(retryableAnnotationTypes);
this.advice = buildAdvice();
if (this.advice instanceof BeanFactoryAware) {
((BeanFactoryAware) this.advice).setBeanFactory(this.beanFactory);
}
}既然我們從注解不能找到入口,那么就從日志入手
看日志

通過日志我們可以看到 RetryOperationsInterceptor.invoke()這段方法,那么在執(zhí)行重試的時候,肯定也調(diào)用這個方法,所以我們直接進(jìn)入到這個類中,RetryOperationsInterceptor本質(zhì)是一個攔截器,從類名我們可以推斷出,這個攔截器就是攔截有@Retryable注解的方法

所以我們可以直接關(guān)注攔截器的核心方法invoke()
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
String name;
if (StringUtils.hasText(this.label)) {
name = this.label;
}
else {
name = invocation.getMethod().toGenericString();
}
final String label = name;
//初始化重試機(jī)制的回調(diào)函數(shù),這里是重點(diǎn),在重試執(zhí)行我們的業(yè)務(wù)邏輯的時候,就會進(jìn)入到
//這里回調(diào)函數(shù)中,然后執(zhí)行doWithRetry()方法,但是第一次只是初始化,并不會進(jìn)入到這里面
RetryCallback<Object, Throwable> retryCallback = new MethodInvocationRetryCallback<Object, Throwable>(
invocation, label) {
@Override
public Object doWithRetry(RetryContext context) throws Exception {
context.setAttribute(RetryContext.NAME, this.label);
if (this.invocation instanceof ProxyMethodInvocation) {
context.setAttribute("___proxy___", ((ProxyMethodInvocation) this.invocation).getProxy());
try {
return ((ProxyMethodInvocation) this.invocation).invocableClone().proceed();
}
catch (Exception e) {
throw e;
}
catch (Error e) {
throw e;
}
catch (Throwable e) {
throw new IllegalStateException(e);
}
}
else {
throw new IllegalStateException(
"MethodInvocation of the wrong type detected - this should not happen with Spring AOP, "
+ "so please raise an issue if you see this exception");
}
}
};
//還記得我們在自己RetryServiceImpl中實(shí)現(xiàn)了一個方法recover(),并且用@Recover標(biāo)記
//如果我們實(shí)現(xiàn)了這個方法,那么this.recoverer就不為空,就會進(jìn)入到if分支里面去
//最后調(diào)用this.retryOperations.execute()方法
if (this.recoverer != null) {
ItemRecovererCallback recoveryCallback = new ItemRecovererCallback(invocation.getArguments(),
this.recoverer);
try {
Object recovered = this.retryOperations.execute(retryCallback, recoveryCallback);
return recovered;
}
finally {
RetryContext context = RetrySynchronizationManager.getContext();
if (context != null) {
context.removeAttribute("__proxy__");
}
}
}
//如果我們自己沒有實(shí)現(xiàn)recover()方法,那么this.recoverer就等于null,就會直接進(jìn)入到這里面來了
return this.retryOperations.execute(retryCallback);
}public final <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback,
RecoveryCallback<T> recoveryCallback) throws E {
//繼續(xù)進(jìn)入doExecute方法
return doExecute(retryCallback, recoveryCallback, null);
}protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
RecoveryCallback<T> recoveryCallback, RetryState state) throws E, ExhaustedRetryException {
//RetryPolicy這個對象包含二個屬性
//maxAttempts:也就是重試的最大次數(shù),當(dāng)達(dá)到這個次數(shù)之后就不會再次重試了
//retryableClassifier:還記得我們加在方法上的@Retryable(value = RuntimeException.class,maxAttempts = 3,backoff = @Backoff(delay = 2000,multiplier = 1.5))
//這里設(shè)置了一個異常類型,表示的是只有返回的是這個類型的異常才會進(jìn)行重試
//如果返回的是其它類型的異常就不會進(jìn)行重試,所以retryableClassifier這個值就是保存注解
//里面value設(shè)置的異常類型
RetryPolicy retryPolicy = this.retryPolicy;
BackOffPolicy backOffPolicy = this.backOffPolicy;
//初始化我們當(dāng)前線程重試的上下文
//在上下文中有一個很重的屬性count,初始化的時候這個值為0,后續(xù)重試一次,這個值就會加1
RetryContext context = open(retryPolicy, state);
//將上下文保存到ThreadLocal中,也是防止并發(fā)安全
RetrySynchronizationManager.register(context);
Throwable lastException = null;
boolean exhausted = false;
try {
// 給客戶一個機(jī)會來增強(qiáng)上下文。。。,這里不是重點(diǎn)
boolean running = doOpenInterceptors(retryCallback, context);
if (!running) {
throw new TerminatedRetryException("Retry terminated abnormally by interceptor before first attempt");
}
BackOffContext backOffContext = null;
Object resource = context.getAttribute("backOffContext");
if (resource instanceof BackOffContext) {
backOffContext = (BackOffContext) resource;
}
if (backOffContext == null) {
backOffContext = backOffPolicy.start(context);
if (backOffContext != null) {
context.setAttribute("backOffContext", backOffContext);
}
}
//核心方法
//這里就是重試機(jī)制實(shí)現(xiàn)的核心實(shí)現(xiàn),首先這里是一個while循環(huán)
//我們看第一個方法canRetry(retryPolicy, context),意思就是是否可以重試
while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
try {
lastException = null;
//如果可以重試,就會執(zhí)行doWithRetry()方法
//在之前我們分析RetryOperationsInterceptor類中的invoke()方法的時候,在那里
//已經(jīng)實(shí)現(xiàn)了回調(diào)方法,所以此時就會進(jìn)入到那個回調(diào)方法中
return retryCallback.doWithRetry(context);
}
catch (Throwable e) {
lastException = e;
try {
registerThrowable(retryPolicy, state, context, e);
}
catch (Exception ex) {
throw new TerminatedRetryException("Could not register throwable", ex);
}
finally {
doOnErrorInterceptors(retryCallback, context, e);
}
if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
try {
backOffPolicy.backOff(backOffContext);
}
catch (BackOffInterruptedException ex) {
lastException = e;
throw ex;
}
}
if (shouldRethrow(retryPolicy, context, state)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Rethrow in retry for policy: count=" + context.getRetryCount());
}
throw RetryTemplate.<E>wrapIfNecessary(e);
}
}
if (state != null && context.hasAttribute(GLOBAL_STATE)) {
break;
}
}
exhausted = true;
return handleRetryExhausted(recoveryCallback, context, state);
}
catch (Throwable e) {
throw RetryTemplate.<E>wrapIfNecessary(e);
}
finally {
//清除上下文信息
close(retryPolicy, context, state, lastException == null || exhausted);
doCloseInterceptors(retryCallback, context, lastException);
//將ThreadLocal中的上下文信息清除1掉
RetrySynchronizationManager.clear();
}
}在上述中我們發(fā)現(xiàn)有兩個核心的方法,一個就是 canRetry(retryPolicy, context),還有一個就是retryCallback.doWithRetry(context);
protected boolean canRetry(RetryPolicy retryPolicy, RetryContext context) {
//進(jìn)入這個方法
return retryPolicy.canRetry(context);
}具體的實(shí)現(xiàn)類是SimpleRetryPolicy
public boolean canRetry(RetryContext context) {
Throwable t = context.getLastThrowable();
//retryForException(t):判斷返回的異常是否跟我們注解設(shè)置的異常類型一致,
// 在分析RetryPolicy對象中有個屬性就保存了我們注解設(shè)置的異常類型
//context.getRetryCount() < getMaxAttempts():重試次數(shù)是否已經(jīng)達(dá)到了我們設(shè)置的最大次數(shù)
return (t == null || retryForException(t)) && context.getRetryCount() < getMaxAttempts();
}如果返回的異常類型與我們設(shè)置的一樣,并且重試次數(shù)還沒有達(dá)到,那么就會進(jìn)入到while循環(huán)中執(zhí)行retryCallback.doWithRetry(context);方法
//這段代碼就是RetryOperationsInterceptor攔截器中的invoke()方法,我把這段代碼截取出來了
RetryCallback<Object, Throwable> retryCallback = new MethodInvocationRetryCallback<Object, Throwable>(invocation, name) {
//執(zhí)行這段方法
public Object doWithRetry(RetryContext context) throws Exception {
context.setAttribute("context.name", this.label);
if (this.invocation instanceof ProxyMethodInvocation) {
context.setAttribute("___proxy___", ((ProxyMethodInvocation)this.invocation).getProxy());
try {
// 這里就是執(zhí)行我們自己的業(yè)務(wù)邏輯了,如果有異常就拋出,然后在重試機(jī)制的
// while循環(huán)中捕獲,繼而判斷異常是否符合并且重試次數(shù)是否達(dá)到,如果條件符合
//就繼續(xù)重試執(zhí)行,如果不符合,就不會再重試了
return ((ProxyMethodInvocation)this.invocation).invocableClone().proceed();
} catch (Exception var3) {
throw var3;
} catch (Error var4) {
throw var4;
} catch (Throwable var5) {
throw new IllegalStateException(var5);
}
} else {
throw new IllegalStateException("MethodInvocation of the wrong type detected - this should not happen with Spring AOP, so please raise an issue if you see this exception");
}
}
};所以在我們使用Spring-Retry的時候,設(shè)置的異常類型一定要一致,否則這個重試機(jī)制就不會生效了
以上就是Spring Retry實(shí)現(xiàn)重試機(jī)制的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Spring Retry重試的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
解讀java.lang.Character.isLetterOrDigit()的使用方式
這篇文章主要介紹了解讀java.lang.Character.isLetterOrDigit()的使用方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06
SpringBoot整合Web之CORS支持與配置類和 XML配置及注冊攔截器
這篇文章主要介紹了SpringBoot整合Web開發(fā)中CORS支持與配置類和 XML配置及注冊攔截器的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
最新log4j2遠(yuǎn)程代碼執(zhí)行漏洞(附解決方法)
Apache?Log4j2?遠(yuǎn)程代碼執(zhí)行漏洞攻擊代碼,該漏洞利用無需特殊配置,經(jīng)多方驗(yàn)證,Apache?Struts2、Apache?Solr、Apache?Druid、Apache?Flink等均受影響,本文就介紹一下解決方法2021-12-12
SpringBoot使用@ResponseBody返回圖片的實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot使用@ResponseBody返回圖片的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Spring中配置和讀取多個Properties文件的方式方法
本篇文章主要介紹了Spring中配置和讀取多個Properties文件的方式方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-04-04
java.lang.NumberFormatException異常解決方案詳解
這篇文章主要介紹了java.lang.NumberFormatException異常解決方案詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08

