Spring 中 BeanFactory 的生命周期與源碼解析(附例程)

參考地址:

《Spring核心之BeanFactory 一張圖看懂getBean全過程》
《Spring bean的生命周期,對比源碼詳解》
《Spring源碼學習--Bean的生命周期》
《AbstractApplicationContext.refresh()》
《容器的功能擴展(三)finishRefresh》
《spring中InitializingBean接口使用理解》

參考書籍:

《Spring+3.x企業應用開發實戰》


前段時間項目中用 EhCache 緩存,其中為某些 Bean 添加緩存的工作放到了 InitializingBean 接口的 afterPropertiesSet() 方法中。對該方法一直都表示很奇怪,BeanFactory 的生命周期部分內容筆者也覺得應該重點學習一下,所以有時間就總結學習一下。
本文將從一個例程說明 BeanFactory 的生命周期的起始到終結。

一. BeanFactory 例程

筆者以 《Spring源碼學習--Bean的生命周期》 中的例程為例。筆者也將其上傳到筆者的 github 賬號上,可以從上面下載并使用 idea 進行實驗。
筆者建議,在 IDE 中建立鏈接中工程完畢后,一定要在每一個方法里面打上斷點,從 main 方法開始運行并觀察程序運行的流程。

鏈接中的源碼不再贅述。源碼運行結果為:

現在開始初始化容器
八月 15, 2018 10:27:00 下午 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7f560810: startup date [Wed Aug 15 22:27:00 CST 2018]; root of context hierarchy
八月 15, 2018 10:27:00 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [beanLife.xml]

這是BeanFactoryPostProcessor實現類構造器!!
BeanFactoryPostProcessor調用postProcessBeanFactory方法
這是BeanPostProcessor實現類構造器!!
這是InstantiationAwareBeanPostProcessorAdapter實現類構造器!!
InstantiationAwareBeanPostProcessor調用postProcessBeforeInstantiation方法
【構造器】調用Person的構造器實例化
InstantiationAwareBeanPostProcessor調用postProcessPropertyValues方法
【注入屬性】注入屬性address
【注入屬性】注入屬性name
【注入屬性】注入屬性phone
【BeanNameAware接口】調用BeanNameAware.setBeanName()
【BeanFactoryAware接口】調用BeanFactoryAware.setBeanFactory()
BeanPostProcessor接口方法postProcessBeforeInitialization對屬性進行更改!
【InitializingBean接口】調用InitializingBean.afterPropertiesSet()
【init-method】調用<bean>的init-method屬性指定的初始化方法
BeanPostProcessor接口方法postProcessAfterInitialization對屬性進行更改!
InstantiationAwareBeanPostProcessor調用postProcessAfterInitialization方法
容器初始化成功
Person [address=廣州, name=張三, phone=110]
現在開始關閉容器!
【DiposibleBean接口】調用DiposibleBean.destory()
【destroy-method】調用<bean>的destroy-method屬性指定的初始化方法

注:
準確的說,該例程的應用上下文環境是 ApplicationContext,所以講的不完全是 BeanFactory 的生命周期。但 ApplicationContext 是 BeanFactory 的派生實現類,且 ApplicationContext 的生命周期與 BeanFactory 生命周期十分相近,所以還是用該例程進行說明即可。

二. 從例程看 BeanFactory 中 Bean 的生命周期

在《Spring+3.x企業應用開發實戰》一書中,用一幅圖描述了 BeanFactory 中 Bean 生命周期的完整過程:

圖 1.1 BeanFactory 中 Bean 的生命周期

筆者用上面的例程調試了一下,發現輸出結果是與上圖中的流程不相稱的。所以筆者總結了一下從 main 方法開始運行到 bean 的銷毀整個流程如下:

2.1 ApplicationContext 的初始化

  1. main 函數:
    • ApplicationContext factory = new ClassPathXmlApplicationContext("beanLife.xml");

從 main 函數開始,第一句代碼定義了一個 classpath 路徑為基準的應用上下文。也就是說,定義了 bean 文件的 xml 文件只有放在 web-info/classes 目錄下的配置文件才有效果

2.2 BeanFactoryPostProcessor

然后代碼進入了 AbstractApplicationContext 中。在 ClassPathXmlApplicationContext 中,初始化的核心代碼在 AbstractApplicationContext 的 refresh() 方法中:


...
try {
    this.postProcessBeanFactory(beanFactory);
    this.invokeBeanFactoryPostProcessors(beanFactory);
    this.registerBeanPostProcessors(beanFactory);
    this.initMessageSource();
    this.initApplicationEventMulticaster();
    this.onRefresh();
    this.registerListeners();
    this.finishBeanFactoryInitialization(beanFactory);
    this.finishRefresh();
}
...

  1. AbstractApplicationContext:
    • invokeBeanFactoryPostProcessors(beanFactory): 調用 Bean 工廠后處理器;
  2. AbstractApplicationContext # doGetBean:
    • beanName = "beanFactoryPostProcessor";
    • 在 invokeBeanFactoryPostProcessors() 方法中載入 id 為 beanFactoryPostProcessor 的 bean,在 beanLife.xml 中,該 bean 對應的類為我們自定義的 MyBeanFactoryPostProcessor;
  3. 構造函數:MyBeanFactoryPostProcessor;
  4. MyBeanFactoryPostProcessor # postProcessBeanFactory:
    • MyBeanFactoryPostProcessor 實現 BeanFactoryPostProcessor 接口;
    • BeanDefinition bd = arg0.getBeanDefinition("person"):
      • (從 beanLife.xml 中)獲取名為 person 的 bean 定義;同理如果參數為 "computer",也可以獲取 computer 的 bean 定義;
    • bd.getPropertyValues().addPropertyValue("phone", "110");
      • 獲得了 bean 定義后,可以設置屬性值;

2.3 BeanPostProcessor 與 InstantiationAwareBeanPostProcessor

BeanFactoryPostProcessor 完成后,應用上下文的 refresh() 方法運行至 registerBeanPostProcessors,進行 BeanPostProcessor 的初始化:

  1. AbstractApplicationContext # registerBeanPostProcessors;
    • 注冊 Bean 后處理器;
  2. AbstractApplicationContext # doGetBean:
    • beanName = "beanPostProcessor";
    • registerBeanPostProcessors() 方法中載入 id 為 beanPostProcessor 的 bean,在 beanLife.xml 中,該 bean 對應的類為我們自定義的 MyBeanPostProcessor;
  3. 構造函數:MyBeanPostProcessor;
  4. AbstractApplicationContext # doGetBean:
    • beanName = "inistantiationAwareBeanPostProcessor";
    • registerBeanPostProcessors() 中,載入 id 為 inistantiationAwareBeanPostProcessor 的 bean,在 beanLife.xml 中,該 bean 對應的類為我們自定義的 MyInstantiationAwareBeanPostProcessor;
  5. 構造函數:MyInstantiationAwareBeanPostProcessor;
  6. AbstractApplicationContext # finishBeanFactoryInitialization();
    • 完成 BeanFactory 的初始化工作;
    • 此后,BeanFactory 凍結所有的 Bean 定義,不再可以修改或者做 post process 操作;

注:關于 AbstractApplicationContext # refresh() 方法詳細流程見:《AbstractApplicationContext.refresh()》

2.4 xml 文件 bean 的實例化與初始化

截止到上面,應用上下文已經裝載完畢,上下文將對 xml 文件中的 bean 中進行實例化。beanLife.xml 配置文件中有 Person 和 Computer 兩個 bean,這里以 Person 為例進行說明。

首先是實例化,即 Person 構造函數的調用。

  1. MyInstantiationAwareBeanPostProcessor # postProcessBeforeInstantiation:
    • 實例化之前的處理;
  2. 構造函數:Person;
    • 注意到,即使此時已經調用了 Person 的構造函數,但實際上 main 函數并沒有運行到 Person 的 getBean 階段。說明 main 函數獲取 bean 之前,bean 已經在應用上下文中裝載完畢

然后是初始化,即 Person 屬性注入的過程。

  1. MyInstantiationAwareBeanPostProcessor # postProcessPropertyValues:
    • 為初始化的對象注入屬性;
  2. Person 注入屬性;
    • 分別調用 Person 屬性值 name, address, phone 的 set 方法;
  3. BeanNameAware # setBeanName
    • Person 實現了 BeanNameAware 接口,則調用該接口方法 setBeanName;
  4. BeanFactoryAware # setBeanFactory
    • Person 實現了 BeanFactoryAware 接口,則調用該接口方法 setBeanFactory;
  5. MyBeanPostProcessor # postProcessBeforeInitialization:
    • 根據《Spring+3.x 企業應用開發實戰》一書中所述:BeanPostProcessor 在 Spring 框架中占有重要的地位,它為容器提供對 Bean 進行后續加工處理的切入點,Spring 容器所提供的各種功能(如 AOP,動態代理等),都通過 BeanPostProcessor 實施;
  6. InitializingBean # afterPropertiesSet():
    • 初始化方法一;
  7. beanLife.xml 的 Person 的 init-method 方法:
    • 初始化方法二;
  8. MyBeanPostProcessor # postProcessAfterInitialization:
  9. MyInstantiationAwareBeanPostProcessor # postProcessAfterInitialization

經歷了上面的實例化 (Instantiation)初始化 (Initialization) 之后,一個 bean 就創建完成了。
與 person 的 bean 實例化過程相同,剩下一個 id 為 computer 的 bean 實例化與初始化的過程如下:

  1. MyInstantiationAwareBeanPostProcessor # postProcessBeforeInstantiation
  2. 構造函數:Computer
  3. MyInstantiationAwareBeanPostProcessor # postProcessPropertyValues
  4. Computer 注入屬性;
  5. BeanNameAware # setBeanName
  6. BeanFactoryAware # setBeanFactory
  7. MyBeanPostProcessor # postProcessBeforeInitialization
  8. InitializingBean # afterPropertiesSet()
  9. beanLife.xml 的 Computer 的 init-method 方法
  10. MyBeanPostProcessor # postProcessAfterInitialization
  11. MyInstantiationAwareBeanPostProcessor # postProcessAfterInitialization

注:關于兩種初始化方法 (afterPropertiesSet 與 init-method 指定方法) 的區別:

  1. spring為bean提供了兩種初始化bean的方式,實現InitializingBean接口,實現afterPropertiesSet方法,或者在配置文件中同過init-method指定,兩種方式可以同時使用;
  2. 實現InitializingBean接口是直接調用afterPropertiesSet方法,比通過反射調用init-method指定的方法效率相對來說要高點。但是init-method方式消除了對spring的依賴;
  3. 如果調用afterPropertiesSet方法時出錯,則不調用init-method指定的方法。

2.5 finishRefresh

  1. AbstractApplicationContext # finishRefresh
  2. 第四次 doGetBean, beanName = "lifecycleProcessor";
  3. main # Person person = factory.getBean("person",Person.class);
  4. 第五次 doGetBean, beanName = "person";
  5. 第六次 doGetBean, beanName = "computer";
  6. main # ((ClassPathXmlApplicationContext)factory).registerShutdownHook();
  7. 第七次 doGetBean, beanName = "lifecycleProcessor"

2.6 main 調用 beans

此后進入 main 函數的 getBean 部分:

public static void main(String[] args) {
    ...
    
    //得到Preson,并使用
    Person person = context.getBean("person",Person.class);
    System.out.println(person);
    // 得到 Computer,并使用
    Computer computer = context.getBean("computer", Computer.class);
    System.out.println(computer);
    
    ...
}

從前面在 BeanPostProcessor 中已經將所有 bean 在應用上下文中實例化完成,在 main 函數這里只是從應用上下文中,通過應用上下文的 getBean 方法取出即可。

2.7 bean 的銷毀

在基于 web 的 ApplicationContext 實現中,已有相應的實現來處理關閉 web 應用時恰當地關閉 Spring IoC 容器。但對于該例中的一個非 web 應用的環境下使用 Spring 的 IoC 容器,如果想讓容器優雅的關閉,并調用 singleton 的 bean 相應 destory 回調方法,則需要在 JVM 里注冊一個“關閉鉤子” (shutdown hook)。這一點非常容易做到,并且將會確保你的 Spring IoC 容器被恰當關閉,以及所有由單例持有的資源都會被釋放

為了注冊“關閉鉤子”,你只需要簡單地調用在 AbstractApplicationContext 實現中的registerShutdownHook() 方法即可。

  1. main # registerShutdownHook();
    • main 函數中,AbstractApplicationContext 調用 registerShutdownHook() 方法,注冊容器的關閉;
  2. Computer 的銷毀
    • (1) DisposableBean(Computer) # destory
    • (2) beanLife.xml 的 Computer 的 destroy-method 方法
  3. Person 的銷毀
    • (1) DisposableBean(Person) # destory
    • (2) beanLife.xml 的 Person 的 destroy-method 方法

至此,該程序結束。

三. 后記

Spring 生命周期的理解,對后面的事務處理、AOP 等重要特性有很大的幫助。但如果要理解生命周期,單看書是很難理解的,尤其是對著那些又長又多的類名,和它們那些又長又多又像的方法。所以筆者建議如果想要理解聲明周期:

調試!!!

調試!!!

調試!!!

只有對著教程,運行代碼的一步步調試,才能加深自己的印象。

筆者的工程,已經上傳到了筆者的 github 賬號上。
路徑如下:https://github.com/upcAutoLang/SpringBeanLIfeCycleDemo

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1.1 Spring IoC容器和bean簡介 本章介紹了Spring Framework實現的控制反轉(IoC)...
    起名真是難閱讀 2,617評論 0 8
  • 1.1 spring IoC容器和beans的簡介 Spring 框架的最核心基礎的功能是IoC(控制反轉)容器,...
    simoscode閱讀 6,748評論 2 22
  • 創建 bean 在經歷過 AbstractAutowireCapableBeanFactory#createBea...
    仗劍詩篇閱讀 2,236評論 0 1
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,868評論 18 139
  • 盛夏傍晚,來到小河邊納涼。遠離了汽車的轟鳴,空調的煩躁,坐在混著青草香味的空氣里,頓時神清氣爽。索性躺在河堤上,管...
    香凝綠蔭閱讀 602評論 1 6