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 建立代理對象的入口方法分析,過程比較簡單,這裡簡單總結一下:
- 若 bean 是 AOP 基礎設施類型,則直接傳回
- 為 bean 查找合适的通知器
- 如果通知器數組不為空,則為 bean 生成代理對象,并傳回該對象
- 若數組為空,則傳回原始 bean
上面的流程看起來并不複雜,不過不要被表象所迷糊,以上流程不過是冰山一角。

圖檔來源:無版權圖檔網站 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 會将上的配置解析為下面的結果:
如上圖所示,紅框中對應的是普通的 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 所有的邏輯,代碼雖然有點長,但并不複雜。主要做了兩件事情:
- 從容器中查找所有類型為 Advisor 的 bean 對應的名稱
- 周遊 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 方法的執行流程做個總結。如下:
- 擷取容器中所有 bean 的名稱(beanName)
- 周遊上一步擷取到的 bean 名稱數組,并擷取目前 beanName 對應的 bean 類型(beanType)
- 根據 beanType 判斷目前 bean 是否是一個的 Aspect 注解類,若不是則不做任何處理
- 調用 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)整個過程的邏輯,如下:
- 從目标 bean 中擷取不包含 Pointcut 注解的方法清單
- 周遊上一步擷取的方法清單,并調用 getAdvisor 擷取目前方法對應的 Advisor
- 建立 AspectJExpressionPointcut 對象,并從方法中的注解中擷取表達式,最後設定到切點對象中
- 建立 Advisor 實作類對象 InstantiationModelAwarePointcutAdvisorImpl
- 調用 instantiateAdvice 方法建構通知
- 調用 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
本作品采用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協定進行許可。