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却扫描不到,所以需要使用@ImportRetryConfiguration类加入到本项目的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条件,若满足则创建代理对象。

代理对象的advisor.png

注: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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 118,387评论 1 239
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 51,599评论 1 200
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 73,683评论 0 167
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 36,160评论 0 127
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 42,971评论 1 205
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 35,672评论 1 124
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 27,638评论 2 206
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 26,785评论 0 119
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 25,740评论 5 172
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 29,822评论 0 178
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 27,043评论 1 168
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 28,308评论 1 178
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 22,618评论 0 25
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 25,218评论 2 164
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 29,065评论 3 172
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 24,062评论 0 4
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 24,108评论 0 113
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 30,260评论 2 188
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 30,690评论 2 188

推荐阅读更多精彩内容