2019獨角獸企業重金招聘Python工程師標準>>>
???????在上文Spring Aop之Target Source詳解中,我們講解了Spring是如何通過封裝Target Source
來達到對最終獲取的目標bean進行封裝的目的。其中我們講解到,Spring Aop對目標bean進行代理是通過AnnotationAwareAspectJAutoProxyCreator.postProcessAfterInitialization()
進行的,Spring Aop的代理主要分為三個步驟:獲取所有的Advisor,過濾可應用到當前bean的Adivsor和使用Advisor為當前bean生成代理對象。本文主要對這三步中的第一步獲取所有的Advisor進行講解。
1. 骨架方法
???????首先我們看看postProcessAfterInitialization()
方法的實現:
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {if (bean != null) {// 獲取當前bean的key:如果beanName不為空,則以beanName為key,如果為FactoryBean類型,// 前面還會添加&符號,如果beanName為空,則以當前bean對應的class為keyObject cacheKey = getCacheKey(bean.getClass(), beanName);// 判斷當前bean是否正在被代理,如果正在被代理則不進行封裝if (!this.earlyProxyReferences.contains(cacheKey)) {// 對當前bean進行封裝return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
}
???????從上述代碼可以看出,對目標bean的封裝是主要是通過wrapIfNecessary()
方法進行的,該方法就是Spring對目標bean進行代理的骨架方法。如下是該方法的實現:
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// 判斷當前bean是否在TargetSource緩存中存在,如果存在,則直接返回當前bean。這里進行如此判斷的// 原因是在上文中,我們講解了如何通過自己聲明的TargetSource進行目標bean的封裝,在封裝之后其實// 就已經對封裝之后的bean進行了代理,并且添加到了targetSourcedBeans緩存中。因而這里判斷得到// 當前緩存中已經存在當前bean,則說明該bean已經被代理過,這樣就可以直接返回當前bean。if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}// 這里advisedBeans緩存了已經進行了代理的bean,如果緩存中存在,則可以直接返回if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}// 這里isInfrastructureClass()用于判斷當前bean是否為Spring系統自帶的bean,自帶的bean是// 不用進行代理的;shouldSkip()則用于判斷當前bean是否應該被略過if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {// 對當前bean進行緩存this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// 獲取當前bean的Advices和AdvisorsObject[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);if (specificInterceptors != DO_NOT_PROXY) {// 對當前bean的代理狀態進行緩存this.advisedBeans.put(cacheKey, Boolean.TRUE);// 根據獲取到的Advices和Advisors為當前bean生成代理對象Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));// 緩存生成的代理bean的類型,并且返回生成的代理beanthis.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}
???????在上述骨架方法中,Spring主要進行了三件事:
- 判斷當前bean是否已經生成過代理對象,或者是否是應該被略過的對象,是則直接返回,否則進行下一步;
- 獲取當前bean的Advisors和Advices,如果當前bean不需要代理,則返回DO_NOT_PROXY;
- 通過生成的Advisors和Advices為目標bean生成代理對象。
???????關于上述骨架方法,這里需要說明兩個點:
shouldSkip()
方法中對當前bean判斷是否應該略過時,其主要做了兩件事:a. 為當前bean生成需要代理的Advisors;b. 判斷生成的Advisor是否為AspectJPointcutAdvisor
類型。因而實際上判斷略過的過程就是判斷是否為AspectJPointcutAdvisor
,判斷這個類的原因在于Spring Aop的切面和切點的生成也可以通過在xml文件中使用<aop:config/>
標簽進行。這個標簽最終解析得到的Adivsor類型就是``AspectJPointcutAdvisor類型的,因為其在解析
aop:config/的時候就已經生成了Advisor,因而這里需要對這種類型的Advisor進行略過。這里
aop:config/`也是一種自定義標簽,關于其解析過程,讀者可以參照本人前面的博文自行閱讀器源碼;getAdvicesAndAdvisorsForBean()
方法就其名稱而言是獲取Advisors和Advices,但實際上其返回值是一個Advisor的數組。Spring Aop在為目標bean獲取需要進行代理的切面邏輯的時候最終得到的是Advisor,這里Advice表示的是每個切面邏輯中使用@Before
、@After
和@Around
等需要織入的代理方法。因為每個代理方法都表示一個Advice,并且每個代理方法最終都會生成一個Advisor,因而Advice和Advisor就本質而言其實沒有太大的區別。Advice表示需要織入的切面邏輯,而Advisor則表示將切面邏輯進行封裝之后的織入者。
2. 切面的生成
???????雖然在shouldSkip()
方法中會為當前bean生成Advisor,但是在getAdvicesAndAdvisorsForBean()
中也還是會獲取一次,只不過在第一次生成的時候會將得到的Advisor都進行緩存,因而第二次獲取時可以直接從緩存中獲取。我們這里還是以getAdvicesAndAdvisorsForBean()
方法為準來進行講解。如下是該方法的源碼:
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {// 為目標bean生成AdvisorList<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);if (advisors.isEmpty()) {return DO_NOT_PROXY;}return advisors.toArray();
}
???????我們繼續看findEligibleAdvisors()
方法:
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {// 將當前系統中所有的切面類的切面邏輯進行封裝,從而得到目標AdvisorList<Advisor> candidateAdvisors = findCandidateAdvisors();// 對獲取到的所有Advisor進行判斷,看其切面定義是否可以應用到當前bean,從而得到最終需要應用的AdvisorList<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);// 提供的hook方法,用于對目標Advisor進行擴展extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {// 對需要代理的Advisor按照一定的規則進行排序eligibleAdvisors = sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;
}
???????在上述方法中,Spring Aop首先獲取到了系統中所有的切面邏輯,并將其封裝為了Advisor對象,然后通過遍歷Advisor判斷哪些Advisor是可以應用到當前bean的,最后將需要織入的Advisor返回。這里我們看看findCandidateAdvisors()
的源碼:
protected List<Advisor> findCandidateAdvisors() {// 找到系統中實現了Advisor接口的beanList<Advisor> advisors = super.findCandidateAdvisors();if (this.aspectJAdvisorsBuilder != null) {// 找到系統中使用@Aspect標注的bean,并且找到該bean中使用@Before,@After等標注的方法,// 將這些方法封裝為一個個Advisoradvisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());}return advisors;
}
???????可以看到,findCandidateAdvisors()
主要是通過兩種方式獲取切面邏輯,一種是在系統中找到實現了Advisor接口的所有類,另一種是在找到系統中使用@Aspect
標注的類,并將其切面邏輯封裝為Advisor,這兩種Advisor都有可能是我們需要進行織入的切面邏輯。這里super.findCandidateAdvisors()
方法最終調用的是BeanFactoryAdvisorRetrievalHelper.findAdvisorBeans()
方法,我們首先看看該方法的實現:
public List<Advisor> findAdvisorBeans() {String[] advisorNames = null;synchronized (this) {advisorNames = this.cachedAdvisorBeanNames;if (advisorNames == null) {// 獲取當前BeanFactory中所有實現了Advisor接口的bean的名稱advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Advisor.class, true, false);this.cachedAdvisorBeanNames = advisorNames;}}if (advisorNames.length == 0) {return new LinkedList<>();}// 對獲取到的實現Advisor接口的bean的名稱進行遍歷List<Advisor> advisors = new LinkedList<>();for (String name : advisorNames) {// isEligibleBean()是提供的一個hook方法,用于子類對Advisor進行過濾,這里默認返回值都是trueif (isEligibleBean(name)) {// 如果當前bean還在創建過程中,則略過,其創建完成之后會為其判斷是否需要織入切面邏輯if (this.beanFactory.isCurrentlyInCreation(name)) {if (logger.isDebugEnabled()) {logger.debug("Skipping currently created advisor '" + name + "'");}} else {try {// 將當前bean添加到結果中advisors.add(this.beanFactory.getBean(name, Advisor.class));} catch (BeanCreationException ex) {// 對獲取過程中產生的異常進行封裝Throwable rootCause = ex.getMostSpecificCause();if (rootCause instanceof BeanCurrentlyInCreationException) {BeanCreationException bce = (BeanCreationException) rootCause;String bceBeanName = bce.getBeanName();if (bceBeanName != null && this.beanFactory.isCurrentlyInCreation(bceBeanName)) {if (logger.isDebugEnabled()) {logger.debug("Skipping advisor '" + name + "' with dependency on currently created bean: " + ex.getMessage());}continue;}}throw ex;}}}}return advisors;
}
???????這里findAdvisorBeans()
方法邏輯其實非常簡單,其主要是在BeanFactory中找打實現了Advisor接口的類,然后通過hook方法判斷子類是否需要對Advisor進行過濾,最后將過濾之后的Advisor返回。
???????接下來我們看看BeanFactoryAspectJAdvisorsBuilder.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<>();aspectNames = new LinkedList<>();// 獲取當前BeanFactory中所有的beanString[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this.beanFactory, Object.class, true, false);// 對獲取到的所有bean進行循環遍歷for (String beanName : beanNames) {// 判斷當前bean是否為子類定制的需要過濾的beanif (!isEligibleBean(beanName)) {continue;}// 獲取當前遍歷的bean的Class類型Class<?> beanType = this.beanFactory.getType(beanName);if (beanType == null) {continue;}// 判斷當前bean是否使用了@Aspect注解進行標注if (this.advisorFactory.isAspect(beanType)) {aspectNames.add(beanName);// 對于使用了@Aspect注解標注的bean,將其封裝為一個AspectMetadata類型。// 這里在封裝的過程中會解析@Aspect注解上的參數指定的切面類型,如perthis// 和pertarget等。這些被解析的注解都會被封裝到其perClausePointcut屬性中AspectMetadata amd = new AspectMetadata(beanType, beanName);// 判斷@Aspect注解中標注的是否為singleton類型,默認的切面類都是singleton// 類型if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {// 將BeanFactory和當前bean封裝為MetadataAwareAspect-// InstanceFactory對象,這里會再次將@Aspect注解中的參數都封裝// 為一個AspectMetadata,并且保存在該factory中MetadataAwareAspectInstanceFactory factory =new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);// 通過封裝的bean獲取其Advice,如@Before,@After等等,并且將這些// Advice都解析并且封裝為一個個的AdvisorList<Advisor> classAdvisors this.advisorFactory.getAdvisors(factory);// 如果切面類是singleton類型,則將解析得到的Advisor進行緩存,// 否則將當前的factory進行緩存,以便再次獲取時可以通過factory直接獲取if (this.beanFactory.isSingleton(beanName)) {this.advisorsCache.put(beanName, classAdvisors);} else {this.aspectFactoryCache.put(beanName, factory);}advisors.addAll(classAdvisors);} else {// 如果@Aspect注解標注的是perthis和pertarget類型,說明當前切面// 不可能是單例的,因而這里判斷其如果是單例的則拋出異常if (this.beanFactory.isSingleton(beanName)) {throw new IllegalArgumentException("Bean with name '" + beanName + "' is a singleton, but aspect "+ "instantiation model is not singleton");}// 將當前BeanFactory和切面bean封裝為一個多例類型的FactoryMetadataAwareAspectInstanceFactory factory =new PrototypeAspectInstanceFactory(this.beanFactory, beanName);// 對當前bean和factory進行緩存this.aspectFactoryCache.put(beanName, factory);advisors.addAll(this.advisorFactory.getAdvisors(factory));}}}this.aspectBeanNames = aspectNames;return advisors;}}}if (aspectNames.isEmpty()) {return Collections.emptyList();}// 通過所有的aspectNames在緩存中獲取切面對應的Advisor,這里如果是單例的,則直接從advisorsCache// 獲取,如果是多例類型的,則通過MetadataAwareAspectInstanceFactory立即生成一個List<Advisor> advisors = new LinkedList<>();for (String aspectName : aspectNames) {List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);// 如果是單例的Advisor bean,則直接添加到返回值列表中if (cachedAdvisors != null) {advisors.addAll(cachedAdvisors);} else {// 如果是多例的Advisor bean,則通過MetadataAwareAspectInstanceFactory生成MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);advisors.addAll(this.advisorFactory.getAdvisors(factory));}}return advisors;
}
???????對于通過@Aspect注解獲取切面邏輯的方法,這里的邏輯也比較簡單,Spring首先會過濾得到BeanFactory中所有標注有@Aspect的類,然后對該注解參數進行解析,判斷其環繞的目標bean是單例的還是多例的。如果是單例的,則直接緩存到advisorsCache中;如果是多例的,則將生成Advisor的factory進行緩存,以便每次獲取時都通過factory獲取一個新的Advisor。上述方法中主要是對@Aspect注解進行了解析,我們前面講過,Spring Aop的Advisor對應的是Advice,而每個Advice都是對應的一個@Before或者@After等標注方法的切面邏輯,這里對這些切面邏輯的解析過程就在上述的advisorFactory.getAdvisors(factory)
方法調用中。這里我們看看該方法的實現:
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {// 獲取當前切面類的Class類型Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();// 獲取當前切面bean的名稱String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();// 對當前切面bean進行校驗,主要是判斷其切點是否為perflow或者是percflowbelow,Spring暫時不支持// 這兩種類型的切點validate(aspectClass);// 將當前aspectInstanceFactory進行封裝,這里LazySingletonAspectInstanceFactoryDecorator// 使用裝飾器模式,主要是對獲取到的切面實例進行了緩存,保證每次獲取到的都是同一個切面實例MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);List<Advisor> advisors = new LinkedList<>();// 這里getAdvisorMethods()會獲取所有的沒有使用@Pointcut注解標注的方法,然后對其進行遍歷for (Method method : getAdvisorMethods(aspectClass)) {// 判斷當前方法是否標注有@Before,@After或@Around等注解,如果標注了,則將其封裝為一個AdvisorAdvisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, advisors.size(), aspectName);if (advisor != null) {advisors.add(advisor);}}// 這里的isLazilyInstantiated()方法判斷的是當前bean是否應該被延遲初始化,其主要是判斷當前// 切面類是否為perthis,pertarget或pertypewithiin等聲明的切面。因為這些類型所環繞的目標bean// 都是多例的,因而需要在運行時動態判斷目標bean是否需要環繞當前的切面邏輯if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {// 如果Advisor不為空,并且是需要延遲初始化的bean,則在第0位位置添加一個同步增強器,// 該同步增強器實際上就是一個BeforeAspect的AdvisorAdvisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);advisors.add(0, instantiationAdvisor);}// 判斷屬性上是否包含有@DeclareParents注解標注的需要新添加的屬性,如果有,則將其封裝為一個Advisorfor (Field field : aspectClass.getDeclaredFields()) {Advisor advisor = getDeclareParentsAdvisor(field);if (advisor != null) {advisors.add(advisor);}}return advisors;
}
???????在上述getAdvisors()
方法中,Spring會遍歷當前切面類所有的方法,包括父類和父接口的方法,找到其中沒有使用@Pointcut
注解標注的方法,然后對找到的方法進行遍歷,將其封裝為一個Advisor。這里我們繼續看封裝為Advisor的方法:
public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) {// 校驗當前切面類是否使用了perflow或者percflowbelow標識的切點,Spring暫不支持這兩種切點validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());// 獲取當前方法中@Before,@After或者@Around等標注的注解,并且獲取該注解的值,將其// 封裝為一個AspectJExpressionPointcut對象AspectJExpressionPointcut expressionPointcut = getPointcut(candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());if (expressionPointcut == null) {return null;}// 將獲取到的切點,切點方法等信息封裝為一個Advisor對象,也就是說當前Advisor包含有所有// 當前切面進行環繞所需要的信息return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
}
???????到這里Spring才將@Before,@After或@Around標注的方法封裝為了一個Advisor對象。需要說明的是,這里封裝成的Advisor對象只是一個半成品。所謂的半成品指的是此時其并沒有對切點表達式進行解析,其還只是使用一個字符串保存在AspectJExpressionPointcut對象中,只有在真正使用當前Advice邏輯進行目標bean的環繞的時候才會對其進行解析。
3. 小結
???????本文主要講解了Spring是如何獲取所有的Advisor的,即首先獲取BeanFactory中所有實現了Advisor接口的bean,然后獲取BeanFactory中所有標注了@Aspect注解的bean,解析該bean中的所有的切面邏輯,并且封裝為一個個Advisor,這兩種方式得到的Advisor都有可能是最終會應用到目標bean上的切面邏輯。需要注意的是,這里獲取到的Advisor并沒有對切點表達式進行解析,實際的解析過程是在判斷當前bean是否可以應用到目標bean時進行的。這也是一個小小的優化,因為解析切點表達式的過程是一個比較復雜的過程。