曹工說Spring Boot源碼(18)-- Spring AOP源碼分析三部曲,終於快講完了 (aop:config完整解析【下】)


寫在前面的話

相關背景及資源:

曹工說Spring Boot源碼(1)-- Bean Definition到底是什么,附spring思維導圖分享

曹工說Spring Boot源碼(2)-- Bean Definition到底是什么,咱們對着接口,逐個方法講解

曹工說Spring Boot源碼(3)-- 手動注冊Bean Definition不比游戲好玩嗎,我們來試一下

曹工說Spring Boot源碼(4)-- 我是怎么自定義ApplicationContext,從json文件讀取bean definition的?

曹工說Spring Boot源碼(5)-- 怎么從properties文件讀取bean

曹工說Spring Boot源碼(6)-- Spring怎么從xml文件里解析bean的

曹工說Spring Boot源碼(7)-- Spring解析xml文件,到底從中得到了什么(上)

曹工說Spring Boot源碼(8)-- Spring解析xml文件,到底從中得到了什么(util命名空間)

曹工說Spring Boot源碼(9)-- Spring解析xml文件,到底從中得到了什么(context命名空間上)

曹工說Spring Boot源碼(10)-- Spring解析xml文件,到底從中得到了什么(context:annotation-config 解析)

曹工說Spring Boot源碼(11)-- context:component-scan,你真的會用嗎(這次來說說它的奇技淫巧)

曹工說Spring Boot源碼(12)-- Spring解析xml文件,到底從中得到了什么(context:component-scan完整解析)

曹工說Spring Boot源碼(13)-- AspectJ的運行時織入(Load-Time-Weaving),基本內容是講清楚了(附源碼)

曹工說Spring Boot源碼(14)-- AspectJ的Load-Time-Weaving的兩種實現方式細細講解,以及怎么和Spring Instrumentation集成

曹工說Spring Boot源碼(15)-- Spring從xml文件里到底得到了什么(context:load-time-weaver 完整解析)

曹工說Spring Boot源碼(16)-- Spring從xml文件里到底得到了什么(aop:config完整解析【上】)

曹工說Spring Boot源碼(17)-- Spring從xml文件里到底得到了什么(aop:config完整解析【中】)

工程代碼地址 思維導圖地址

工程結構圖:

概要

本篇是接着前兩篇講的,為了避免不必要的重復,請大家先看下。

曹工說Spring Boot源碼(16)-- Spring從xml文件里到底得到了什么(aop:config完整解析【上】)

曹工說Spring Boot源碼(17)-- Spring從xml文件里到底得到了什么(aop:config完整解析【中】)

本篇主要講解spring如何在bean生成過程中,完成“狸貓換太子”操作的。(利用代理對象,替換ioc容器中的原有bean)。

回顧前文

spring解析的xml如下:

<!--目標對象-->
<bean id="performer" class="foo.Performer"/>

<!--切面-->
<bean id="performAspect" class="foo.PerformAspect"/>

<!--配置切入點-->
<aop:config>
    <aop:pointcut id="mypointcut" expression="execution(public * foo.Perform.sing(..))"/>
    <aop:aspect id="myAspect" ref="performAspect">
        <aop:after method="afterPerform" pointcut-ref="mypointcut"/>
    </aop:aspect>
</aop:config>

經前文的理解,總共生成了如下幾個bean definition。

bean definition 中bean class 備注
PerformAspect 通知
Performer 要切的目標
AspectJExpressionPointcut 切點,即<aop:pointcut />那一行
org.springframework.aop.aspectj.AspectJPointcutAdvisor advisor,解析<aop:after method="afterPerform" pointcut-ref="mypointcut"/> 這一行得到
AspectJAwareAdvisorAutoProxyCreator 實現了BeanPostProcessor接口,解析<aop:config>得到

spring大致啟動流程

  1. 解析xml、注解,通過各種渠道,得到bean definition;

  2. 從bean definition集合中,找出bean class實現了BeanFactoryPostProcessor接口的子集,然后通過getBean來獲取這部分特殊的bean,然后依次調用其postProcessBeanFactory方法

    來對其余的bean definition進行處理;

    public interface BeanFactoryPostProcessor {
    
    	/**
    	 * Modify the application context's internal bean factory after its standard
    	 * initialization. All bean definitions will have been loaded, but no beans
    	 * will have been instantiated yet. This allows for overriding or adding
    	 * properties even to eager-initializing beans.
    	 * @param beanFactory the bean factory used by the application context
    	 * @throws org.springframework.beans.BeansException in case of errors
    	 */
    	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory);
    }
    
  3. 從bean definition集合中,找出bean class實現了BeanPostProcessor接口的子集,然后通過getBean來獲取這部分特殊的bean,然后保存起來。

  4. 找出不具有特殊功能的(沒實現BeanFactoryPostProcessor,也沒實現BeanPostProcessor)bean definition 集合,再過濾出單例bean,且lazy-init屬性為false(說明要在spring容器初始化過程中,實例化的bean)的這部分,作為子集;然后依次調用其getBean方法,在這個過程中,會使用第三步獲取到的beanPostProcessor來處理這些bean,時機大致包括:實例化前后,初始化前后。

以上就是spring比較粗的流程,當然,很多細節沒說,不過核心流程是這樣的。

spring aop如何生效

1. 實例化AspectJAwareAdvisorAutoProxyCreator

在前面的spring流程中,第三步,就會去查找實現了BeanPostProcessor的bean definition集合,其中,就會包含AspectJAwareAdvisorAutoProxyCreator 這個bean definition;然后通過getBean方法,將這個bean definition實例化為bean。(這個過程,類似於通過class來創建對象)

這個步驟的具體實現入口在:

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
      // Prepare this context for refreshing.
      prepareRefresh();

      // Tell the subclass to refresh the internal bean factory.
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      /**
       * 配置beanFactory
       */
      prepareBeanFactory(beanFactory);

      try {
         postProcessBeanFactory(beanFactory);

         // Invoke factory processors registered as beans in the context.
         invokeBeanFactoryPostProcessors(beanFactory);

         // 入口在這里!這里面會去 getBean
         registerBeanPostProcessors(beanFactory);

         // Initialize message source for this context.
         initMessageSource();

         // Initialize other special beans in specific context subclasses.
         onRefresh();

         // Check for listener beans and register them.
         registerListeners();

         /**
          * 這里對單例bean、且lazy-init=false進行實例化
          */
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         finishRefresh();
      }
   }
}

具體跟進去后,在如下位置,809行,就通過getBean,獲取到了AspectJAwareAdvisorAutoProxyCreator這個bean了:

2.AspectJAwareAdvisorAutoProxyCreator對target bean進行后置處理,生成代理

在“spring大致啟動流程”中的第四步,去預先實例化bean時,會進入以下邏輯:

public void refresh() throws BeansException, IllegalStateException {
   synchronized (this.startupShutdownMonitor) {
	  ...

      try {
		...

         // Check for listener beans and register them.
         registerListeners();

         /**
          * 入口在這里。這里對單例bean、且lazy-init=false進行實例化
          */
         finishBeanFactoryInitialization(beanFactory);

         // Last step: publish corresponding event.
         finishRefresh();
      }
   }
}

然后會進入以下邏輯:

org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons
public void preInstantiateSingletons() throws BeansException {
   if (this.logger.isInfoEnabled()) {
      this.logger.info("Pre-instantiating singletons in " + this);
   }
   // 其中 this.beanDefinitionNames 保存了所有的bean definition名稱
   for (String beanName : this.beanDefinitionNames) {
      RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
      // 實例化那些:非抽象、單例、非lazy-init的bean definition
      if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
         if (isFactoryBean(beanName)) {
		   ...
            if (isEagerInit) {
               getBean(beanName);
            }
         }
         else {
            getBean(beanName);
         }
      }
   }
}

所以,這里就是對那些:非抽象、單例、非lazy-init的bean definition 進行實例化。

我們這里,大家看下當前容器中包含的全部的bean definition:

我這邊的demo,第一個要實例化的bean,就是performer。在getBean()處理performer這個bean的過程中,會經歷以下的流程:

其中,AspectJAwareAdvisorAutoProxyCreator 作為beanPostProcessor,在實例化performer這個bean時,有兩個時間點參與進來。

哪兩個時間點呢,大家再看看,AspectJAwareAdvisorAutoProxyCreator 的類圖:

其實它不止實現了BeanPostProcessor接口,它還實現了InstantiationAwareBeanPostProcessor接口。這個多出來的接口,干嘛的?大家看看它的方法就知道了:

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {

	// 注意,before Instantiation的意思是,實例化之前調用
	Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;

	// 這個,實例化之后調用
	boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException;

	...無關方法
}

所以,InstantiationAwareBeanPostProcessor擴展了兩個時間點出來,一個是實例化前,一個是實例化后。

什么叫實例化?把一個對象new出來,通俗來講,就叫做實例化。

在spring的getBean流程里,實例化肯定是早於初始化的。所以,一個bean,會經歷如下順序(大家直接看前面點的圖,更清晰):

  1. InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation;

  2. 根據bean definition來實例化,假設bean class為A,bean definition中的constructorArgumentValues參數,如果為空,則使用A的默認構造函數;如果constructorArgumentValues有值,表示:在此之前,需要先獲取到相應的構造函數值,才能去反射通過構造器來創建對象

    這里面,會涉及到bean的遞歸調用。

    比如,以前一篇和開頭提到的,AspectJPointcutAdvisor 這個bean definition來說,其中它的構造函數,就要求一個AbstractAspectJAdvice 對象:

    public AspectJPointcutAdvisor(AbstractAspectJAdvice advice) {
       Assert.notNull(advice, "Advice must not be null");
       this.advice = advice;
       this.pointcut = advice.buildSafePointcut();
    }
    

    那AbstractAspectJAdvice 這個對象要怎么生成,這個在我們的場景下,是被抽象成一個內部bean的,bean class為AspectJAfterAdvice。AspectJAfterAdvice這個類呢,構造函數又是下面這樣的:

    public AspectJAfterAdvice(
          Method aspectJBeforeAdviceMethod, AspectJExpressionPointcut pointcut, AspectInstanceFactory aif) {
    
       super(aspectJBeforeAdviceMethod, pointcut, aif);
    }
    

    又依賴了一堆其他的參數,這三個參數,其中2個也是被定義為了內部bean definition,一個為bean 引用。(具體請參照前一篇)。

    所以,這個bean的實例化過程就相對繁瑣,涉及到bean的遞歸生成。

  3. InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation;

  4. 屬性注入,此時是autowired等發揮作用的地方;

  5. BeanPostProcessor的postProcessBeforeInitialization

  6. BeanPostProcessor#postProcessAfterInitialization

這里面6個步驟,AspectJAwareAdvisorAutoProxyCreator 在其中兩個地方,實現了自己的業務邏輯。

2.1 InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation

具體實現在:

org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
   Object cacheKey = getCacheKey(beanClass, beanName);

   if (beanName == null || !this.targetSourcedBeans.contains(beanName)) {
      if (this.advisedBeans.containsKey(cacheKey)) {
         return null;
      }
      // 入口在這:shouldSkip 
      if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
         this.advisedBeans.put(cacheKey, Boolean.FALSE);
         return null;
      }
   }

   // Create proxy here if we have a custom TargetSource.
   // Suppresses unnecessary default instantiation of the target bean:
   // The TargetSource will handle target instances in a custom fashion.
   if (beanName != null) {
      TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
      if (targetSource != null) {
         this.targetSourcedBeans.add(beanName);
         Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
         Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
         this.proxyTypes.put(cacheKey, proxy.getClass());
         return proxy;
      }
   }

   return null;
}

以上邏輯中,我們需要進入到shouldSkip方法,重點是下面的findCandidateAdvisors方法:

@Override
protected boolean shouldSkip(Class beanClass, String beanName) {
   // TODO: Consider optimization by caching the list of the aspect names
   /**
    * 這里調用了父類中實現的findCandidateAdvisors,獲取候選的advisor bean;這里也是真正根據bean
    * definition去生成advisor bean的地方
    */
   List<Advisor> candidateAdvisors = **findCandidateAdvisors()**;
   for (Advisor advisor : candidateAdvisors) {
      /**
       * 如果當前要檢查的bean,就是advisor里的通知類,則跳過
       */
      if (advisor instanceof AspectJPointcutAdvisor) {
         if (((AbstractAspectJAdvice) advisor.getAdvice()).getAspectName().equals(beanName)) {
            return true;
         }
      }
   }
   return super.shouldSkip(beanClass, beanName);
}

下面會找出ioc容器中,實現了Advisor接口的bean definition,並全部實例化。Advisor是什么?

我們前面提到的AspectJPointcutAdvisor,就實現了這個接口。

public List<Advisor> findAdvisorBeans() {
   // Determine list of advisor bean names, if not cached already.
   String[] advisorNames = null;
   synchronized (this) {
      advisorNames = this.cachedAdvisorBeanNames;
      if (advisorNames == null) {
         // 1.從spring容器查找Advisor類型的bean definition
         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>();
   for (String name : advisorNames) {
      if (isEligibleBean(name) && !this.beanFactory.isCurrentlyInCreation(name)) {
            // 遍歷那些bean definition,通過getBean,來獲取bean
            advisors.add(this.beanFactory.getBean(name, Advisor.class));
      }
   }
   return advisors;
}

再啰嗦一句,advisor差不多是aop的核心數據結構,你通過Aspect注解方式,最終也是解析為一個個的Advisor。

一個切點+一個切面方法 基本就等於一個Advisor對象。比如,一個before方法,算一個;在一個after,又算一個。

2.2 BeanPostProcessor的postProcessAfterInitialization

這是第二個時間點,在這里,檢查bean要不要被攔截,生成代理。大家可以簡單理解,因為每個bean的創建過程,都要被它處理,它呢,就會檢查,這個bean,是不是匹配切點,如果匹配,就生成代理。

舉個例子,以前看過一個小說,是京官去很偏遠的地方上任,路上被人殺了,並被另外一個人拿了印章,到了地方,靠着印章,招搖撞騙,當了地方官。假設切點是:每個過路人;切面:偷天換日。那這個PostProcessor,就是那伙人,攔截每個過路人,並判斷是不是那個倒霉的京官,如果是,就殺了並且換個人拿了印章去當地方官。

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
   if (bean != null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (!this.earlyProxyReferences.contains(cacheKey)) {
         // 返回生成的代理對象
         return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}

這里的下面這句,bean是原始bean,wrapIfNecessary返回的,就作為最終的bean。如果你不需要切這個bean,那你就返回它本身;如果要代理,你就返回另一個代理對象即可。

return wrapIfNecessary(bean, beanName, cacheKey);

我們仔細看這個方法內部:

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;
   }
   if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
   }

   // 獲取前面2.1章節里的Advisor對象,並保存到Object[] specificInterceptors 里
   Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
   if (specificInterceptors != null) {
      this.advisedBeans.put(cacheKey, Boolean.TRUE);
      // 創建代理,注意,Advisor數組,已經被傳進去了。
      Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
      this.proxyTypes.put(cacheKey, proxy.getClass());
      return proxy;
   }

   this.advisedBeans.put(cacheKey, Boolean.FALSE);
   return bean;
}

大家看上面的注釋吧,這里獲取了ioc容器里的Advisor對象,如果advisor數組不為null,則創建代理,並返回代理。大家看下面的debug圖就理解了。

創建代理的過程就簡單了,基本就是:有接口就搞個jdk 動態代理,否則就cglib代理。

我做了個小測試,

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
   if (bean.getClass().getName().equals("foo.Performer")) {
      return "hahha";
   }
   ...其他代碼省略,主要加了上面那個if
   return bean;
}
public final class Main {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(
                "context-namespace-test-aop.xml");

        Perform performer = (Perform) ctx.getBean(Perform.class);
        performer.sing();
    }
}

然后在倒數第二行,獲取Perform類型的bean時,報錯了。為啥呢?因為本來foo.Performer實現了Perform接口,但現在,我用一個string作為代理返回去了,所以就沒有實現Perform接口的bean存在了

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [foo.Perform] is defined
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:296)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1196)
	at foo.Main.main(Main.java:23)

總結

spring aop這個東西,還是不簡單,我本來打算這一講把全部內容說清楚;但現在發現,創建代理這部分,還是得放到下一篇。

至於源碼那些,在第16篇里給了地址的,大家可以看看。有啥問題,及時聯系我。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM