參考地址:
《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 生命周期的完整過程:
筆者用上面的例程調試了一下,發現輸出結果是與上圖中的流程不相稱的。所以筆者總結了一下從 main 方法開始運行到 bean 的銷毀整個流程如下:
2.1 ApplicationContext 的初始化
- 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();
}
...
- AbstractApplicationContext:
- invokeBeanFactoryPostProcessors(beanFactory): 調用 Bean 工廠后處理器;
-
AbstractApplicationContext # doGetBean:
- beanName = "beanFactoryPostProcessor";
- 在 invokeBeanFactoryPostProcessors() 方法中載入 id 為 beanFactoryPostProcessor 的 bean,在 beanLife.xml 中,該 bean 對應的類為我們自定義的 MyBeanFactoryPostProcessor;
- 構造函數:MyBeanFactoryPostProcessor;
- 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 的初始化:
- AbstractApplicationContext # registerBeanPostProcessors;
- 注冊 Bean 后處理器;
-
AbstractApplicationContext # doGetBean:
- beanName = "beanPostProcessor";
- 在 registerBeanPostProcessors() 方法中載入 id 為 beanPostProcessor 的 bean,在 beanLife.xml 中,該 bean 對應的類為我們自定義的 MyBeanPostProcessor;
- 構造函數:MyBeanPostProcessor;
-
AbstractApplicationContext # doGetBean:
- beanName = "inistantiationAwareBeanPostProcessor";
- 在 registerBeanPostProcessors() 中,載入 id 為 inistantiationAwareBeanPostProcessor 的 bean,在 beanLife.xml 中,該 bean 對應的類為我們自定義的 MyInstantiationAwareBeanPostProcessor;
- 構造函數:MyInstantiationAwareBeanPostProcessor;
- AbstractApplicationContext # finishBeanFactoryInitialization();
- 完成 BeanFactory 的初始化工作;
- 此后,BeanFactory 凍結所有的 Bean 定義,不再可以修改或者做 post process 操作;
注:關于 AbstractApplicationContext # refresh() 方法詳細流程見:《AbstractApplicationContext.refresh()》
2.4 xml 文件 bean 的實例化與初始化
截止到上面,應用上下文已經裝載完畢,上下文將對 xml 文件中的 bean 中進行實例化。beanLife.xml 配置文件中有 Person 和 Computer 兩個 bean,這里以 Person 為例進行說明。
首先是實例化,即 Person 構造函數的調用。
- MyInstantiationAwareBeanPostProcessor # postProcessBeforeInstantiation:
- 實例化之前的處理;
- 構造函數:Person;
- 注意到,即使此時已經調用了 Person 的構造函數,但實際上 main 函數并沒有運行到 Person 的 getBean 階段。說明 main 函數獲取 bean 之前,bean 已經在應用上下文中裝載完畢;
然后是初始化,即 Person 屬性注入的過程。
- MyInstantiationAwareBeanPostProcessor # postProcessPropertyValues:
- 為初始化的對象注入屬性;
- Person 注入屬性;
- 分別調用 Person 屬性值 name, address, phone 的 set 方法;
- BeanNameAware # setBeanName
- Person 實現了 BeanNameAware 接口,則調用該接口方法 setBeanName;
- BeanFactoryAware # setBeanFactory
- Person 實現了 BeanFactoryAware 接口,則調用該接口方法 setBeanFactory;
-
MyBeanPostProcessor # postProcessBeforeInitialization:
- 根據《Spring+3.x 企業應用開發實戰》一書中所述:BeanPostProcessor 在 Spring 框架中占有重要的地位,它為容器提供對 Bean 進行后續加工處理的切入點,Spring 容器所提供的各種功能(如 AOP,動態代理等),都通過 BeanPostProcessor 實施;
- InitializingBean # afterPropertiesSet():
- 初始化方法一;
- beanLife.xml 的 Person 的 init-method 方法:
- 初始化方法二;
- MyBeanPostProcessor # postProcessAfterInitialization:
- MyInstantiationAwareBeanPostProcessor # postProcessAfterInitialization
經歷了上面的實例化 (Instantiation) 與初始化 (Initialization) 之后,一個 bean 就創建完成了。
與 person 的 bean 實例化過程相同,剩下一個 id 為 computer 的 bean 實例化與初始化的過程如下:
- MyInstantiationAwareBeanPostProcessor # postProcessBeforeInstantiation
- 構造函數:Computer
- MyInstantiationAwareBeanPostProcessor # postProcessPropertyValues
- Computer 注入屬性;
- BeanNameAware # setBeanName
- BeanFactoryAware # setBeanFactory
- MyBeanPostProcessor # postProcessBeforeInitialization
- InitializingBean # afterPropertiesSet()
- beanLife.xml 的 Computer 的 init-method 方法
- MyBeanPostProcessor # postProcessAfterInitialization
- MyInstantiationAwareBeanPostProcessor # postProcessAfterInitialization
注:關于兩種初始化方法 (afterPropertiesSet 與 init-method 指定方法) 的區別:
- spring為bean提供了兩種初始化bean的方式,實現InitializingBean接口,實現afterPropertiesSet方法,或者在配置文件中同過init-method指定,兩種方式可以同時使用;
- 實現InitializingBean接口是直接調用afterPropertiesSet方法,比通過反射調用init-method指定的方法效率相對來說要高點。但是init-method方式消除了對spring的依賴;
- 如果調用afterPropertiesSet方法時出錯,則不調用init-method指定的方法。
2.5 finishRefresh
- AbstractApplicationContext # finishRefresh
- 第四次 doGetBean, beanName = "lifecycleProcessor";
- main # Person person = factory.getBean("person",Person.class);
- 第五次 doGetBean, beanName = "person";
- 第六次 doGetBean, beanName = "computer";
- main # ((ClassPathXmlApplicationContext)factory).registerShutdownHook();
- 第七次 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() 方法即可。
- main # registerShutdownHook();
- main 函數中,AbstractApplicationContext 調用 registerShutdownHook() 方法,注冊容器的關閉;
- Computer 的銷毀
- (1) DisposableBean(Computer) # destory
- (2) beanLife.xml 的 Computer 的 destroy-method 方法
- Person 的銷毀
- (1) DisposableBean(Person) # destory
- (2) beanLife.xml 的 Person 的 destroy-method 方法
至此,該程序結束。
三. 后記
Spring 生命周期的理解,對后面的事務處理、AOP 等重要特性有很大的幫助。但如果要理解生命周期,單看書是很難理解的,尤其是對著那些又長又多的類名,和它們那些又長又多又像的方法。所以筆者建議如果想要理解聲明周期:
調試!!!
調試!!!
調試!!!
只有對著教程,運行代碼的一步步調試,才能加深自己的印象。
筆者的工程,已經上傳到了筆者的 github 賬號上。
路徑如下:https://github.com/upcAutoLang/SpringBeanLIfeCycleDemo