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)文章
SpringBoot項(xiàng)目整合Redis教程詳解
這篇文章主要介紹了SpringBoot項(xiàng)目整合Redis教程詳解,Redis?是完全開源的,遵守?BSD?協(xié)議,是一個高性能的?key-value?數(shù)據(jù)庫。感興趣的小伙伴可以參考閱讀本文2023-03-03新的Java訪問mysql數(shù)據(jù)庫工具類的操作代碼
本文通過實(shí)例代碼給大家介紹新的Java訪問mysql數(shù)據(jù)庫工具類的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2021-12-12SpringBoot使用JWT實(shí)現(xiàn)登錄驗(yàn)證的方法示例
這篇文章主要介紹了SpringBoot使用JWT實(shí)現(xiàn)登錄驗(yàn)證的方法示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06Java正則表達(dá)式matcher.group()用法代碼
這篇文章主要給大家介紹了關(guān)于Java正則表達(dá)式matcher.group()用法的相關(guān)資料,最近在做一個項(xiàng)目,需要使用matcher.group()方法匹配出需要的內(nèi)容,文中給出了詳細(xì)的代碼示例,需要的朋友可以參考下2023-08-08OpenJDK源碼解析之System.out.println詳解
這篇文章主要介紹了OpenJDK源碼解析之System.out.println詳解,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04