AOP-@EnableRetry实现的原理(源码存在瑕疵)
在使用Spring-Retry注解式重试时,需要在启动类上加上@EnableRetry
注解。那么这个注解的作用是什么呢?
启动类:
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy=true)
@EnableRetry
@EnableConfigurationProperties
@MapperScan("com.tellme.mapper")
public class StandApplication {
public static void main(String[] args) {
SpringApplication.run(StandApplication.class, args);
}
}
1. @EnableRetry源码分析
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@EnableAspectJAutoProxy(proxyTargetClass = false)
@Import(RetryConfiguration.class)
@Documented
public @interface EnableRetry {
/**
* 指示是否创建基于子类(CGLIB)的代理,而不是标准的基于Java接口的代理。默认值是{@code false}。
*/
boolean proxyTargetClass() default false;
}
该注解需要关注@Import(RetryConfiguration.class)
。
1.1 @Import注解的作用
目的:将RetryConfiguration加入到Spring容器。
若启动类上持有@SpringBootApplication
注解。那么会将启动类同包及其子包的SpringBean(例如:@Service等注解的bean)加入到Spring容器中。
但是org.springframework.retry.annotation.RetryConfiguration
类上虽然有@Configuration
注解,但是Spring却扫描不到,所以需要使用@Import
将RetryConfiguration
类加入到本项目的Spring容器中。
1.2 Spring AOP原理
目的:创建Bean时,会判断bean是否满足RetryConfiguration的pointcut。若满足则创建代理对象。
Spring AOP
原理是动态代理,Spring在出初始化bean时,会在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization
方法中执行配置的BeanPostProcessor
生成代理对象。
生成代理对象的源码分析:(测试代码见附录1)
@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor: getBeanPostProcessors()) {
//使用BeanPostProcessor处理对象(生成代理对象)。
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
debug流程.png
有上图可知,经过AnnotationAwareAspectJAutoProxyCreator
后,目标对象成功变为了代理对象。
SpringAOP联盟(7)-基础的自动代理(AnnotationAwareAspectJAutoProxyCreator)可知,AnnotationAwareAspectJAutoProxyCreator
会获取到Spring容器中的所有的Advisor
。在创建bean时,判断bean是否满足advisor持有的pointcut条件,若满足则创建代理对象。
注:spring-retry
使用AnnotationAwareAspectJAutoProxyCreator
完成代理。
1.3 带有瑕疵的RetryConfiguration类
RetryConfiguration类实际上就是一个advisor。因为使用了@Import
注解,那么该类会被加入到Spring容器。
@Configuration
public class RetryConfiguration extends AbstractPointcutAdvisor implements IntroductionAdvisor, BeanFactoryAware {
private Advice advice;
private Pointcut pointcut;
...
private BeanFactory beanFactory;
@PostConstruct
public void init() {
Set<Class<? extends Annotation>> retryableAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>(1);
retryableAnnotationTypes.add(Retryable.class);
//获取切面
this.pointcut = buildPointcut(retryableAnnotationTypes);
//创建通知
this.advice = buildAdvice();
if (this.advice instanceof BeanFactoryAware) {
((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
}
}
...
protected Advice buildAdvice() {
AnnotationAwareRetryOperationsInterceptor interceptor = new AnnotationAwareRetryOperationsInterceptor();
....
return interceptor;
}
protected Pointcut buildPointcut(Set<Class<? extends Annotation>> retryAnnotationTypes) {
ComposablePointcut result = null;
for (Class<? extends Annotation> retryAnnotationType : retryAnnotationTypes) {
//创建pointcut,切点中存在ClassFilter和MethodMatcher。
Pointcut filter = new AnnotationClassOrMethodPointcut(retryAnnotationType);
if (result == null) {
result = new ComposablePointcut(filter);
}
else {
//union(交集)和intersection(并集)的特性组合两个切入点
result.union(filter);
}
}
return result;
}
//定义的切点
private final class AnnotationClassOrMethodPointcut extends StaticMethodMatcherPointcut {
private final MethodMatcher methodResolver;
AnnotationClassOrMethodPointcut(Class<? extends Annotation> annotationType) {
this.methodResolver = new AnnotationMethodMatcher(annotationType);
setClassFilter(new AnnotationClassOrMethodFilter(annotationType));
}
//getClassFilter().matches(targetClass)只要类的任一方法(例如private)存在@Retry注解,那么便返回true。
@Override
public boolean matches(Method method, Class<?> targetClass) {
return getClassFilter().matches(targetClass) || this.methodResolver.matches(method, targetClass);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AnnotationClassOrMethodPointcut)) {
return false;
}
AnnotationClassOrMethodPointcut otherAdvisor = (AnnotationClassOrMethodPointcut) other;
return ObjectUtils.nullSafeEquals(this.methodResolver, otherAdvisor.methodResolver);
}
}
//类的过滤器
private final class AnnotationClassOrMethodFilter extends AnnotationClassFilter {
private final AnnotationMethodsResolver methodResolver;
AnnotationClassOrMethodFilter(Class<? extends Annotation> annotationType) {
super(annotationType, true);
this.methodResolver = new AnnotationMethodsResolver(annotationType);
}
//super.matches(clazz)会判断注解是否在class上存在
//this.methodResolver.hasAnnotatedMethods(clazz)若是注解在类的某一方法存在,返回true
@Override
public boolean matches(Class<?> clazz) {
return super.matches(clazz) || this.methodResolver.hasAnnotatedMethods(clazz);
}
}
//本类是AnnotationClassOrMethodFilter的一个属性
private static class AnnotationMethodsResolver {
//传入的注解
private Class<? extends Annotation> annotationType;
public AnnotationMethodsResolver(Class<? extends Annotation> annotationType) {
this.annotationType = annotationType;
}
//判断传入的注解在该类上“所有方法”上是否存在,存在返回true
public boolean hasAnnotatedMethods(Class<?> clazz) {
final AtomicBoolean found = new AtomicBoolean(false);
ReflectionUtils.doWithMethods(clazz,
new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException,
IllegalAccessException {
if (found.get()) {
return;
}
Annotation annotation = AnnotationUtils.findAnnotation(method,
annotationType);
if (annotation != null) { found.set(true); }
}
});
return found.get();
}
}
}
advisor中会持有advice(通知)和pointcut(切点)。若bean中A方法满足pointcut,那么该bean会被代理。
执行代理bean的A方法时,会调用advice(通知)进行增强。
上面源码创建
classFilter
时,若类上无@Retryable
注解,那么将通过反射获取到该类的所有method对象,判断method上是否存在@Retryable
。但是判断method是否满足methodMatcher时,难道不会再次遍历bean的所有method吗?
1.3.1 瑕疵
AnnotationClassOrMethodFilter
是类过滤器,在AnnotationMethodsResolver
却判断该类方法上是否存在对应注解,若存在,那么classFilter返回true。
上文说到使用了AnnotationAwareAspectJAutoProxyCreator
来完成代理,具体判断是否代理时依赖org.springframework.aop.support.AopUtils#canApply(org.springframework.aop.Pointcut, java.lang.Class<?>, boolean)
方法,源码如下:
public static boolean canApply(Pointcut pc, Class < ?>targetClass, boolean hasIntroductions) {
Assert.notNull(pc, "Pointcut must not be null");
//类过滤器(classFilter不是true,那么直接返回false)
if (!pc.getClassFilter().matches(targetClass)) {
return false;
}
//获取方法匹配器,若默认为ture,直接返回true
MethodMatcher methodMatcher = pc.getMethodMatcher();
if (methodMatcher == MethodMatcher.TRUE) {
// No need to iterate the methods if we're matching any method anyway...
return true;
}
IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
}
Set < Class < ?>>classes = new LinkedHashSet < >();
if (!Proxy.isProxyClass(targetClass)) {
classes.add(ClassUtils.getUserClass(targetClass));
}
classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
for (Class < ?>clazz: classes) {
//解析类上所有的方法
Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
//遍历所有方法,若某个方法的methodMatcher返回true,那么直接返回true。
for (Method method: methods) {
if (introductionAwareMethodMatcher != null ? introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) : methodMatcher.matches(method, targetClass)) {
return true;
}
}
}
return false;
}
RetryConfiguration
遍历所有方法,校验上面是否存在注解,来决定classFilter
的状态。实际上canApply
经过RetryConfiguration
的classFilter后,依旧需要遍历所有方法,由methodMatcher.matches
最终决定是否进行代理。
所以:RetryConfiguration
中去创建ClassFilter
的操作是存在性能瑕疵的。
将advisor类加入到Spring容器中,完成的AOP!
附录
1. 附录一
测试代码:
public interface XxService {
void test();
}
@Service
@Slf4j
public class XxServiceImpl implements XxService {
@Retryable
@Override
public void test() {
log.info("test()");
XxServiceImpl xxService = (XxServiceImpl) AopContext.currentProxy();
xxService.t();
}
}
断点的技巧.png