javascript
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 中織入通知。下面我們就來看看相關源碼,如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupportimplements 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,直接返回 beanreturn bean;} } |
以上就是 Spring AOP 創建代理對象的入口方法分析,過程比較簡單,這里簡單總結一下:
上面的流程看起來并不復雜,不過不要被表象所迷糊,以上流程不過是冰山一角。
圖片來源:無版權圖片網站?pixabay.com
在本文,以及后續的文章中,我將會對步驟2和步驟3對應的源碼進行分析。在本篇文章先來分析步驟2對應的源碼。
?2.2 篩選合適的通知器
在向目標 bean 中織入通知之前,我們先要為 bean 篩選出合適的通知器(通知器持有通知)。如何篩選呢?方式由很多,比如我們可以通過正則表達式匹配方法名,當然更多的時候用的是 AspectJ 表達式進行匹配。那下面我們就來看一下使用 AspectJ 表達式篩選通知器的過程,如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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 類型的配置,比如下面的配置:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <!-- 目標 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 兩種配置的處理方式,算是為下面的源碼分析做鋪墊。現在鋪墊完畢,我們就來分析一下源碼吧。如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator {//...@Overrideprotected 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 方法的定義,如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 | 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 查找出來。下面我來分析一下這個方法的源碼,如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | 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>();// 遍歷 advisorNamesfor (String name : advisorNames) {if (isEligibleBean(name)) {// 忽略正在創建中的 advisor beanif (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 所有的邏輯,代碼雖然有點長,但并不復雜。主要做了兩件事情:
看完上面的分析,我們繼續來分析一下 @Aspect 注解的解析過程。
?2.2.1.2 buildAspectJAdvisors 方法分析
與上一節的內容相比,解析 @Aspect 注解的過程還是比較復雜的,需要一些耐心去看。下面我們開始分析 buildAspectJAdvisors 方法的源碼,如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | 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);// 遍歷 beanNamesfor (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 方法的執行流程做個總結。如下:
下面我們來重點分析advisorFactory.getAdvisors(factory)這個調用,如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {// 獲取 aspectClass 和 aspectNameClass<?> 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 表達式切點的過程,如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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; } |
獲取切點的過程并不復雜,不過需要注意的是,目前獲取到的切點可能還只是個半成品,需要再次處理一下才行。比如下面的代碼:
| 1 2 3 4 5 6 7 8 9 10 11 | @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 實現類的創建過程。如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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;// 按照注解解析 Advicethis.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);} } |
上面是 InstantiationModelAwarePointcutAdvisorImpl 的構造方法,不過我們無需太關心這個方法中的一些初始化邏輯。我們把目光移到構造方法的最后一行代碼中,即 instantiateAdvice(this.declaredPointcut),這個方法用于創建通知 Advice。在上一篇文章中我已經說過,通知器 Advisor 是通知 Advice 的持有者,所以在 Advisor 實現類的構造方法中創建通知也是合適的。那下面我們就來看看構建通知的過程是怎樣的,如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | 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 -> AspectJMethodBeforeAdvicespringAdvice = new AspectJMethodBeforeAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtAfter: // @After -> AspectJAfterAdvicespringAdvice = new AspectJAfterAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);break;case AtAfterReturning: // @AfterReturning -> AspectJAfterAdvicespringAdvice = new AspectJAfterReturningAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation();if (StringUtils.hasText(afterReturningAnnotation.returning())) {springAdvice.setReturningName(afterReturningAnnotation.returning());}break;case AtAfterThrowing: // @AfterThrowing -> AspectJAfterThrowingAdvicespringAdvice = new AspectJAfterThrowingAdvice(candidateAdviceMethod, expressionPointcut, aspectInstanceFactory);AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation();if (StringUtils.hasText(afterThrowingAnnotation.throwing())) {springAdvice.setThrowingName(afterThrowingAnnotation.throwing());}break;case AtAround: // @Around -> AspectJAroundAdvicespringAdvice = 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)整個過程的邏輯,如下:
如上所示,上面的步驟做了一定的簡化。總的來說,獲取通知器的過程還是比較復雜的,并不是很容易看懂。大家在閱讀的過程中,還要寫一些測試代碼進行調試才行。調試的過程中,一些不關心的調用就別跟進去了,不然會陷入很深的調用棧中,影響對源碼主流程的理解。
現在,大家知道了通知是怎么創建的。那我們難道不要去看看這些通知的實現源碼嗎?顯然,我們應該看一下。那接下里,我們一起來分析一下 AspectJMethodBeforeAdvice,也就是 @Before 注解對應的通知實現類。看看它的邏輯是什么樣的。
?2.2.1.3 AspectJMethodBeforeAdvice 分析
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | public class AspectJMethodBeforeAdvice extends AbstractAspectJAdvice implements MethodBeforeAdvice {public AspectJMethodBeforeAdvice(Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {super(aspectJBeforeAdviceMethod, pointcut, aif);}@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {// 調用通知方法invokeAdviceMethod(getJoinPointMatch(), null, null);}@Overridepublic boolean isBeforeAdvice() {return true;}@Overridepublic 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 上的通知器留下,不適合的就讓它自生自滅吧。那下面我們來分析一下通知器篩選的過程,如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | 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 匹配 classif (!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 做的事情并不多,邏輯也比較簡單。我們一起來看一下,如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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 原始對象 |
| 2018-06-11 | Spring IOC 容器源碼分析 - 余下的初始化工作 |
?Ⅱ. AOP
| 2018-06-17 | Spring AOP 源碼分析系列文章導讀 |
| 2018-06-20 | Spring AOP 源碼分析 - 篩選合適的通知器 |
| 2018-06-20 | Spring AOP 源碼分析 - 創建代理對象 |
| 2018-06-22 | Spring AOP 源碼分析 - 攔截器鏈的執行過程 |
?Ⅲ. MVC
| 2018-06-29 | Spring MVC 原理探秘 - 一個請求的旅行過程 |
| 2018-06-30 | Spring MVC 原理探秘 - 容器的創建過程 |
- 本文鏈接:?https://www.tianxiaobo.com/2018/06/20/Spring-AOP-源碼分析-篩選合適的通知器/
from:?http://www.tianxiaobo.com/2018/06/20/Spring-AOP-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E7%AD%9B%E9%80%89%E5%90%88%E9%80%82%E7%9A%84%E9%80%9A%E7%9F%A5%E5%99%A8/?
總結
以上是生活随笔為你收集整理的Spring AOP 源码分析 - 筛选合适的通知器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring IOC 容器源码分析系列文
- 下一篇: Spring AOP 源码分析 - 创建