@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());
}
}
执行时序图
执行原理流程(举例上面例子)
- 这里没实现类,所以走CgLib动态代理
- 执行到AnnotationAwareRetryOperationsInterceptor, 根据Retryable注解去方法、目标类找,并找到存放在delegates中
- 执行到RetryOperationsInterceptor中invoke方法,最终调用到RetryTemplate#doExecute()方法
3.1 canRetry(retryPolicy, context)判断是否可以重试(context存储了重试过程中上下文,如重试次数、是否有抛出异常)
3.2 执行调用抛异常了,会registerThrowable()记录重试次数、上次异常到context中。然后是否需要执行退避策略backOffPolicy,默认休眠一秒 - 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方法的点如下
- 通过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;
}
}