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

小龙 476 2022-06-10

问题

  1. Adivsor 是什么,它是什么时候构建的
  2. TargetSource 是什么,SpringFramework 的 AOP 为什么要代理它而不代理原始对象

Advisor与切面类的收集

在正在创建 **bean (doCreateBean)**之前会拦截容器中所有 InstantiationAwareBeanPostProcessor 接口的子类,并执行 postProcessBeforeInstantiation() 方法,AnnotationAwareAspectJAutoProxyCreator 实现了 InstantiationAwareBeanPostProcessor,所有在创建 bean 时会被拦截,postProcessBeforeInstantiation() 方法,该方法是在 AbstractAutoProxyCreator 抽象类中。该方法是收集所有需要增强的bean ,它有一个 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 增强器的过程:

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 切面类构造的增强器。

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 增强器。

检查现有的增强器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 的实现类都找出来,看看到底有没有,如果没有,那就不用走下面的流程了。

BeanFactoryUtils 的 beanNamesForTypeIncludingAncestors 方法,底层是用的 getBeanNamesForType 方法去找 bean 的名称(单纯的找 bean 的名称不会创建具体的 bean 对象,SpringFramework 在此做得很谨慎)

初始化原生增强器

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 初始化的动作。通过 Debug ,可以发现并没有原生的增强器被创建。

aspectJAdvisorsBuilder.buildAspectJAdvisors

它是将 Aspect 切面类,转换为一个一个的增强器。

逐个解析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 。

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

解析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 是不是一个切面类了,如果是,则会进入到里面的结构体,把这个类中的通知方法都构造出来。这两个小动作:

判断Class是通知类

判断当前 Class 是不是通知类(切面类),看看类上是不是有标注 @Aspect 注解就可以了

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

它额外判断了是不是被 ajc 编译器编译

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

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 切面类中的通知方法,只不过它在上下文的逻辑中补充了一些额外的校验、处理等等逻辑。

切面类中通知方法的收集

进入到 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 编码来的

增强器的创建

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

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

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

关注一下这个切入点表达式的解析,进入到 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 中已经提前定义好了所有可以声明切入点表达式的注解,并在此处一一寻找,找到就返回。

原型切面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 的解析是多次执行的。

增强器汇总

前面已经把所有的切面类都解析完了,这里只需要把这些构造好的增强器都集中到一个 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 个增强器都封装好了:

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

TargetSource

AOP 的代理其实不是代理的目标对象本身,而是目标对象包装后的 TargetSource 对象

TargetSource的设计

代理对象中直接组合了原始对象,直观一点的理解,就可以是这样子:

image-1654829553986

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

image-1654829564273

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

TargetSource的好处

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

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

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 。

SpringFramework中提供的TargetSource

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

  • SingletonTargetSource :每次 getTarget 都返回同一个目标对象 bean (与直接代理 target 无任何区别)
  • PrototypeTargetSourc :每次 getTarget 都会从 BeanFactory 中创建一个全新的 bean (被它包装的 bean 必须为原型 bean )
  • CommonsPool2TargetSource :内部维护了一个对象池,每次 getTarget 时从对象池中取(底层使用 apache 的 ObjectPool )
  • ThreadLocalTargetSource :每次 getTarget 都会从它所处的线程中取目标对象(由于每个线程都有一个 TargetSource ,所以被它包装的 bean 也必须是原型 bean )
  • HotSwappableTargetSource :内部维护了一个可以热替换的目标对象引用,每次 getTarget 的时候都返回它(它提供了一个线程安全的 swap 方法,以热替换 TargetSource 中被代理的目标对象)

# AOP