今天在某个公众号收到一篇关于Spring重试功能(@Retryable注解)的推文,内容大概是“不要重复开发重试功能啦,靠这个注解一键搞定!”,咦,还有这么个注解?功能这么强?让我来看看它是不是能满足我这多变的需求!源码开扒......
要使用@Retryable注解,必须先开启重试功能(即在启动类上添加@EnableRetry注解)。
1、@EnableRetry
那先看看@EnableRetry注解的内容,除了声明作用域、生命周期等外,多了下面两个注解:
@EnableAspectJAutoProxy(proxyTargetClass = false):哦?!果然用的是切面编程。
@Import(RetryConfiguration.class):需要先引入此配置类,那盲猜一下这个配置类里面应该会包含一些AOP相关的配置。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@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;
}
2、RetryConfiguration配置类
果然如此,继承了AbstractPointcutAdvisor,实现IntroductionAdvisor接口。
AbstractPointcutAdvisor:用来自定义切面通知(Advice)和切入点(Pointcut);
IntroductionAdvisor:用来自定义哪些类作为切面。
2.1 buildAdvice
此方法用来定义切面通知的实现方法,比如设置重试上下文缓存、监听器、参数唯一标识、参数识别策略、重试等待策略等等。
protected Advice buildAdvice() {
AnnotationAwareRetryOperationsInterceptor interceptor = new AnnotationAwareRetryOperationsInterceptor();
if (retryContextCache != null) {
interceptor.setRetryContextCache(retryContextCache);
}
if (retryListeners != null) {
interceptor.setListeners(retryListeners);
}
if (methodArgumentsKeyGenerator != null) {
interceptor.setKeyGenerator(methodArgumentsKeyGenerator);
}
if (newMethodArgumentsIdentifier != null) {
interceptor.setNewItemIdentifier(newMethodArgumentsIdentifier);
}
if (sleeper != null) {
interceptor.setSleeper(sleeper);
}
return interceptor;
}
2.2 buildPointcut
此方法用来获取切点。
protected Pointcut buildPointcut(Set<Class<? extends Annotation>> retryAnnotationTypes) {
ComposablePointcut result = null;
for (Class<? extends Annotation> retryAnnotationType : retryAnnotationTypes) {
Pointcut filter = new AnnotationClassOrMethodPointcut(retryAnnotationType);
if (result == null) {
result = new ComposablePointcut(filter);
}
else {
result.union(filter);
}
}
return result;
}
3、AnnotationAwareRetryOperationsInterceptor
细心的同学可能在上面的buildAdvice方法中,已经发现了“AnnotationAwareRetryOperationsInterceptor”这个类,那这个类是用来干嘛的呢?顾名思义,它是一个拦截器。
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
MethodInterceptor delegate = getDelegate(invocation.getThis(), invocation.getMethod());
if (delegate != null) {
return delegate.invoke(invocation);
}
else {
return invocation.proceed();
}
}
private MethodInterceptor getDelegate(Object target, Method method) {
if (!this.delegates.containsKey(target) || !this.delegates.get(target).containsKey(method)) {
synchronized (this.delegates) {
if (!this.delegates.containsKey(target)) {
this.delegates.put(target, new HashMap<Method, MethodInterceptor>());
}
Map<Method, MethodInterceptor> delegatesForTarget = this.delegates.get(target);
if (!delegatesForTarget.containsKey(method)) {
Retryable retryable = AnnotationUtils.findAnnotation(method, Retryable.class);
if (retryable == null) {
retryable = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Retryable.class);
}
if (retryable == null) {
retryable = findAnnotationOnTarget(target, method);
}
if (retryable == null) {
return delegatesForTarget.put(method, null);
}
MethodInterceptor delegate;
if (StringUtils.hasText(retryable.interceptor())) {
delegate = this.beanFactory.getBean(retryable.interceptor(), MethodInterceptor.class);
}
else if (retryable.stateful()) {
delegate = getStatefulInterceptor(target, method, retryable);
}
else {
delegate = getStatelessInterceptor(target, method, retryable);
}
delegatesForTarget.put(method, delegate);
}
}
}
return this.delegates.get(target).get(method);
}
private MethodInterceptor getStatelessInterceptor(Object target, Method method, Retryable retryable) {
RetryTemplate template = createTemplate(retryable.listeners());
template.setRetryPolicy(getRetryPolicy(retryable));
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
return RetryInterceptorBuilder.stateless()
.retryOperations(template)
.label(retryable.label())
.recoverer(getRecoverer(target, method))
.build();
}
private MethodInterceptor getStatefulInterceptor(Object target, Method method, Retryable retryable) {
RetryTemplate template = createTemplate(retryable.listeners());
template.setRetryContextCache(this.retryContextCache);
CircuitBreaker circuit = AnnotationUtils.findAnnotation(method, CircuitBreaker.class);
if (circuit!=null) {
RetryPolicy policy = getRetryPolicy(circuit);
CircuitBreakerRetryPolicy breaker = new CircuitBreakerRetryPolicy(policy);
breaker.setOpenTimeout(getOpenTimeout(circuit));
breaker.setResetTimeout(getResetTimeout(circuit));
template.setRetryPolicy(breaker);
template.setBackOffPolicy(new NoBackOffPolicy());
String label = circuit.label();
if (!StringUtils.hasText(label)) {
label = method.toGenericString();
}
return RetryInterceptorBuilder.circuitBreaker()
.keyGenerator(new FixedKeyGenerator("circuit"))
.retryOperations(template)
.recoverer(getRecoverer(target, method))
.label(label)
.build();
}
RetryPolicy policy = getRetryPolicy(retryable);
template.setRetryPolicy(policy);
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
String label = retryable.label();
return RetryInterceptorBuilder.stateful()
.keyGenerator(this.methodArgumentsKeyGenerator)
.newMethodArgumentsIdentifier(this.newMethodArgumentsIdentifier)
.retryOperations(template)
.label(label)
.recoverer(getRecoverer(target, method))
.build();
}
看源码,此拦截器实现了IntroductionInterceptor接口中的invoke方法,在getDelegate中通过获取方法或其所在类的@Retryable注解配置,来确定重试机制和策略,同时设置重试失败后操作。最后通过build方法构建拦截器后执行拦截器。
3.1 StatelessRetryInterceptorBuilder和StatefulRetryInterceptorBuilder
在上述构建拦截器过程中出现了两种不同的构建方法:StatelessRetryInterceptorBuilder、StatefulRetryInterceptorBuilder,这两者的区别在于是否有状态。
StatelessRetryInterceptorBuilder适用于无状态方法的拦截器。可以使用StatelessRetryOperationsInterceptor来处理与外部系统的通信,因为它们通常是无状态的操作。
StatefulRetryInterceptorBuilder用于创建StatefulRetryOperationsInterceptor拦截器,这是一种适用于有状态方法的拦截器。例如,可以使用StatefulRetryOperationsInterceptor来处理需要在重试之间保留状态的方法,例如事务性方法。
3.2 StatefulRetryOperationsInterceptor和RetryOperationsInterceptor
上述两个构建方法就是基于此两种拦截器来构建,具体区别可以看两者的源码,此处就不贴源码了。总的来说就是,StatefulRetryOperationsInterceptor此拦截器会在执行的过程中设置一个RetryState状态,可以用来获取当前重试状态;而RetryOperationsInterceptor没有此状态。
3.3 RetryTemplate
在以上所有的配置设置完成后,RetryTemplate就上线了,这是真正执行重试操作的地方。一下是类中的执行方法doExecute:
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;
// Allow the retry policy to initialise itself...
RetryContext context = open(retryPolicy, state);
if (this.logger.isTraceEnabled()) {
this.logger.trace("RetryContext retrieved: " + context);
}
// Make sure the context is available globally for clients who need
// it...
RetrySynchronizationManager.register(context);
Throwable lastException = null;
boolean exhausted = false;
try {
// Give clients a chance to enhance the context...
boolean running = doOpenInterceptors(retryCallback, context);
if (!running) {
throw new TerminatedRetryException(
"Retry terminated abnormally by interceptor before first attempt");
}
// Get or Start the backoff context...
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);
}
}
/*
* We allow the whole loop to be skipped if the policy or context already
* forbid the first try. This is used in the case of external retry to allow a
* recovery in handleRetryExhausted without the callback processing (which
* would throw an exception).
*/
while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
try {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Retry: count=" + context.getRetryCount());
}
// Reset the last exception, so if we are successful
// the close interceptors will not think we failed...
lastException = null;
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;
// 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;
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug(
"Checking for rethrow: count=" + context.getRetryCount());
}
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);
}
}
/*
* A stateful attempt that can retry may rethrow the exception before now,
* but if we get this far in a stateful retry there's a reason for it,
* like a circuit breaker or a rollback classifier.
*/
if (state != null && context.hasAttribute(GLOBAL_STATE)) {
break;
}
}
if (state == null && this.logger.isDebugEnabled()) {
this.logger.debug(
"Retry failed last attempt: count=" + context.getRetryCount());
}
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);
RetrySynchronizationManager.clear();
}
}
可以看出,其中也是通过while循环来进行重试操作。每次循环之前都会通过canRetry和isExhaustedOnly来判断是否可以重试或者重试次数是否已耗尽。
以上就是@Retryable注解涉及到的源码解析。当然除此之外还有一些重试延迟策略(固定延迟重试、随机延迟重试、倍数延迟重试等等)和重试触发策略(异常重试策略、断路器重试策略等等),这些都是根据注解的配置选择的策略,有兴趣的同学可以继续深入研究,在此我就不过多介绍了。
总体来看,重试机制也是AOP的一种形式,将@Retryable注解所在的方法当作切点,所在的类当作切面,以拦截器的形式对方法进行重试。那么使用过程中就得注意以下事项:
1、类内部调用不会触发,因为AOP是基于代理类调用的,内部方法的调用不会触发机制;
2、方法内使用try catch后不抛出异常,则不会触发重试;
3、@Recover注解所在的方法参数一定要是@Retryable抛出的异常,否则无法识别。