欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring?Retry實(shí)現(xiàn)重試機(jī)制的示例詳解

 更新時間:2023年07月24日 10:57:39   作者:我是小趴菜  
這篇文章主要為大家詳細(xì)介紹了Spring-Retry的用法以及實(shí)現(xiàn)原理是怎么樣的,文中的示例代碼講解詳細(xì),具有一定的參考價值,需要的可以了解一下

大家好,我是小趴菜,在工作中,我們經(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教程詳解

    這篇文章主要介紹了SpringBoot項(xiàng)目整合Redis教程詳解,Redis?是完全開源的,遵守?BSD?協(xié)議,是一個高性能的?key-value?數(shù)據(jù)庫。感興趣的小伙伴可以參考閱讀本文
    2023-03-03
  • SpringBoot如何集成i18n(多語言)

    SpringBoot如何集成i18n(多語言)

    這篇文章主要介紹了SpringBoot如何集成i18n(多語言)問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • 新的Java訪問mysql數(shù)據(jù)庫工具類的操作代碼

    新的Java訪問mysql數(shù)據(jù)庫工具類的操作代碼

    本文通過實(shí)例代碼給大家介紹新的Java訪問mysql數(shù)據(jù)庫工具類的方法,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2021-12-12
  • 簡述Java異步上傳文件的三種方式

    簡述Java異步上傳文件的三種方式

    這篇文章主要為大家詳細(xì)介紹了Java異步上傳文件的三種方式,感興趣的小伙伴們可以參考一下
    2016-03-03
  • SpringBoot使用JWT實(shí)現(xiàn)登錄驗(yàn)證的方法示例

    SpringBoot使用JWT實(shí)現(xiàn)登錄驗(yàn)證的方法示例

    這篇文章主要介紹了SpringBoot使用JWT實(shí)現(xiàn)登錄驗(yàn)證的方法示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • Java正則表達(dá)式matcher.group()用法代碼

    Java正則表達(dá)式matcher.group()用法代碼

    這篇文章主要給大家介紹了關(guān)于Java正則表達(dá)式matcher.group()用法的相關(guān)資料,最近在做一個項(xiàng)目,需要使用matcher.group()方法匹配出需要的內(nèi)容,文中給出了詳細(xì)的代碼示例,需要的朋友可以參考下
    2023-08-08
  • OpenJDK源碼解析之System.out.println詳解

    OpenJDK源碼解析之System.out.println詳解

    這篇文章主要介紹了OpenJDK源碼解析之System.out.println詳解,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java的小伙伴們有非常好的幫助,需要的朋友可以參考下
    2021-04-04
  • Spring中如何操作JDBC的實(shí)現(xiàn)

    Spring中如何操作JDBC的實(shí)現(xiàn)

    這篇文章主要介紹了Spring中如何操作JDBC的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Mybatis-Plus邏輯刪除的用法詳解

    Mybatis-Plus邏輯刪除的用法詳解

    這篇文章主要為大家詳細(xì)介紹了Mybatis-Plus 邏輯刪除的用法,文中有詳細(xì)的代碼示例,對我們的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2023-07-07
  • Spring Boot快速搭建Spring框架教程

    Spring Boot快速搭建Spring框架教程

    這篇文章主要為大家詳細(xì)介紹了Spring Boot快速搭建Spring框架教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-10-10

最新評論