Retry注解源码解析

486 阅读4分钟

@Retry注解是Spring提供的一个重试注解,使用简单(这里是为了分析其原理,其实个人认为guava的重试好用多了)。

使用例子

引入pom.xml依赖


<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
    <version>1.3.1</version>
</dependency>

代码例子

/**
 * 两个注解:
 * - @Retryable:重试的方法
 * -- 关键参数如下:
 * -- recover:重试多次都失败后调用的方法(可用@Recover注解标记替代)
 * -- maxAttempts:重试次数,默认=3
 * -- value:什么异常下重试
 * - @Recover:执行重试后的兜底操作
 */
@Service
public class RetryService {
    @Retryable(value = Exception.class, maxAttempts = 3/* , recover = "recover"*/)
    public void service() {// 业务操作
        System.out.println("执行了一次===================");
        throw new RuntimeException("抛异常");
    }

    @Recover
    public void recover(Exception e) {// 这里执行到兜底操作
        System.out.println("这里执行失败后处理");
        System.out.println(e.getMessage());
    }
}

执行时序图

Retry注解执行时区图.png

执行原理流程(举例上面例子)

  1. 这里没实现类,所以走CgLib动态代理
  2. 执行到AnnotationAwareRetryOperationsInterceptor, 根据Retryable注解去方法、目标类找,并找到存放在delegates中
  3. 执行到RetryOperationsInterceptor中invoke方法,最终调用到RetryTemplate#doExecute()方法
    3.1 canRetry(retryPolicy, context)判断是否可以重试(context存储了重试过程中上下文,如重试次数、是否有抛出异常)
    3.2 执行调用抛异常了,会registerThrowable()记录重试次数、上次异常到context中。然后是否需要执行退避策略backOffPolicy,默认休眠一秒
  4. 3中多次重试依旧失败,执行handleRetryExhausted(),如果recoveryCallback不为空就执行对应的失败后置处理方法
public class RetryTemplate implements RetryOperations {
    // 省略其他

    /** 核心方法,具体执行重试的方法 **/
    protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
                                                   RecoveryCallback<T> recoveryCallback, RetryState state) throws E, ExhaustedRetryException {
        // 声明两个策略:重试策略(比如重试三次)、退避策略(比如默认失败了休眠一秒)
        RetryPolicy retryPolicy = this.retryPolicy;
        BackOffPolicy backOffPolicy = this.backOffPolicy;
        // 省略中间代码。。。。。。。
        try {
            // 省略其他代码。。。。
            // !!!3. 重点:判断是否可以重试  canRetry(retryPolicy, context)判断是否可以重试(context存储了重试过程中上下文,如重试次数、是否有抛出异常)  
            // !!!3. 重点:判断是否可以重试  canRetry(retryPolicy, context)判断是否可以重试(context存储了重试过程中上下文,如重试次数、是否有抛出异常)  
            while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
                try {
                    lastException = null;
                    return retryCallback.doWithRetry(context);
                } catch (Throwable e) {
                    // 
                    lastException = e;
                    // 3.1 将异常和重试次数记录起来
                    try {
                        registerThrowable(retryPolicy, state, context, e);
                    } catch (Exception ex) {
                        throw new TerminatedRetryException("Could not register throwable", ex);
                    } finally {
                        doOnErrorInterceptors(retryCallback, context, e);
                    }
                    // 3.2 判断还可以不可以重试,执行退避策略,默认休眠一秒
                    if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
                        try {
                            backOffPolicy.backOff(backOffContext);
                        } catch (BackOffInterruptedException ex) {
                            lastException = e;
                            // back off was prevented by another thread - fail the retry
                            if (this.logger.isDebugEnabled()) {
                                this.logger.debug("Abort retry because interrupted: count=" + context.getRetryCount());
                            }
                            throw ex;
                        }
                    }
                }
                // 省略其他代码
            }
            // 省略其他代码
            exhausted = true;
            // 4. 多次重试失败后就会执行到这个方法,做一些后置处理(就是那些标记了@Recover注解的方法)
            // 4. 多次重试失败后就会执行到这个方法,做一些后置处理(就是那些标记了@Recover注解的方法)
            // -- todo: 但是怎么找到这些方法?多个标记了@Recover注解方法,那优先用哪个??下面会解答这个问题
            // -- todo: 但是怎么找到这些方法?多个标记了@Recover注解方法,那优先用哪个??下面会解答这个问题
            return handleRetryExhausted(recoveryCallback, context, state);
        } catch (Exception e) {
            // 省略其他代码
        }
    }
}

Recover

上面还有一个疑惑,就是recover的方法是怎么确认的,其选择recover方法的点如下

  1. 通过findClosestMatch()找到最相近的方法
    1.1 先判断当前定义的这个recover方法是否抛出异常的父类type.isAssignableFrom(cause)
    1.2 1成立下,判断当前方法抛出异常离这个定义了@Recover注解父类方法有多远(取异常最相似那个,也就是距离最小那个)
    1.2.1 举个例子。抛出RuntimeException,@Recover方法A继承了RuntimeException为AException,@Recover方法B继承了BException
    1.2.2 上面例子中,方法A距离=1,方法B距离=2。1 < 2,所以优先采用方法A作为兜底后置方法
    备注:这里会存在一个泛型问题,比如两个标记了@Recover的方法返回值是List、List,那就是有泛型区分不出来了(从 1.3.2 版本之后会支持泛型的,目前最高版本是1.3.1)
public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationRecoverer<T> {
   /** 找到定义最相近的方法 ***/
   private Method findClosestMatch(Object[] args, Class<? extends Throwable> cause) {
      Method result = null;
      if (StringUtils.isEmpty(this.recoverMethodName)) {
         int min = Integer.MAX_VALUE;  // 定义权重缺乏呢
         for (Map.Entry<Method, SimpleMetadata> entry : this.methods.entrySet()) {
            Method method = entry.getKey();
            SimpleMetadata meta = entry.getValue();
            Class<? extends Throwable> type = meta.getType();
            if (type == null) {
               type = Throwable.class;
            }
            // 核心在于判断当前定义的这个recover方法是否抛出异常的父类
            // 核心在于判断当前定义的这个recover方法是否抛出异常的父类
            if (type.isAssignableFrom(cause)) {
                // 取距离最短那个方法
               int distance = calculateDistance(cause, type);
               if (distance < min) {
                  min = distance;
                  result = method;
               } else if (distance == min) {
                  boolean parametersMatch = compareParameters(args, meta.getArgCount(),
                          method.getParameterTypes());
                  if (parametersMatch) {
                     result = method;
                  }
               }
            }
         }
      } else {
         for (Map.Entry<Method, SimpleMetadata> entry : this.methods.entrySet()) {
            Method method = entry.getKey();
            if (method.getName().equals(this.recoverMethodName)) {
               SimpleMetadata meta = entry.getValue();
               if (meta.type.isAssignableFrom(cause)
                       && compareParameters(args, meta.getArgCount(), method.getParameterTypes())) {
                  result = method;
                  break;
               }
            }
         }
      }
      return result;
   }

   /**
    * 计算距离
    */
   private int calculateDistance(Class<? extends Throwable> cause, Class<? extends Throwable> type) {
      int result = 0;
      Class<?> current = cause;
      while (current != type && current != Throwable.class) {
         result++;
         current = current.getSuperclass();
      }
      return result;
   }
}

参考链接:www.toutiao.com/i7054024092…