Spring retry(二)- 源码解析-启动装配篇 @EnableRetry
上一篇文章,我们快速介绍了下spring-retry的使用技巧,本篇我们将会剖析源码去学习
一、 EnableRetry注解
@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
*/
@AliasFor(annotation = EnableAspectJAutoProxy.class)
boolean proxyTargetClass() default false;
/**
* Indicate the order in which the {@link RetryConfiguration} AOP <b>advice</b> should
* be applied.
* <p>
* The default is {@code Ordered.LOWEST_PRECEDENCE - 1} in order to make sure the
* advice is applied before other advices with {@link Ordered#LOWEST_PRECEDENCE} order
* (e.g. an advice responsible for {@code @Transactional} behavior).
*/
int order() default Ordered.LOWEST_PRECEDENCE - 1;
}
英文翻译我就不再解释了,上面说的很清楚;这里重点提一下@Import(RetryConfiguration.class)这个注解,表明了@EnableRetry注解的启动配置类是RetryConfiguration, 通过@Import注解来注入对应的配置类,这样的做法同样可见于@EnableAsync/@EnableScheduling等注解上; 看到这里,如何定义一个Enable配置注解是不是会了呢~
二、 RetryConfiguration如何构建 ponintCut和advisor
@SuppressWarnings("serial")
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Component
public class RetryConfiguration extends AbstractPointcutAdvisor
implements IntroductionAdvisor, BeanFactoryAware, InitializingBean, SmartInitializingSingleton, ImportAware {
//在这里构建了ponintCut和advice
@Override
public void afterPropertiesSet() throws Exception {
this.retryContextCache = findBean(RetryContextCache.class);
this.methodArgumentsKeyGenerator = findBean(MethodArgumentsKeyGenerator.class);
this.newMethodArgumentsIdentifier = findBean(NewMethodArgumentsIdentifier.class);
this.sleeper = findBean(Sleeper.class);
Set<Class<? extends Annotation>> retryableAnnotationTypes = new LinkedHashSet<>(1);
retryableAnnotationTypes.add(Retryable.class);
//这里构建基于@Retryable注解的切点
this.pointcut = buildPointcut(retryableAnnotationTypes);
//这里构建了aop的通知
this.advice = buildAdvice();
this.advice.setBeanFactory(this.beanFactory);
if (this.enableRetry != null) {
setOrder(enableRetry.getNumber("order"));
}
}
}
RetryConfiguration继承AbstractPointcutAdvisor实现了一个环绕切面,通知的逻辑见AnnotationAwareRetryOperationsInterceptor的实现; 这里需要补一下spring AOP的基础知识,详情见文档 https://cloud.tencent.com/developer/article/1808649 ,我觉的这篇文章已经写的非常好了,就不再细述;
接下来我们重点关注 AnnotationAwareRetryOperationsInterceptor 的实现逻辑,这里才是retgry启动业务的实现逻辑;
三、AnotationAwareRetryOperationsInterceptor 重试retry逻辑的装配
我们看下它的核心实现
public class AnnotationAwareRetryOperationsInterceptor implements IntroductionInterceptor, BeanFactoryAware {
//...省略其它代码
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
//这里获取代理MethodInterceptor
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) {
//缓存解析结果,主要用于class级别的retry配置
ConcurrentMap<Method, MethodInterceptor> cachedMethods = this.delegates.get(target);
if (cachedMethods == null) {
cachedMethods = new ConcurrentHashMap<>();
}
MethodInterceptor delegate = cachedMethods.get(method);
if (delegate == null) {
MethodInterceptor interceptor = NULL_INTERCEPTOR;
Retryable retryable = AnnotatedElementUtils.findMergedAnnotation(method, Retryable.class);
if (retryable == null) {
retryable = classLevelAnnotation(method, Retryable.class);
}
if (retryable == null) {
retryable = findAnnotationOnTarget(target, method, Retryable.class);
}
if (retryable != null) {
//假如你需要实现自定义的拦截器,那就走的是这里的逻辑,例如你要实现retry的上下文放置到数据库里记录,不放在内存里,那你就要考虑这里作文章了
if (StringUtils.hasText(retryable.interceptor())) {
interceptor = this.beanFactory.getBean(retryable.interceptor(), MethodInterceptor.class);
}
//有状态的retry, 走的是这里,我没有深入研究stateful的应用场景,总感觉即便是有状态的重试,必然跟业务逻辑本身也强关联的,那状态保护的逻辑,肯定也会抽出来,所以我不考虑放到框架本身去做,毕竟不同业务,状态保护方法不同;
else if (retryable.stateful()) {
interceptor = getStatefulInterceptor(target, method, retryable);
}
//这里应该是通用逻辑链路了
else {
interceptor = getStatelessInterceptor(target, method, retryable);
}
}
cachedMethods.putIfAbsent(method, interceptor);
delegate = cachedMethods.get(method);
}
this.delegates.putIfAbsent(target, cachedMethods);
return delegate == NULL_INTERCEPTOR ? null : delegate;
}
//无状态的重试拦截器
private MethodInterceptor getStatelessInterceptor(Object target, Method method, Retryable retryable) {
//这里基于注解生产重试策略 和 延迟策略
RetryTemplate template = createTemplate(retryable.listeners());
template.setRetryPolicy(getRetryPolicy(retryable, true));
template.setBackOffPolicy(getBackoffPolicy(retryable.backoff(), true));
return RetryInterceptorBuilder.stateless()
.retryOperations(template)
.label(retryable.label())
//这里关注下怎么构建recover的
.recoverer(getRecoverer(target, method))
.build();
}
//...省略其它代码
}
最终,无状态的重试拦截器见,所以RetryOperationsInterceptor是常用链路的实现逻辑
public static class StatelessRetryInterceptorBuilder extends RetryInterceptorBuilder<RetryOperationsInterceptor> {
private final RetryOperationsInterceptor interceptor = new RetryOperationsInterceptor();
@Override
public RetryOperationsInterceptor build() {
if (this.recoverer != null) {
this.interceptor.setRecoverer(this.recoverer);
}
if (this.retryOperations != null) {
this.interceptor.setRetryOperations(this.retryOperations);
}
else {
this.interceptor.setRetryOperations(this.retryTemplate);
}
if (this.label != null) {
this.interceptor.setLabel(this.label);
}
return this.interceptor;
}
private StatelessRetryInterceptorBuilder() {
}
}
四、RetryOperationsInterceptor 常用retry operation的实现逻辑
RetryOperationsInterceptor是直接重试拦截器的实现逻辑,它的逻辑比较简单,封装了retryTemplate的执行逻辑;核心代码见RetryOperationsInterceptor#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;
//重试回调,这里不作赘述
RetryCallback<Object, Throwable> retryCallback = new MethodInvocationRetryCallback<Object, Throwable>(
invocation, label) {
@Override
public Object doWithRetry(RetryContext context) throws Exception {
context.setAttribute(RetryContext.NAME, this.label);
context.setAttribute("ARGS", new Args(invocation.getArguments()));
/*
* If we don't copy the invocation carefully it won't keep a reference to
* the other interceptors in the chain. We don't have a choice here but to
* specialise to ReflectiveMethodInvocation (but how often would another
* implementation come along?).
*/
if (this.invocation instanceof ProxyMethodInvocation) {
context.setAttribute("___proxy___", ((ProxyMethodInvocation) this.invocation).getProxy());
try {
return ((ProxyMethodInvocation) this.invocation).invocableClone().proceed();
}
catch (Exception | 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");
}
}
};
//有recover的时候,走这里就好
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__");
}
}
}
//没有recover走这里,都是调用RetryTemplate#execute的逻辑
return this.retryOperations.execute(retryCallback);
}
至此,全部的装配逻辑就讲结束,流程看下来并不复杂,对于未来自己想要定义一个类似的注解具有很大的参考意义!下一篇,我们讲解一下retry的真正的执行逻辑,并对整体的设计做一个介绍和思考总结