Spring AOP 原始碼初窺(三)掃描Advice與Bean匹配

不苦發表於2019-01-19

版本

  • spring 5.0.8.BUILD-SNAPSHOT
  • aspectjweaver 1.8.13

如何掃描Advice

接上一回,講到了getAdvicesAndAdvisorsForBean方法,該方法的目的是獲取並生成Advisor Bean。其中包含了掃描通過@Aspect註解配置且與Bean方法的匹配的Advice,也是本章主要講的內容

getAdvicesAndAdvisorsForBean

/org/springframework/aop/framework/autoproxy/AbstractAdvisorAutoProxyCreator.java

@Override
@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(
        Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {

    List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
    if (advisors.isEmpty()) {
        return DO_NOT_PROXY;
    }
    return advisors.toArray();
}

/**
 * Find all eligible Advisors for auto-proxying this class.
 * @param beanClass the clazz to find advisors for
 * @param beanName the name of the currently proxied bean
 * @return the empty List, not {@code null},
 * if there are no pointcuts or interceptors
 * @see #findCandidateAdvisors
 * @see #sortAdvisors
 * @see #extendAdvisors
 */
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
    List<Advisor> candidateAdvisors = findCandidateAdvisors();
    List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
    extendAdvisors(eligibleAdvisors);
    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

....

關注findEligibleAdvisors方法可以清楚地知道這裡主要分四步:

  • findCandidateAdvisors (獲取候選Advisor)
  • findAdvisorsThatCanApply (獲取適用於bean的Advisor: 例如Pointcut匹配)
  • extendAdvisors (特殊處理,這裡不贅述)
  • sortAdvisors (排序,不贅述)

什麼是Advisor? 首先,Advice是增強方法,即@Around, @Before等註解修飾的方法。而Advisor則是在Advice之上再包了一層。例如PointcutAdvisor則包有Advice和Pointcut

下面接著看findCandidateAdvisors和findAdvisorsThatCanApply

findCandidateAdvisors

/org/springframework/aop/framework/autoproxy/AbstractAdvisorAutoProxyCreator.java
....
/**
 * Find all candidate Advisors to use in auto-proxying.
 * @return the List of candidate Advisors
 */
protected List<Advisor> findCandidateAdvisors() {
    Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available");
    return this.advisorRetrievalHelper.findAdvisorBeans();
}
....
/org/springframework/aop/aspectj/annotation/AnnotationAwareAspectJAutoProxyCreator.java
....
@Override
protected List<Advisor> findCandidateAdvisors() {
    // Add all the Spring advisors found according to superclass rules.
    List<Advisor> advisors = super.findCandidateAdvisors();
    // Build Advisors for all AspectJ aspects in the bean factory.
    if (this.aspectJAdvisorsBuilder != null) {
        advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
    }
    return advisors;
}
....

這裡findCandidateAdvisors在AbstractAdvisorAutoProxyCreator中有實現,同時被AnnotationAwareAspectJAutoProxyCreator重寫了。不過可以看到重寫的方法中先呼叫了super.findCandidateAdvisor,因此兩個方法的程式碼都被執行了。

  • this.advisorRetrievalHelper.findAdvisorBeans (該方法主要從BeanFactory中獲取Advisor Bean)
  • this.aspectJAdvisorsBuilder.buildAspectJAdvisors (從所有Bean中獲取@Aspect配置的Bean並建立Advisor,也是我們關注的內容,下面細講)
buildAspectJAdvisors
/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java
....
/**
 * Look for AspectJ-annotated aspect beans in the current bean factory,
 * and return to a list of Spring AOP Advisors representing them.
 * <p>Creates a Spring Advisor for each AspectJ advice method.
 * @return the list of {@link org.springframework.aop.Advisor} beans
 * @see #isEligibleBean
 */
public List<Advisor> buildAspectJAdvisors() {
    // aspectBeanNames,快取
    // tips: aspectBeanNames由volatile修飾
    // volatile: 保證變數可見性,指令不可重排
    List<String> aspectNames = this.aspectBeanNames;
    // 如快取存在,則跳過初始化步驟
    if (aspectNames == null) {
        // synchronized, 同步鎖
        // 鎖住當前例項,裡面的內容同一時間只會被一個執行緒執行
        synchronized (this) {
            // 拿到鎖後再次獲取快取,避免重複初始化
            aspectNames = this.aspectBeanNames;
            if (aspectNames == null) {
                List<Advisor> advisors = new ArrayList<>();
                aspectNames = new ArrayList<>();
                
                // 獲取原始類為Object的bean,即所有bean
                String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                        this.beanFactory, Object.class, true, false);
                for (String beanName : beanNames) {
                    // 是否是合適的bean
                    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.
                    Class<?> beanType = this.beanFactory.getType(beanName);
                    if (beanType == null) {
                        continue;
                    }
                    
                    // isAspect 是否為@Aspect註解修飾的Bean
                    // 是不是很親切方法,終於讀到它了。這是我們在第二章一開始就提到的方法。
                    if (this.advisorFactory.isAspect(beanType)) {
                        aspectNames.add(beanName);
                        
                        // 由beanType,beanName組合Metadata,包含了建立Advisor需要的內容
                        AspectMetadata amd = new AspectMetadata(beanType, beanName);
                        
                        // PerClauseKind.SINGLETON 單例模式
                        // 由@Aspect中的value引數配置,這個引數Bean的scope 有點類似,用來配置生命 週期,預設都為單例。可配為"每..."的模式
                        if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                            // 生成例項工廠類
                            MetadataAwareAspectInstanceFactory factory =
                                    new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                            // 生成Advisors例項
                            List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                            
                            // 如果Aspect Bean是單例,則快取到advisorsCache
                            if (this.beanFactory.isSingleton(beanName)) {
                                this.advisorsCache.put(beanName, classAdvisors);
                            }
                            // 如果不是,則將工廠快取到aspectFactoryCache
                            else {
                                this.aspectFactoryCache.put(beanName, factory);
                            }
                            advisors.addAll(classAdvisors);
                        }
                        else {
                            // Per target or per this.
                            // bean為單例,@Aspect也要配置為單例
                            if (this.beanFactory.isSingleton(beanName)) {
                                throw new IllegalArgumentException("Bean with name `" + beanName +
                                        "` is a singleton, but aspect instantiation model is not singleton");
                            }
                            
                            // 跟前一個分支一樣,生成Advisors例項,然後將工廠快取到aspectFactoryCache
                            MetadataAwareAspectInstanceFactory factory =
                                    new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
                            this.aspectFactoryCache.put(beanName, factory);
                            advisors.addAll(this.advisorFactory.getAdvisors(factory));
                        }
                    }
                }
                
                this.aspectBeanNames = aspectNames;
                return advisors;
            }
        }
    }

    if (aspectNames.isEmpty()) {
        return Collections.emptyList();
    }
    List<Advisor> advisors = new ArrayList<>();
    for (String aspectName : aspectNames) {
        // 通過aspectName(beanName)獲取advisors快取
        List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
        
        // 如已存在,則載入advisorsCache
        if (cachedAdvisors != null) {
            advisors.addAll(cachedAdvisors);
        }
        // 如不存在,則載入factoryCache,再從工廠生成advisors,與上面初始時候的兩個分支對應
        // ps:這裡並沒有做factory的空判斷...
        else {
            MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
            advisors.addAll(this.advisorFactory.getAdvisors(factory));
        }
    }
    return advisors;
}
....

由於這段程式碼比較長,我將過程註釋在程式碼中。其中英文為原始碼註釋。

那麼,以上便是通過beanName掃描@Aspect配置並生成Advisor的過程了。其中this.advisorFactory.getAdvisors(factory)是生成Advisor類的具體內容。深挖的話還能再寫一篇文章,這裡就不細說了。有興趣的可以自行閱讀。

findAdvisorsThatCanApply

現在我們獲得了所有的候選Advisor,那麼找出和當前Bean匹配的Advisor呢?

/org/springframework/aop/framework/autoproxy/AbstractAdvisorAutoProxyCreator.java
....
/**
 * Search the given candidate Advisors to find all Advisors that
 * can apply to the specified bean.
 * @param candidateAdvisors the candidate Advisors
 * @param beanClass the target`s bean class
 * @param beanName the target`s bean name
 * @return the List of applicable Advisors
 * @see ProxyCreationContext#getCurrentProxiedBeanName()
 */
protected List<Advisor> findAdvisorsThatCanApply(
        List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {

    ProxyCreationContext.setCurrentProxiedBeanName(beanName);
    try {
        return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
    }
    finally {
        ProxyCreationContext.setCurrentProxiedBeanName(null);
    }
}
....

一步一步往下探

/org/springframework/aop/support/AopUtils.java
....
/**
 * Determine the sublist of the {@code candidateAdvisors} list
 * that is applicable to the given class.
 * @param candidateAdvisors the Advisors to evaluate
 * @param clazz the target class
 * @return sublist of Advisors that can apply to an object of the given class
 * (may be the incoming List as-is)
 */
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
    if (candidateAdvisors.isEmpty()) {
        return candidateAdvisors;
    }
    List<Advisor> eligibleAdvisors = new ArrayList<>();
    for (Advisor candidate : candidateAdvisors) {
        if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
            eligibleAdvisors.add(candidate);
        }
    }
    boolean hasIntroductions = !eligibleAdvisors.isEmpty();
    for (Advisor candidate : candidateAdvisors) {
        if (candidate instanceof IntroductionAdvisor) {
            // already processed
            continue;
        }
        if (canApply(candidate, clazz, hasIntroductions)) {
            eligibleAdvisors.add(candidate);
        }
    }
    return eligibleAdvisors;
}
....
/**
 * Can the given advisor apply at all on the given class?
 * This is an important test as it can be used to optimize
 * out a advisor for a class.
 * @param advisor the advisor to check
 * @param targetClass class we`re testing
 * @return whether the pointcut can apply on any method
 */
public static boolean canApply(Advisor advisor, Class<?> targetClass) {
    return canApply(advisor, targetClass, false);
}

/**
 * Can the given advisor apply at all on the given class?
 * <p>This is an important test as it can be used to optimize out a advisor for a class.
 * This version also takes into account introductions (for IntroductionAwareMethodMatchers).
 * @param advisor the advisor to check
 * @param targetClass class we`re testing
 * @param hasIntroductions whether or not the advisor chain for this bean includes
 * any introductions
 * @return whether the pointcut can apply on any method
 */
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
    if (advisor instanceof IntroductionAdvisor) {
        return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
    }
    else if (advisor instanceof PointcutAdvisor) {
        PointcutAdvisor pca = (PointcutAdvisor) advisor;
        return canApply(pca.getPointcut(), targetClass, hasIntroductions);
    }
    else {
        // It doesn`t have a pointcut so we assume it applies.
        return true;
    }
}
....

最後定位到canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions)方法

/org/springframework/aop/support/AopUtils.java
/**
 * Can the given pointcut apply at all on the given class?
 * <p>This is an important test as it can be used to optimize
 * out a pointcut for a class.
 * @param pc the static or dynamic pointcut to check
 * @param targetClass the class to test
 * @param hasIntroductions whether or not the advisor chain
 * for this bean includes any introductions
 * @return whether the pointcut can apply on any method
 */
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
    Assert.notNull(pc, "Pointcut must not be null");
    if (!pc.getClassFilter().matches(targetClass)) {
        return false;
    }

    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);
        for (Method method : methods) {
            if (introductionAwareMethodMatcher != null ?
                    introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
                    methodMatcher.matches(method, targetClass)) {
                return true;
            }
        }
    }

    return false;
}

可以看出判斷是否是該bean合適的advisor,是通過advisor.getPointcut().getClassFilter().matches(targetClass)方法來判斷的。匹配完class以後下面還有MethodMatcher來匹配method。回想我們在配置pointcut的時候不僅僅有class的規則,也有method的規則。

當然,再深入matches方法進去的話就是pointcut的匹配語法實現了。有興趣的可以自行閱讀。

一些總結

讀到這兒,Spring AOP如何掃描@Aspect配置,生成Advisor類,並匹配對應的Bean整個流程已經很清楚了。這裡再總結一下:

  1. 獲取已在BeanFactory的Advisor Bean
  2. 獲取所有Object Bean,過濾出@Aspect註解修飾的Bean,並生成Advisor
  3. 遍歷上述獲取的所有Advisor,由Advisor的Pointcut ClassFilter匹配合適的Bean

相關文章