天天看點

Spring AOP 源碼分析 - 篩選合适的通知器

1.簡介

從本篇文章開始,我将會對 Spring AOP 部分的源碼進行分析。本文是 Spring AOP 源碼分析系列文章的第二篇,本文主要分析 Spring AOP 是如何為目标 bean 篩選出合适的通知器(Advisor)。在上一篇AOP 源碼分析導讀一文中,我簡單介紹了 AOP 中的一些術語及其對應的源碼,部分術語和源碼将會在本篇文章中出現。如果大家不熟悉這些術語和源碼,不妨去看看。

關于 Spring AOP,我個人在日常開發中用過一些,也參照過 tiny-spring 過寫過一個玩具版的 AOP 架構,并寫成了文章。正因為前面做了一些準備工作,最近再看 Spring AOP 源碼時,覺得也沒那麼難了。是以如果大家打算看 AOP 源碼的話,這裡建議大家多做一些準備工作。比如熟悉 AOP 的中的術語,亦或是實作一個簡單的 IOC 和 AOP,并将兩者整合在一起。經過如此準備,相信大家會對 AOP 會有更多的認識。

好了,其他的就不多說了,下面進入源碼分析階段。

2.源碼分析

2.1 AOP 入口分析

在導讀一文中,我已經說過 Spring AOP 是在何處向目标 bean 中織入通知(Advice)的。也說過 Spring 是如何将 AOP 和 IOC 子產品整合到一起的,即通過拓展點 BeanPostProcessor 接口。Spring AOP 抽象代理建立器實作了 BeanPostProcessor 接口,并在 bean 初始化後置處理過程中向 bean 中織入通知。下面我們就來看看相關源碼,如下:

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport             implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {         @Override         /** bean 初始化後置處理方法 */         public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {             if (bean != null) {                 Object cacheKey = getCacheKey(bean.getClass(), beanName);                 if (!this.earlyProxyReferences.contains(cacheKey)) {                     // 如果需要,為 bean 生成代理對象                     return wrapIfNecessary(bean, beanName, cacheKey);                 }             }             return bean;         }         protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {             if (beanName != null && this.targetSourcedBeans.contains(beanName)) {                 return bean;             }             if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {                 return bean;             }             /*              * 如果是基礎設施類(Pointcut、Advice、Advisor 等接口的實作類),或是應該跳過的類,              * 則不應該生成代理,此時直接傳回 bean              */              if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {                 // 将 <cacheKey, FALSE> 鍵值對放入緩存中,供上面的 if 分支使用                 this.advisedBeans.put(cacheKey, Boolean.FALSE);                 return bean;             }             // 為目标 bean 查找合适的通知器             Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);             /*              * 若 specificInterceptors != null,即 specificInterceptors != DO_NOT_PROXY,              * 則為 bean 生成代理對象,否則直接傳回 bean              */              if (specificInterceptors != DO_NOT_PROXY) {                 this.advisedBeans.put(cacheKey, Boolean.TRUE);                 // 建立代理                 Object proxy = createProxy(                         bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));                 this.proxyTypes.put(cacheKey, proxy.getClass());                 /*                  * 傳回代理對象,此時 IOC 容器輸入 bean,得到 proxy。此時,                  * beanName 對應的 bean 是代理對象,而非原始的 bean                  */                  return proxy;             }             this.advisedBeans.put(cacheKey, Boolean.FALSE);             // specificInterceptors = null,直接傳回 bean             return bean;         }     }           

以上就是 Spring AOP 建立代理對象的入口方法分析,過程比較簡單,這裡簡單總結一下:

  1. 若 bean 是 AOP 基礎設施類型,則直接傳回
  2. 為 bean 查找合适的通知器
  3. 如果通知器數組不為空,則為 bean 生成代理對象,并傳回該對象
  4. 若數組為空,則傳回原始 bean

上面的流程看起來并不複雜,不過不要被表象所迷糊,以上流程不過是冰山一角。

Spring AOP 源碼分析 - 篩選合适的通知器

圖檔來源:無版權圖檔網站 pixabay.com

在本文,以及後續的文章中,我将會對步驟2和步驟3對應的源碼進行分析。在本篇文章先來分析步驟2對應的源碼。

2.2 篩選合适的通知器

在向目标 bean 中織入通知之前,我們先要為 bean 篩選出合适的通知器(通知器持有通知)。如何篩選呢?方式由很多,比如我們可以通過正規表達式比對方法名,當然更多的時候用的是 AspectJ 表達式進行比對。那下面我們就來看一下使用 AspectJ 表達式篩選通知器的過程,如下:

protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {         // 查找合适的通知器         List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);         if (advisors.isEmpty()) {             return DO_NOT_PROXY;         }         return advisors.toArray();     }     protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {         // 查找所有的通知器         List<Advisor> candidateAdvisors = findCandidateAdvisors();         /*          * 篩選可應用在 beanClass 上的 Advisor,通過 ClassFilter 和 MethodMatcher          * 對目标類和方法進行比對          */         List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);         // 拓展操作         extendAdvisors(eligibleAdvisors);         if (!eligibleAdvisors.isEmpty()) {             eligibleAdvisors = sortAdvisors(eligibleAdvisors);         }         return eligibleAdvisors;     }           

如上,Spring 先查詢出所有的通知器,然後再調用 findAdvisorsThatCanApply 對通知器進行篩選。在下面幾節中,我将分别對 findCandidateAdvisors 和 findAdvisorsThatCanApply 兩個方法進行分析,繼續往下看吧。

2.2.1 查找通知器

Spring 提供了兩種配置 AOP 的方式,一種是通過 XML 進行配置,另一種是注解。對于兩種配置方式,Spring 的處理邏輯是不同的。對于 XML 類型的配置,比如下面的配置:

<!-- 目标 bean -->     <bean id="hello" class="xyz.coolblog.aop.Hello"/>     <aop:aspectj-autoproxy/>     <!-- 普通 bean,包含 AOP 切面邏輯 -->     <bean id="aopCode" class="xyz.coolblog.aop.AopCode"/>     <!-- 由 @Aspect 注解修飾的切面類 -->     <bean id="annotationAopCode" class="xyz.coolblog.aop.AnnotationAopCode"/>     <aop:config>         <aop:aspect ref="aopCode">             <aop:pointcut id="helloPointcut" expression="execution(* xyz.coolblog.aop.*.hello*(..))" />             <aop:before method="before" pointcut-ref="helloPointcut"/>             <aop:after method="after" pointcut-ref="helloPointcut"/>         </aop:aspect>     </aop:config>           

Spring 會将上的配置解析為下面的結果:

Spring AOP 源碼分析 - 篩選合适的通知器

如上圖所示,紅框中對應的是普通的 bean 定義,比如

<bean id="hello" .../>、<bean id="annotationAopCode" .../>、<bean id="appCode" .../>

等配置。黃色框中的則是切點的定義,類型為 AspectJExpressionPointcut,對應

<aop:pointcut id="helloPointcut" .../>

配置。那綠色框中的結果對應的是什麼配置呢?目前僅剩下兩個配置沒說,是以對應

<aop:before .../>

<aop:after .../>

配置,類型為 AspectJPointcutAdvisor。這裡請大家注意,由 @Aspect 注解修飾的 AnnotationAopCode 也是普通類型的 bean,該 bean 會在查找通知器的過程中被解析,并被建構為一個或多個 Advisor。

上面講解了 Spring AOP 兩種配置的處理方式,算是為下面的源碼分析做鋪墊。現在鋪墊完畢,我們就來分析一下源碼吧。如下:

public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator {         //...         @Override         protected List<Advisor> findCandidateAdvisors() {             // 調用父類方法從容器中查找所有的通知器             List<Advisor> advisors = super.findCandidateAdvisors();             // 解析 @Aspect 注解,并建構通知器             advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());             return advisors;         }         //...     }           

AnnotationAwareAspectJAutoProxyCreator 覆寫了父類的方法 findCandidateAdvisors,并增加了一步操作,即解析 @Aspect 注解,并建構成通知器。下面我先來分析一下父類中的 findCandidateAdvisors 方法的邏輯,然後再來分析 buildAspectJAdvisors 方法邏的輯。

2.2.1.1 findCandidateAdvisors 方法分析

我們先來看一下 AbstractAdvisorAutoProxyCreator 中 findCandidateAdvisors 方法的定義,如下:

public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator {         private BeanFactoryAdvisorRetrievalHelper advisorRetrievalHelper;         //...         protected List<Advisor> findCandidateAdvisors() {             return this.advisorRetrievalHelper.findAdvisorBeans();         }         //...     }           

從上面的源碼中可以看出,AbstractAdvisorAutoProxyCreator 中的 findCandidateAdvisors 是個空殼方法,所有邏輯封裝在了一個 BeanFactoryAdvisorRetrievalHelper 的 findAdvisorBeans 方法中。這裡大家可以仔細看一下類名 BeanFactoryAdvisorRetrievalHelper 和方法 findAdvisorBeans,兩個名字其實已經描述出他們的職責了。BeanFactoryAdvisorRetrievalHelper 可以了解為

從 bean 容器中擷取 Advisor 的幫助類

,findAdvisorBeans 則可了解為

查找 Advisor 類型的 bean

。是以即使不看 findAdvisorBeans 方法的源碼,我們也可從方法名上推斷出它要做什麼,即從 bean 容器中将 Advisor 類型的 bean 查找出來。下面我來分析一下這個方法的源碼,如下:

public List<Advisor> findAdvisorBeans() {         String[] advisorNames = null;         synchronized (this) {             // cachedAdvisorBeanNames 是 advisor 名稱的緩存             advisorNames = this.cachedAdvisorBeanNames;             /*              * 如果 cachedAdvisorBeanNames 為空,這裡到容器中查找,              * 并設定緩存,後續直接使用緩存即可              */              if (advisorNames == null) {                 // 從容器中查找 Advisor 類型 bean 的名稱                 advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(                         this.beanFactory, Advisor.class, true, false);                 // 設定緩存                 this.cachedAdvisorBeanNames = advisorNames;             }         }         if (advisorNames.length == 0) {             return new LinkedList<Advisor>();         }         List<Advisor> advisors = new LinkedList<Advisor>();         // 周遊 advisorNames         for (String name : advisorNames) {             if (isEligibleBean(name)) {                 // 忽略正在建立中的 advisor bean                 if (this.beanFactory.isCurrentlyInCreation(name)) {                     if (logger.isDebugEnabled()) {                         logger.debug("Skipping currently created advisor '" + name + "'");                     }                 }                 else {                     try {                         /*                          * 調用 getBean 方法從容器中擷取名稱為 name 的 bean,                          * 并将 bean 添加到 advisors 中                          */                          advisors.add(this.beanFactory.getBean(name, Advisor.class));                     }                     catch (BeanCreationException ex) {                         Throwable rootCause = ex.getMostSpecificCause();                         if (rootCause instanceof BeanCurrentlyInCreationException) {                             BeanCreationException bce = (BeanCreationException) rootCause;                             if (this.beanFactory.isCurrentlyInCreation(bce.getBeanName())) {                                 if (logger.isDebugEnabled()) {                                     logger.debug("Skipping advisor '" + name +                                             "' with dependency on currently created bean: " + ex.getMessage());                                 }                                 continue;                             }                         }                         throw ex;                     }                 }             }         }         return advisors;     }           

以上就是從容器中查找 Advisor 類型的 bean 所有的邏輯,代碼雖然有點長,但并不複雜。主要做了兩件事情:

  1. 從容器中查找所有類型為 Advisor 的 bean 對應的名稱
  2. 周遊 advisorNames,并從容器中擷取對應的 bean

看完上面的分析,我們繼續來分析一下 @Aspect 注解的解析過程。

2.2.1.2 buildAspectJAdvisors 方法分析

與上一節的内容相比,解析 @Aspect 注解的過程還是比較複雜的,需要一些耐心去看。下面我們開始分析 buildAspectJAdvisors 方法的源碼,如下:

public List<Advisor> buildAspectJAdvisors() {         List<String> aspectNames = this.aspectBeanNames;         if (aspectNames == null) {             synchronized (this) {                 aspectNames = this.aspectBeanNames;                 if (aspectNames == null) {                     List<Advisor> advisors = new LinkedList<Advisor>();                     aspectNames = new LinkedList<String>();                     // 從容器中擷取所有 bean 的名稱                     String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(                             this.beanFactory, Object.class, true, false);                     // 周遊 beanNames                     for (String beanName : beanNames) {                         if (!isEligibleBean(beanName)) {                             continue;                         }                         // 根據 beanName 擷取 bean 的類型                         Class<?> beanType = this.beanFactory.getType(beanName);                         if (beanType == null) {                             continue;                         }                         // 檢測 beanType 是否包含 Aspect 注解                         if (this.advisorFactory.isAspect(beanType)) {                             aspectNames.add(beanName);                             AspectMetadata amd = new AspectMetadata(beanType, beanName);                             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);                             }                             else {                                 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);                                 advisors.addAll(this.advisorFactory.getAdvisors(factory));                             }                         }                     }                     this.aspectBeanNames = aspectNames;                     return advisors;                 }             }         }         if (aspectNames.isEmpty()) {             return Collections.emptyList();         }         List<Advisor> advisors = new LinkedList<Advisor>();         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;     }           

上面就是 buildAspectJAdvisors 的代碼,看起來比較長。代碼比較多,我們關注重點的方法調用即可。在進行後續的分析前,這裡先對 buildAspectJAdvisors 方法的執行流程做個總結。如下:

  1. 擷取容器中所有 bean 的名稱(beanName)
  2. 周遊上一步擷取到的 bean 名稱數組,并擷取目前 beanName 對應的 bean 類型(beanType)
  3. 根據 beanType 判斷目前 bean 是否是一個的 Aspect 注解類,若不是則不做任何處理
  4. 調用 advisorFactory.getAdvisors 擷取通知器

下面我們來重點分析

advisorFactory.getAdvisors(factory)

這個調用,如下:

public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {         // 擷取 aspectClass 和 aspectName         Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();         String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();         validate(aspectClass);         MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =                 new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);         List<Advisor> advisors = new LinkedList<Advisor>();         // getAdvisorMethods 用于傳回不包含 @Pointcut 注解的方法         for (Method method : getAdvisorMethods(aspectClass)) {             // 為每個方法分别調用 getAdvisor 方法             Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);             if (advisor != null) {                 advisors.add(advisor);             }         }         // If it's a per target aspect, emit the dummy instantiating aspect.         if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {             Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);             advisors.add(0, instantiationAdvisor);         }         // Find introduction fields.         for (Field field : aspectClass.getDeclaredFields()) {             Advisor advisor = getDeclareParentsAdvisor(field);             if (advisor != null) {                 advisors.add(advisor);             }         }         return advisors;     }     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;         }         // 建立 Advisor 實作類         return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,                 this, aspectInstanceFactory, declarationOrderInAspect, aspectName);     }           

如上,getAdvisor 方法包含兩個主要步驟,一個是擷取 AspectJ 表達式切點,另一個是建立 Advisor 實作類。在第二個步驟中,包含一個隐藏步驟 -- 建立 Advice。下面我将按順序依次分析這兩個步驟,先看擷取 AspectJ 表達式切點的過程,如下:

private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) {         // 擷取方法上的 AspectJ 相關注解,包括 @Before,@After 等         AspectJAnnotation<?> aspectJAnnotation =                 AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);         if (aspectJAnnotation == null) {             return null;         }         // 建立一個 AspectJExpressionPointcut 對象         AspectJExpressionPointcut ajexp =                 new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]);         // 設定切點表達式         ajexp.setExpression(aspectJAnnotation.getPointcutExpression());         ajexp.setBeanFactory(this.beanFactory);         return ajexp;     }     protected static AspectJAnnotation<?> findAspectJAnnotationOnMethod(Method method) {         // classesToLookFor 中的元素是大家熟悉的         Class<?>[] classesToLookFor = new Class<?>[] {                 Before.class, Around.class, After.class, AfterReturning.class, AfterThrowing.class, Pointcut.class};         for (Class<?> c : classesToLookFor) {             // 查找注解             AspectJAnnotation<?> foundAnnotation = findAnnotation(method, (Class<Annotation>) c);             if (foundAnnotation != null) {                 return foundAnnotation;             }         }         return null;     }           

擷取切點的過程并不複雜,不過需要注意的是,目前擷取到的切點可能還隻是個半成品,需要再次處理一下才行。比如下面的代碼:

@Aspect     public class AnnotationAopCode {         @Pointcut("execution(* xyz.coolblog.aop.*.world*(..))")         public void pointcut() {}         @Before("pointcut()")         public void before() {             System.out.println("AnnotationAopCode`s before");         }     }           

@Before 注解中的表達式是

pointcut()

,也就是說 ajexp 設定的表達式隻是一個中間值,不是最終值,即

execution(* xyz.coolblog.aop.*.world*(..))

。是以後續還需要将 ajexp 中的表達式進行轉換,關于這個轉換的過程,我就不說了。有點複雜,我暫時沒怎麼看懂。

說完切點的擷取過程,下面再來看看 Advisor 實作類的建立過程。如下:

public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut,             Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory,             MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {         this.declaredPointcut = declaredPointcut;         this.declaringClass = aspectJAdviceMethod.getDeclaringClass();         this.methodName = aspectJAdviceMethod.getName();         this.parameterTypes = aspectJAdviceMethod.getParameterTypes();         this.aspectJAdviceMethod = aspectJAdviceMethod;         this.aspectJAdvisorFactory = aspectJAdvisorFactory;         this.aspectInstanceFactory = aspectInstanceFactory;         this.declarationOrder = declarationOrder;         this.aspectName = aspectName;         if (aspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {             Pointcut preInstantiationPointcut = Pointcuts.union(                     aspectInstanceFactory.getAspectMetadata().getPerClausePointcut(), this.declaredPointcut);             this.pointcut = new PerTargetInstantiationModelPointcut(                     this.declaredPointcut, preInstantiationPointcut, aspectInstanceFactory);             this.lazy = true;         }         else {             this.pointcut = this.declaredPointcut;             this.lazy = false;             // 按照注解解析 Advice             this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);         }     }           

上面是 InstantiationModelAwarePointcutAdvisorImpl 的構造方法,不過我們無需太關心這個方法中的一些初始化邏輯。我們把目光移到構造方法的最後一行代碼中,即 instantiateAdvice(this.declaredPointcut),這個方法用于建立通知 Advice。在上一篇文章中我已經說過,通知器 Advisor 是通知 Advice 的持有者,是以在 Advisor 實作類的構造方法中建立通知也是合适的。那下面我們就來看看建構通知的過程是怎樣的,如下:

private Advice instantiateAdvice(AspectJExpressionPointcut pcut) {         return this.aspectJAdvisorFactory.getAdvice(this.aspectJAdviceMethod, pcut,                 this.aspectInstanceFactory, this.declarationOrder, this.aspectName);     }     public Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut,             MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) {         Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();         validate(candidateAspectClass);         // 擷取 Advice 注解         AspectJAnnotation<?> aspectJAnnotation =                 AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod);         if (aspectJAnnotation == null) {             return null;         }         if (!isAspect(candidateAspectClass)) {             throw new AopConfigException("Advice must be declared inside an aspect type: " +                     "Offending method '" + candidateAdviceMethod + "' in class [" +                     candidateAspectClass.getName() + "]");         }         if (logger.isDebugEnabled()) {             logger.debug("Found AspectJ method: " + candidateAdviceMethod);         }         AbstractAspectJAdvice springAdvice;         // 按照注解類型生成相應的 Advice 實作類         switch (aspectJAnnotation.getAnnotationType()) {             case AtBefore:    // @Before -> AspectJMethodBeforeAdvice                 springAdvice = new AspectJMethodBeforeAdvice(                         candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);                 break;             case AtAfter:    // @After -> AspectJAfterAdvice                 springAdvice = new AspectJAfterAdvice(                         candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);                 break;             case AtAfterReturning:    // @AfterReturning -> AspectJAfterAdvice                 springAdvice = new AspectJAfterReturningAdvice(                         candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);                 AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();                 if (StringUtils.hasText(afterReturningAnnotation.returning())) {                     springAdvice.setReturningName(afterReturningAnnotation.returning());                 }                 break;             case AtAfterThrowing:    // @AfterThrowing -> AspectJAfterThrowingAdvice                 springAdvice = new AspectJAfterThrowingAdvice(                         candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);                 AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();                 if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {                     springAdvice.setThrowingName(afterThrowingAnnotation.throwing());                 }                 break;             case AtAround:    // @Around -> AspectJAroundAdvice                 springAdvice = new AspectJAroundAdvice(                         candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);                 break;             /*              * 什麼都不做,直接傳回 null。從整個方法的調用棧來看,              * 并不會出現注解類型為 AtPointcut 的情況              */              case AtPointcut:                     if (logger.isDebugEnabled()) {                     logger.debug("Processing pointcut '" + candidateAdviceMethod.getName() + "'");                 }                 return null;             default:                 throw new UnsupportedOperationException(                         "Unsupported advice type on method: " + candidateAdviceMethod);         }         springAdvice.setAspectName(aspectName);         springAdvice.setDeclarationOrder(declarationOrder);         /*          * 擷取方法的參數清單名稱,比如方法 int sum(int numX, int numY),           * getParameterNames(sum) 得到 argNames = [numX, numY]          */         String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod);         if (argNames != null) {             // 設定參數名             springAdvice.setArgumentNamesFromStringArray(argNames);         }         springAdvice.calculateArgumentBindings();         return springAdvice;     }           

上面的代碼邏輯不是很複雜,主要的邏輯就是根據注解類型生成與之對應的通知對象。下面來總結一下擷取通知器(getAdvisors)整個過程的邏輯,如下:

  1. 從目标 bean 中擷取不包含 Pointcut 注解的方法清單
  2. 周遊上一步擷取的方法清單,并調用 getAdvisor 擷取目前方法對應的 Advisor
  3. 建立 AspectJExpressionPointcut 對象,并從方法中的注解中擷取表達式,最後設定到切點對象中
  4. 建立 Advisor 實作類對象 InstantiationModelAwarePointcutAdvisorImpl
  5. 調用 instantiateAdvice 方法建構通知
  6. 調用 getAdvice 方法,并根據注解類型建立相應的通知

如上所示,上面的步驟做了一定的簡化。總的來說,擷取通知器的過程還是比較複雜的,并不是很容易看懂。大家在閱讀的過程中,還要寫一些測試代碼進行調試才行。調試的過程中,一些不關心的調用就别跟進去了,不然會陷入很深的調用棧中,影響對源碼主流程的了解。

現在,大家知道了通知是怎麼建立的。那我們難道不要去看看這些通知的實作源碼嗎?顯然,我們應該看一下。那接下裡,我們一起來分析一下 AspectJMethodBeforeAdvice,也就是 @Before 注解對應的通知實作類。看看它的邏輯是什麼樣的。

2.2.1.3 AspectJMethodBeforeAdvice 分析
public class AspectJMethodBeforeAdvice extends AbstractAspectJAdvice implements MethodBeforeAdvice {         public AspectJMethodBeforeAdvice(                 Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {             super(aspectJBeforeAdviceMethod, pointcut, aif);         }         @Override         public void before(Method method, Object[] args, Object target) throws Throwable {             // 調用通知方法             invokeAdviceMethod(getJoinPointMatch(), null, null);         }         @Override         public boolean isBeforeAdvice() {             return true;         }         @Override         public boolean isAfterAdvice() {             return false;         }     }     protected Object invokeAdviceMethod(JoinPointMatch jpMatch, Object returnValue, Throwable ex) throws Throwable {         // 調用通知方法,并向其傳遞參數         return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));     }     protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {         Object[] actualArgs = args;         if (this.aspectJAdviceMethod.getParameterTypes().length == 0) {             actualArgs = null;         }         try {             ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);             // 通過反射調用通知方法             return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);         }         catch (IllegalArgumentException ex) {             throw new AopInvocationException("Mismatch on arguments to advice method [" +                     this.aspectJAdviceMethod + "]; pointcut expression [" +                     this.pointcut.getPointcutExpression() + "]", ex);         }         catch (InvocationTargetException ex) {             throw ex.getTargetException();         }     }           

如上,AspectJMethodBeforeAdvice 的源碼比較簡單,這裡我們僅關注 before 方法。這個方法調用了父類中的 invokeAdviceMethod,然後 invokeAdviceMethod 在調用 invokeAdviceMethodWithGivenArgs,最後在 invokeAdviceMethodWithGivenArgs 通過反射執行通知方法。是不是很簡單?

關于 AspectJMethodBeforeAdvice 就簡單介紹到這裡吧,至于剩下的幾種實作,大家可以自己去看看。好了,關于 AspectJMethodBeforeAdvice 的源碼分析,就分析到這裡了。我們繼續往下看吧。

2.2.2 篩選合适的通知器

查找出所有的通知器,整個流程還沒算完,接下來我們還要對這些通知器進行篩選。适合應用在目前 bean 上的通知器留下,不适合的就讓它自生自滅吧。那下面我們來分析一下通知器篩選的過程,如下:

protected List<Advisor> findAdvisorsThatCanApply(             List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {         ProxyCreationContext.setCurrentProxiedBeanName(beanName);         try {             // 調用重載方法             return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);         }         finally {             ProxyCreationContext.setCurrentProxiedBeanName(null);         }     }     public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {         if (candidateAdvisors.isEmpty()) {             return candidateAdvisors;         }         List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();         for (Advisor candidate : candidateAdvisors) {             // 篩選 IntroductionAdvisor 類型的通知器             if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {                 eligibleAdvisors.add(candidate);             }         }         boolean hasIntroductions = !eligibleAdvisors.isEmpty();         for (Advisor candidate : candidateAdvisors) {             if (candidate instanceof IntroductionAdvisor) {                 continue;             }             // 篩選普通類型的通知器             if (canApply(candidate, clazz, hasIntroductions)) {                 eligibleAdvisors.add(candidate);             }         }         return eligibleAdvisors;     }     public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {         if (advisor instanceof IntroductionAdvisor) {             /*              * 從通知器中擷取類型過濾器 ClassFilter,并調用 matchers 方法進行比對。              * ClassFilter 接口的實作類 AspectJExpressionPointcut 為例,該類的              * 比對工作由 AspectJ 表達式解析器負責,具體比對細節這個就沒法分析了,我              * AspectJ 表達式的工作流程不是很熟              */             return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);         }         else if (advisor instanceof PointcutAdvisor) {             PointcutAdvisor pca = (PointcutAdvisor) advisor;             // 對于普通類型的通知器,這裡繼續調用重載方法進行篩選             return canApply(pca.getPointcut(), targetClass, hasIntroductions);         }         else {             return true;         }     }     public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {         Assert.notNull(pc, "Pointcut must not be null");         // 使用 ClassFilter 比對 class         if (!pc.getClassFilter().matches(targetClass)) {             return false;         }         MethodMatcher methodMatcher = pc.getMethodMatcher();         if (methodMatcher == MethodMatcher.TRUE) {             return true;         }         IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;         if (methodMatcher instanceof IntroductionAwareMethodMatcher) {             introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;         }         /*          * 查找目前類及其父類(以及父類的父類等等)所實作的接口,由于接口中的方法是 public,          * 是以目前類可以繼承其父類,和父類的父類中所有的接口方法          */          Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));         classes.add(targetClass);         for (Class<?> clazz : classes) {             // 擷取目前類的方法清單,包括從父類中繼承的方法             Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);             for (Method method : methods) {                 // 使用 methodMatcher 比對方法,比對成功即可立即傳回                 if ((introductionAwareMethodMatcher != null &&                         introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||                         methodMatcher.matches(method, targetClass)) {                     return true;                 }             }         }         return false;     }           

以上是通知器篩選的過程,篩選的工作主要由 ClassFilter 和 MethodMatcher 完成。關于 ClassFilter 和 MethodMatcher 我在導讀一文中已經說過了,這裡再說一遍吧。在 AOP 中,切點 Pointcut 是用來比對連接配接點的,以 AspectJExpressionPointcut 類型的切點為例。該類型切點實作了ClassFilter 和 MethodMatcher 接口,比對的工作則是由 AspectJ 表達式解析器複雜。除了使用 AspectJ 表達式進行比對,Spring 還提供了基于正規表達式的切點類,以及更簡單的根據方法名進行比對的切點類。大家有興趣的話,可以自己去了解一下,這裡就不多說了。

在完成通知器的查找和篩選過程後,還需要進行最後一步處理 -- 對通知器清單進行拓展。怎麼拓展呢?我們一起到下一節中一探究竟吧。

2.2.3 拓展篩選出通知器清單

拓展方法 extendAdvisors 做的事情并不多,邏輯也比較簡單。我們一起來看一下,如下:

protected void extendAdvisors(List<Advisor> candidateAdvisors) {         AspectJProxyUtils.makeAdvisorChainAspectJCapableIfNecessary(candidateAdvisors);     }     public static boolean makeAdvisorChainAspectJCapableIfNecessary(List<Advisor> advisors) {         // 如果通知器清單是一個空清單,則啥都不做         if (!advisors.isEmpty()) {             boolean foundAspectJAdvice = false;             /*              * 下面的 for 循環用于檢測 advisors 清單中是否存在               * AspectJ 類型的 Advisor 或 Advice              */             for (Advisor advisor : advisors) {                 if (isAspectJAdvice(advisor)) {                     foundAspectJAdvice = true;                 }             }             /*              * 向 advisors 清單的首部添加 DefaultPointcutAdvisor,              * 至于為什麼這樣做,我會在後續的文章中進行說明              */             if (foundAspectJAdvice && !advisors.contains(ExposeInvocationInterceptor.ADVISOR)) {                 advisors.add(0, ExposeInvocationInterceptor.ADVISOR);                 return true;             }         }         return false;     }     private static boolean isAspectJAdvice(Advisor advisor) {         return (advisor instanceof InstantiationModelAwarePointcutAdvisor ||                 advisor.getAdvice() instanceof AbstractAspectJAdvice ||                 (advisor instanceof PointcutAdvisor &&                          ((PointcutAdvisor) advisor).getPointcut() instanceof AspectJExpressionPointcut));     }           

如上,上面的代碼比較少,也不複雜。由源碼可以看出 extendAdvisors 是一個空殼方法,除了調用makeAdvisorChainAspectJCapableIfNecessary,該方法沒有其他更多的邏輯了。至于 makeAdvisorChainAspectJCapableIfNecessary 這個方法,該方法主要的目的是向通知器清單首部添加 DefaultPointcutAdvisor 類型的通知器,也就是 ExposeInvocationInterceptor.ADVISOR。至于添加此種類型通知器的意圖,我會在後面文章裡說明,這裡不便展開。關于 extendAdvisors 這個方法,這裡就先說到這了。

3.總結

到這裡,本篇文章就接近尾聲了。這篇文章有點長,大家看下來應該蠻累的吧。由于個人能力問題,暫時未能做到對本篇文章中所貼的源碼進行更為細緻的分析,有點遺憾。不過好在目前把主邏輯分析弄清楚了,總的來說還算合格吧,給個及格分。大家在閱讀的過程中,如果發現文章中出現錯誤或不妥之處,這裡還請指明,也請多多指教。大家共同學習,一起進步。

好了,本篇文章就到這裡了。謝謝大家的閱讀。

參考

  • 《Spring 源碼深度解析》- 郝佳

附錄:Spring 源碼分析文章清單

Ⅰ. IOC

更新時間 标題
2018-05-30 Spring IOC 容器源碼分析系列文章導讀
2018-06-01 Spring IOC 容器源碼分析 - 擷取單例 bean
2018-06-04 Spring IOC 容器源碼分析 - 建立單例 bean 的過程
2018-06-06 Spring IOC 容器源碼分析 - 建立原始 bean 對象
2018-06-08 Spring IOC 容器源碼分析 - 循環依賴的解決辦法
2018-06-11 Spring IOC 容器源碼分析 - 填充屬性到 bean 原始對象
Spring IOC 容器源碼分析 - 餘下的初始化工作

Ⅱ. AOP

2018-06-17 Spring AOP 源碼分析系列文章導讀
2018-06-20 Spring AOP 源碼分析 - 篩選合适的通知器
Spring AOP 源碼分析 - 建立代理對象
2018-06-22 Spring AOP 源碼分析 - 攔截器鍊的執行過程

Ⅲ. MVC

2018-06-29 Spring MVC 原理探秘 - 一個請求的旅行過程
2018-06-30 Spring MVC 原理探秘 - 容器的建立過程

本文在知識共享許可協定 4.0 下釋出,轉載需在明顯位置處注明出處

作者:田小波

本文同步釋出在我的個人部落格:http://www.tianxiaobo.com

Spring AOP 源碼分析 - 篩選合适的通知器

本作品采用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協定進行許可。