Spring AOP是如何收集切面类并封装的

这一章咱主要研究两个大问题:

  1. Advisor 是什么,它是怎么构建的

  2. TargetSource 是什么,SpringFramework 的 AOP 为什么要代理它而不是原始对象

1. Advisor与切面类的收集

在上一章中,咱有看到一个 shouldSkip 的跳过动作:

// AspectJAwareAdvisorAutoProxyCreator
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    for (Advisor advisor : candidateAdvisors) {
        if (advisor instanceof AspectJPointcutAdvisor &&
                ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
            return true;
        }
    }
    return super.shouldSkip(beanClass, beanName);
}

这里面它会先获取到一些候选的增强器,而这个方法的底层,其实就是解析切面类,构造 Advisor 增强器的过程,咱深入研究一下。

findCandidateAdvisors 方法的源码还算简单:

protected List<Advisor> findCandidateAdvisors() {
    // Add all the Spring advisors found according to superclass rules.
    // 根据父类的规则添加所有找到的Spring原生的增强器
    List<Advisor> advisors = super.findCandidateAdvisors();
    // Build Advisors for all AspectJ aspects in the bean factory.
    // 解析BeanFactory中所有的AspectJ切面,并构造增强器
    if (this.aspectJAdvisorsBuilder != null) {
        advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    }
    return advisors;
}

总共两个步骤,分别代表切面的两个来源:SpringFramework 原生 AOP 的增强器,以及解析完 AspectJ 切面类构造的增强器。咱一个一个来看。

1.1 super.findCandidateAdvisors

来到父类 AbstractAdvisorAutoProxyCreator 中:

private BeanFactoryAdvisorRetrievalHelper advisorRetrievalHelper;

protected List<Advisor> findCandidateAdvisors() {
    Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available");
    return this.advisorRetrievalHelper.findAdvisorBeans();
}

可以发现它委托了一个 advisorRetrievalHelper 来处理 SpringFramework 原生的 AOP 增强器。而它这个方法的篇幅就比较长了,咱拆解出核心的主干逻辑研究。

1.1.1 检查现有的增强器bean

注意看源码中的注释:

public List<Advisor> findAdvisorBeans() {
    // Determine list of advisor bean names, if not cached already.
    // 确定增强器bean名称的列表(如果尚未缓存)
    String[] advisorNames = this.cachedAdvisorBeanNames;
    if (advisorNames == null) {
        // Do not initialize FactoryBeans here: We need to leave all regular beans
        // uninitialized to let the auto-proxy creator apply to them!
        // 不要在这里初始化FactoryBeans:
        // 我们需要保留所有未初始化的常规bean,以使自动代理创建者对其应用
        advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                this.beanFactory, Advisor.class, true, false);
        this.cachedAdvisorBeanNames = advisorNames;
    }
    // 如果当前IOC容器中没有任何增强器类型的bean,直接返回
    if (advisorNames.length == 0) {
        return new ArrayList<>();
    }
    // ......

这里的主要动作,是将 IOC 容器中所有类型为 Advisor 的实现类都找出来,看看到底有没有,如果没有,那就不用走下面的流程了。

BeanFactoryUtilsbeanNamesForTypeIncludingAncestors 方法,底层是用的 getBeanNamesForType 方法去找 bean 的名称(单纯的找 bean 的名称不会创建具体的 bean 对象,SpringFramework 在此做得很谨慎),感兴趣的小伙伴可以自己去看一下,小册在此就不展开了。

1.1.2 初始化原生增强器

    List<Advisor> advisors = new ArrayList<>();
    for (String name : advisorNames) {
        if (isEligibleBean(name)) {
            if (this.beanFactory.isCurrentlyInCreation(name)) {
                // logger ......
            }
            else {
                try {
                    advisors.add(this.beanFactory.getBean(name, Advisor.class));
                } // catch ......
            }
        }
    }
    return advisors;
}

下面的源码蛮长的,不过这里咱只关心主干流程的重点,那无非就是 beanFactory.getBean 了,非常常规的 bean 初始化的动作。只不过我们自始至终都没有讲过 SpringFramework 原生的增强器,因为编写它实在是太复杂了,主流的编写还是以 AspectJ 形式为主,所以咱这里知道一下就可以了。

至于中间省略的 catch 块,其实流程还挺复杂的,里面做了循环依赖的异常处理,由于涉及的内容过于复杂,而且咱前面也说过了,循环依赖的部分可以在 SpringBoot 小册的第 15 章完整的学习,所以咱这里就不多展开了。

通过 Debug ,也可以发现并没有原生的增强器被创建:

以上就是在父类 AbstractAdvisorAutoProxyCreator 中的 findCandidateAdvisors 方法的逻辑,下面咱来看另一部分的委托:aspectJAdvisorsBuilder

1.2 aspectJAdvisorsBuilder.buildAspectJAdvisors

从方法名上理解,它就是将 Aspect 切面类,转换为一个一个的增强器。这个方法也是长得很,咱分段来研究。

1.2.1 逐个解析IOC容器中所有的bean类型

这段源码一下子把缩进拉上去了,阅读观感可能不是特别好,不过好在逻辑不算复杂:

public List<Advisor> buildAspectJAdvisors() {
    List<String> aspectNames = this.aspectBeanNames;

    if (aspectNames == null) {
        synchronized (this) {
            aspectNames = this.aspectBeanNames;
            if (aspectNames == null) {
                List<Advisor> advisors = new ArrayList<>();
                aspectNames = new ArrayList<>();
                // 获取IOC容器中的所有bean
                String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                        this.beanFactory, Object.class, true, false);
                for (String beanName : beanNames) {
                    if (!isEligibleBean(beanName)) {
                        continue;
                    }
                    // We must be careful not to instantiate beans eagerly as in this case they
                    // would be cached by the Spring container but would not have been weaved.
                    // 我们必须小心,不要急于实例化bean,因为在这种情况下,IOC容器会缓存它们,但不会被织入增强器
                    Class<?> beanType = this.beanFactory.getType(beanName);
                    if (beanType == null) {
                        continue;
                    }
                    // ......

可以发现,这一段的核心逻辑,是将 IOC 容器,以及它的父 IOC 容器中,所有的 bean 的名称全部取出(直接声明父类型为 Object ,显然是取所有),之后,它会逐个解析这些 bean 对应的 Class

Debug 中也发现,它真的把包括内部的一些 bean ( environment )在内的所有 bean 的名称全部拿了出来:

注意一个细节,框架在这里控制的很好,它借助 BeanFactory 去取 bean 的类型,而不是先 getBean 后再取类型,这样可以保证 bean 不会被提前创建。而没有初始化 bean 实例的前提下,要想获取 bean 的 Class ,那就只能靠 BeanDefinition 了,所以我们可以在 AbstractBeanFactorygetType 方法中看到合并 RootBeanDefinition 的动作,随后调用 RootBeanDefinitiongetBeanClass 方法获取 bean 的 Class 类型。

1.2.2 解析Aspect切面类,构造增强器

这段缩进实在是太大了,我给调小了一点,小伙伴们看着还舒服点:

    // ......
    if (this.advisorFactory.isAspect(beanType)) {
        // 当前解析bean的所属类型是一个切面类
        aspectNames.add(beanName);
        AspectMetadata amd = new AspectMetadata(beanType, beanName);
        // 下面是单实例切面bean会走的流程
        if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
            MetadataAwareAspectInstanceFactory factory =
                    new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
            // 解析生成增强器
            List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
            if (this.beanFactory.isSingleton(beanName)) {
                this.advisorsCache.put(beanName, classAdvisors);
            }
            else {
                this.aspectFactoryCache.put(beanName, factory);
            }
            advisors.addAll(classAdvisors);
        }
        // ......

这一步就是判断当前解析的 bean 所属的 Class 是不是一个切面类了,如果是,则会进入到里面的结构体,把这个类中的通知方法都构造出来。

这两个小动作,咱拿出来解析一下。

1.2.2.1 判断Class是通知类

如何判断当前 Class 是不是通知类(切面类)呢?很简单,看看类上是不是有标注 @Aspect 注解就完事了呗?但是源码中还多判断了一步:

public boolean isAspect(Class<?> clazz) {
    // @Aspect注解并且不是被ajc编译器编译的
    return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));
}

请注意一点,它额外判断了是不是被 ajc 编译器编译,这是为什么呢?我们可以从文档注释中获取到一些信息:

We consider something to be an AspectJ aspect suitable for use by the Spring AOP system if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test is that aspects written in the code-style (AspectJ language) also have the annotation present when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP.

如果它有 @Aspect 注解,并且不是由 ajc 编译的,我们才认为这个 Class 类是适合 Spring AOP 系统使用的 AspectJ 切面。不用 ajc 编译的原因,是因为以代码风格(AspectJ 语言)编写的方面在由带有 -1.5 标志的 ajc 编译时也存在注解,但它们不能被 Spring AOP 使用。

简单的理解来说,SpringFramework 的 AOP 有整合 AspectJ 的部分,而原生的 AspectJ 也可以编写 Aspect 切面,而这种切面在特殊的编译条件下,生成的字节码中类上也会标注 **@Aspect** 注解,但是 SpringFramework 并不能利用它,所以这里它做了一个额外的判断处理,避免了这种 Class 被误加载。

1.2.2.2 advisorFactory.getAdvisors:构造增强器

这个方法又是好复杂呀,咱来到 ReflectiveAspectJAdvisorFactory 中来看:

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
    // Aspect切面类的Class
    Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
    String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
    // 再次校验一下切面类上是不是标注了@Aspect注解
    validate(aspectClass);

    // 此处利用Decorator装饰者模式,目的是保证Advisor增强器不会被多次实例化
    MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
            new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);

    List<Advisor> advisors = new ArrayList<>();
    // 逐个解析通知方法,并封装为增强器
    for (Method method : getAdvisorMethods(aspectClass)) {
        Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
        if (advisor != null) {
            advisors.add(advisor);
        }
    }

    // If it's a per target aspect, emit the dummy instantiating aspect.
    // 通过在装饰者内部的开始加入SyntheticInstantiationAdvisor增强器,达到延迟初始化切面bean的目的
    if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
        Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
        advisors.add(0, instantiationAdvisor);
    }

    // Find introduction fields.
    // 对@DeclareParent注解功能的支持(AspectJ的引介)
    for (Field field : aspectClass.getDeclaredFields()) {
        Advisor advisor = getDeclareParentsAdvisor(field);
        if (advisor != null) {
            advisors.add(advisor);
        }
    }

    return advisors;
}

整段源码阅读下来,思路倒是蛮明确,它果然与我们的推测一致,就是解析 Aspect 切面类中的通知方法,只不过它在上下文的逻辑中补充了一些额外的校验、处理等等逻辑。

重点关注这里面的两个小动作:1) 通知方法是怎么收集的;2) 增强器的创建都需要什么东西。

1.2.2.3 切面类中通知方法的收集

进入到 getAdvisorMethods 方法中:

private List<Method> getAdvisorMethods(Class<?> aspectClass) {
    final List<Method> methods = new ArrayList<>();
    ReflectionUtils.doWithMethods(aspectClass, method -> {
        // Exclude pointcuts
        if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) {
            methods.add(method);
        }
    }, ReflectionUtils.USER_DECLARED_METHODS);
    if (methods.size() > 1) {
        methods.sort(METHOD_COMPARATOR);
    }
    return methods;
}

其实会发现,这个方法很简单的,它就是把咱们定义的切面类中除了通用的切入点表达式抽取以外的所有方法都取出来!并且取出来之后又做了一个排序的动作,而排序的原则就是按照 Unicode 编码来的,这个咱已经研究过了。

1.2.2.4 增强器的创建

getAdvisor 方法就是创建 Advisor 增强器了,可能有的小伙伴会产生疑惑,上面的方法只是取出了自己定义的非 @Pointcut 方法,那对于没有声明切入点表达式的方法,它岂不是也一起返回了?不过没关系,其实它在这里又做了一次过滤:

public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
        int declarationOrderInAspect, String aspectName) {
    validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());

    AspectJExpressionPointcut expressionPointcut = getPointcut(
            candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
    // 没有声明通知注解的方法也会被过滤
    if (expressionPointcut == null) {
        return null;
    }
    return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
            this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}

可以发现框架还是做得滴水不漏的。注意看下面的构造方法中传入的关键参数,它们分别是:

  • expressionPointcut :AspectJ 切入点表达式的封装

  • candidateAdviceMethod :通知方法本体

  • this :当前的 ReflectiveAspectJAdvisorFactory

  • aspectInstanceFactory :上面的那个装饰者 MetadataAwareAspectInstanceFactory

刨去工厂本身,其实增强器的结构就是一个切入点表达式 + 一个通知方法,与之前的推测完全一致。

1.2.2.5 解析通知注解上的切入点表达式

到此为止其实增强器本身已经没有什么问题了,咱再来关注一下这个切入点表达式的解析,进入到 getPointcut 方法中:

private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {
    // 检索通知方法上的注解
    AspectJAnnotation<?> aspectJAnnotation =
            AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);
    if (aspectJAnnotation == null) {
        return null;
    }

    // 根据注解的类型,构造切入点表达式模型
    AspectJExpressionPointcut ajexp =
            new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);
    ajexp.setExpression(aspectJAnnotation.getPointcutExpression());
    if (this.beanFactory != null) {
        ajexp.setBeanFactory(this.beanFactory);
    }
    return ajexp;
}

步骤很简单,先去找通知方法上标注的注解,然后把切入点表达式提取出来,返回。很明显 findAspectJAnnotationOnMethod 的逻辑是相对重要的,咱进去看看:

private static final Class<?>[] ASPECTJ_ANNOTATION_CLASSES = new Class<?>[] {
        Pointcut.class, Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class};

protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {
    for (Class<?> clazz : ASPECTJ_ANNOTATION_CLASSES) {
        AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) clazz);
        if (foundAnnotation != null) {
            return foundAnnotation;
        }
    }
    return null;
}

果然,它在 AbstractAspectJAdvisorFactory 中已经提前定义好了所有可以声明切入点表达式的注解,并在此处一一寻找,找到就返回。

在此 Debug 会发现它只是把切入点表达式的内容,以及参数等信息,封装到 AspectJExpressionPointcut 中了:

1.2.3 原型切面bean的处理

上面我们只是看到了单实例切面 bean 的处理和解析,下面的 else 部分是原型切面 bean 的处理逻辑:

                // ......
                else {
                    // Per target or per this.
                    if (this.beanFactory.isSingleton(beanName)) {
                        throw new IllegalArgumentException("Bean with name '" + beanName +
                                "' is a singleton, but aspect instantiation model is not singleton");
                    }
                    MetadataAwareAspectInstanceFactory factory =
                            new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
                    this.aspectFactoryCache.put(beanName, factory);
                    // 解析Aspect切面类,构造增强器
                    advisors.addAll(this.advisorFactory.getAdvisors(factory));
                }
            }
        }
        this.aspectBeanNames = aspectNames;
        return advisors;
    }
    // ......

可以发现,对于原型切面 bean 的解析,它的核心解析动作依然是 advisorFactory.getAdvisors 方法,只是这里面不会再用到 advisorsCache 这个缓存区了,这也说明原型切面 bean 的解析是多次执行的。

1.2.4 增强器汇总

最后一部分到了整理的环节了,前面已经把所有的切面类都解析完了,这里只需要把这些构造好的增强器都集中到一个 List 中,返回即可。源码很简单,小伙伴们扫一眼就好:

    // ......
    if (aspectNames.isEmpty()) {
        return Collections.emptyList();
    }
    List<Advisor> advisors = new ArrayList<>();
    for (String aspectName : aspectNames) {
        List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
        if (cachedAdvisors != null) {
            advisors.addAll(cachedAdvisors);
        }
        else {
            MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
            advisors.addAll(this.advisorFactory.getAdvisors(factory));
        }
    }
    return advisors;
}

至此,切入点表达式也就解析完了,通知方法也有了,Advisor 增强器也就顺理的创建出来了。

Debug 返回的时候,可以发现 Logger 中的 5 个增强器都封装好了:

所有的增强器创建完成后,接下来的内容就是承接上一章的 bean 匹配,决定是否跳过 bean 的增强的步骤了。

2. TargetSource

上一章咱简单的提了一嘴,AOP 的代理其实不是代理的目标对象本身,而是目标对象包装后的 **TargetSource** 对象,SpringFramework 为什么要这么做,这样做有什么好处,咱这里也来深入研究一下。

2.1 TargetSource的设计

之前在复习动态代理的时候,咱说代理对象中直接组合了原始对象,直观一点的理解,就可以是这样子:

但是在 SpringFramework 的 AOP 中,代理对象并没有直接代理 Target ,而是给 Target 加了一个壳,而加的这个壳就是 **TargetSource** ,用图示理解就是这样:

是不是一下子就容易理解了呢?TargetSource 可以看做是目标对象 Target 的一个包装、容器,原本代理对象要执行 method.invoke(target, args) 这样的逻辑时,本来要拿到的是目标对象,但被 TargetSource 包装之后,就只能调用 method.invoke(targetSource.getTarget(), args) 这样的形式了。

2.2 TargetSource的好处

既然每次调用代理对象的方法,最终会调用到 TargetSourcegetTarget 方法,而这个 getTarget 方法是 TargetSource 决定如何返回的,那这里面可就大有文章了。举个最简单的例子:每次 getTarget 的值可以不一样吧?每次 getTarget 的时候可以从一个对象池中取吧?哎,是不是突然想到了数据库连接池?其实 TargetSource 也有基于池的实现。

所以咱可以总结出来,让 AOP 代理 TargetSource 的好处,是可以控制每次方法调用时作用的具体对象实例,从而让方法的调用更加灵活

2.3 TargetSource的结构

翻开 TargetSource 的源码,可以发现它是一个接口,

public interface TargetSource extends TargetClassAware {
	Class<?> getTargetClass();
	boolean isStatic();
	Object getTarget() throws Exception;
	void releaseTarget(Object target) throws Exception;
}

可以发现除了 getTarget 方法之外,还有一个 releaseTarget ,咱也能很快的猜到它的作用是交回 / 释放目标对象之类的操作,它也是用于那些基于对象池的 TargetSource ,在目标对象调用方法完成后,紧接着调用 releaseTarget 方法来释放目标对象的。

另外还有一个 isStatic 方法,可能小伙伴们会疑惑:bean 哪来的静态一说?对于 Java 来讲,Class 与 object 的作用域可以分出来静态和非静态( Class 级别的成员是静态的,object 级别的成员是非静态的),那对于 SpringFramework 来讲,单实例 bean 与原型 bean 的作用域也可以划分出静态和非静态的概念:单实例 bean 是一个 ApplicationContext 中只有一个实例,原型 bean 是每次获取都会拿到一个全新的实例,所以单实例 bean 就可以划为 “静态 bean ”,原型 bean 则为非静态 bean 。

2.4 SpringFramework中提供的TargetSource

SpringFramework 中针对不同场景不同需求,预设了几个 TargetSource 的实现,咱可以稍微了解一下:

  • SingletonTargetSource :每次 getTarget 都返回同一个目标对象 bean (与直接代理 target 无任何区别)

  • PrototypeTargetSource :每次 getTarget 都会从 BeanFactory 中创建一个全新的 bean (被它包装的 bean 必须为原型 bean )

  • CommonsPool2TargetSource :内部维护了一个对象池,每次 getTarget 时从对象池中取(底层使用 apache 的 ObjectPool

  • ThreadLocalTargetSource :每次 getTarget 都会从它所处的线程中取目标对象(由于每个线程都有一个 TargetSource ,所以被它包装的 bean 也必须是原型 bean )

  • HotSwappableTargetSource :内部维护了一个可以热替换的目标对象引用,每次 getTarget 的时候都返回它(它提供了一个线程安全的 swap 方法,以热替换 TargetSource 中被代理的目标对象)

这些设计在底层都不算复杂,小伙伴们可以自行翻一把源码看看,小册就不大张旗鼓的贴源码了。

最后更新于