1. IOC容器概述
IOC和AOP是Spring框架的核心功能,而IOC又是AOP實現的基礎,因而可以說IOC是整個Spring框架的基石。那么什么是IOC?IOC即控制反轉同辣,通俗的說就是讓Spring框架來幫助我們完成對象的依賴管理和生命周期控制等等工作拷姿。從面向對象的角度來說,具有這種行為旱函,完成這種工作的主體就可以形象的稱之為IOC容器响巢。從代碼角度來看,IOC容器不過是Spring中定義的具有IOC基本功能的一些類的統稱棒妨,這些類都遵循一些共同的接口規(guī)范抵乓,所以我們可以說實現某些接口的具體的實現類就是IOC容器。而IOC容器的啟動流程靶衍,就是創(chuàng)建并初始化一個該實現類的實例的過程灾炭,在這個過程中要進行諸如配置文件的加載解析,核心組件的注冊颅眶,bean 實例的創(chuàng)建等一系列繁瑣復雜的操作蜈出,因而整個過程顯得相對漫長,邏輯也相對復雜涛酗。
2. BeanFactory和ApplicationContext的區(qū)別
前面說到Spring中為容器類定義了一些接口規(guī)范铡原,如下圖所示
具體而言,Spring中的容器類可以分為兩大類商叹。
- 一類是由BeanFactory接口定義的核心容器燕刻。BeanFactory位于整個容器類體系結構的頂端,其基本實現類為DefaultListableBeanFactory剖笙。之所以稱其為核心容器卵洗,是因為該類容器實現IOC的核心功能:比如配置文件的加載解析,Bean依賴的注入以及生命周期的管理等弥咪。BeanFactory作為Spring框架的基礎設施过蹂,面向Spring框架本身,一般不會被用戶直接使用聚至。
- 另一類則是由ApplicationContext接口定義的容器酷勺,通常譯為應用上下文,不過稱其為應用容器可能更形象些扳躬。它在BeanFactory提供的核心IOC功能之上作了擴展脆诉。通常ApplicationContext的實現類內部都持有一個BeanFactory的實例,IOC容器的核心功能會交由它去完成贷币。而ApplicationContext本身击胜,則專注于在應用層對BeanFactory作擴展,比如提供對國際化的支持片择,支持框架級的事件監(jiān)聽機制以及增加了很多對應用環(huán)境的適配等潜的。ApplicationContext面向的是使用Spring框架的開發(fā)者骚揍。開發(fā)中經常使用的ClassPathXmlApplicationContext就是典型的Spring的應用容器字管,也是要進行解讀的IOC容器啰挪。
3. 解讀IOC容器啟動流程的意義
- IOC容器在啟動時會注冊并初始化Spring框架的所有基礎組件,這些組件不僅在IOC模塊中被用到嘲叔,也會被AOP等模塊使用亡呵。因而熟悉IOC容器的啟動流程不僅是掌握IOC模塊的關鍵,也是理解整個Spring框架的前提硫戈。
- Spring是個很靈活的框架锰什,允許用戶在原有功能上進行擴展或者進行滿足業(yè)務需求的個性化設置,比如對容器和Bean的生命周期過程進行增強丁逝,進行事件監(jiān)聽等等汁胆。要更好的使用Spring的這些特性,必須了解其工作原理霜幼,而答案就在IOC容器的啟動過程中嫩码。
- Spring框架在實現時使用了大量的設計模式,體現了很多優(yōu)秀的設計思想罪既。其IOC容器的啟動源碼就是供開發(fā)者學習這種設計經驗的絕佳樣板铸题。
4. 初探IOC容器啟動源碼
啟動Spring容器,本質上是創(chuàng)建并初始化一個具體的容器類的過程琢感,以常見的容器類ClassPathXmlApplicationContext為例丢间,啟動一個Spring容器可以用以下代碼表示
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
盡管只有短短的一行代碼,但已經創(chuàng)建并啟動了一個Spring的IOC容器驹针。為了后面更好的理解烘挫,先來看下ClassPathXmlApplicationContext的類繼承結構
關鍵的幾個類已經用紅色箭頭標注了出來。
- AbstractApplicationContext
ApplicationContext接口的抽象實現類柬甥,能夠自動檢測并注冊各種后置處理器(PostProcessor)和事件監(jiān)聽器(Listener)墙牌,以模板方法模式定義了一些容器的通用方法,比如啟動容器的真正方法refresh()就是在該類中定義的暗甥。 - AbstractRefreshableApplicationContext
繼承AbstractApplicationContext的抽象類喜滨。內部持有一個DefaultListableBeanFactory 的實例,使得繼承AbstractRefreshableApplicationContext的Spring的應用容器內部默認有一個Spring的核心容器撤防,那么Spring容器的一些核心功能就可以委托給內部的核心容器去完成虽风。AbstractRefreshableApplicationContext在內部定義了創(chuàng)建,銷毀以及刷新核心容器BeanFactory的方法寄月。 - ClassPathXmlApplicationContext
最常用的Spring的應用容器之一辜膝。在啟動時會加載類路徑下的xml文件作為容器的配置信息。
下面就正式開始容器啟動流程的源碼閱讀
進入ClassPathXmlApplicationContext的構造方法漾肮,首先調用了重載構造函數
/**
* Create a new ClassPathXmlApplicationContext厂抖, loading the definitions
* from the given XML file and automatically refreshing the context.
* @param configLocation resource location
* @throws BeansException if context creation failed
*/
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true克懊, null);
}
這里有兩點需要注意下:
- 創(chuàng)建ClassPathXmlApplicationContext時需要指定xml文件的路徑作為參數忱辅,盡管我們在創(chuàng)建時只指定了一個七蜘,但其實可以同時指定多個。
- Spring容器有父子容器的概念墙懂,通過HierarchicalBeanFactory接口定義了具有層級關系的容器體系橡卤。而在抽象實現類AbstractApplicationContext類的內部,有一個表示父容器的成員變量损搬。
/** Parent context */
private ApplicationContext parent;
重載函數的第三個參數即表示要創(chuàng)建的ClassPathXmlApplicationContext的父容器碧库,不過這里只需要設置為null。關于Spring的父子容器巧勤,還有一些獨特的訪問規(guī)則嵌灰,子容器可以訪問父容器中的Bean,父容器不可以訪問子容器中的Bean颅悉。不知道這個規(guī)則在使用Spring做web開發(fā)時可能會碰到一些匪夷所思的問題伞鲫。
繼續(xù)跟進源碼
//設置父容器
super(parent);
//設置xml文件的路徑參數
setConfigLocations(configLocations);
if (refresh) { //默認為true
//啟動Spring容器
refresh();
}
設置完父容器和xml文件的路徑信息后,終于看到了refresh()方法签舞,正如前面提到的秕脓,這是真正啟動Spring容器的方法,想要知道Spring IOC容器的啟動流程儒搭,就要知道該方法內部都做了什么吠架。
4.1 容器啟動流程的不同階段
為了更好的進行講解,可以將容器啟動的整個流程劃分為以下五個階段
4.2 前期準備
主程序
public static void main(String[] args) {
System.out.println("現在開始初始化容器");
ApplicationContext ac = new ClassPathXmlApplicationContext("application.xml");
System.out.println("容器初始化成功");
Person person = (Person)ac.getBean("person");
System.out.println(person);
}
xml
<bean id="person" class="com.atguigu.pojo.Person" init-method="myInit" destroy-method="myDestory">
<property name="name" value="尚硅谷"/>
<property name="address" value="武漢"/>
<property name="age" value="22"/>
</bean>
結果運行
容器初始化成功
Person{name='尚硅谷'搂鲫, address='武漢'傍药, age=22, beanFactory=org.springframework.beans.factory.support.DefaultListableBeanFactory@694e1548: defining beans [person]; root of factory hierarchy魂仍, beanName='person'}
(1) 配置的<bean>是怎么轉換成Person對象的呢拐辽?
(2) 配置的value值是怎樣設置到person中的呢?
(3) spring容器怎樣管理該對象的呢擦酌?
請帶著這些問題繼續(xù)往下看
4.3 基礎組件
- BeanFactory
spring底層容器俱诸,定義了最基本的容器功能,注意區(qū)分FactoryBean
- ApplicationContext
擴展于BeanFactory赊舶,擁有更豐富的功能睁搭。例如:添加事件發(fā)布機制、父子級容器笼平,一般都是直接使用ApplicationContext园骆。
- Resource
bean配置文件,一般為xml文件寓调⌒客伲可以理解為保存bean信息的文件。
- BeanDefinition
beandifinition定義了bean的基本信息夺英,根據它來創(chuàng)造bean
4.4 容器啟動過程
(1)資源定位:找到配置文件Resource
(2)BeanDefinition載入和解析: 將配置文件解析成BeanDefinition
(3)BeanDefinition注冊:將BeanDefinition向Map中注冊 Map<name晌涕,beandefinition>
(4)bean的實例化和依賴注入
此過程由getBean()方法觸發(fā)
創(chuàng)造 bean
實現依賴注入
此過程根據上述的BeanDefition滋捶,
(1)通過反射或者Cglib的方式創(chuàng)造bean
(2)根據配置的依賴將所需要的bean注入進來,此過程會遞歸調用getBean()方法渐排。
(3)根據bean的scope決定是否緩存該Bean炬太,一般情況為單例灸蟆。容器會緩存該對象驯耻。
這個過程大概可以理解為
將原材料進行加工,創(chuàng)造可以直接利用的產品炒考。
到此可缚,spring容器就可以對外提供服務了。
5. 總結
容器啟動的過程可以分為2大步:
(1)獲取斋枢、解析帘靡、注冊配置信息,將配置的文件信息轉換Map<name瓤帚,beanDefinition>
(2)根據上述的Map<name描姚,beanDefinition>去實例化bean,并完成以來注入
以上是根據傳統的xml形式配置Bean戈次,現在很少用轩勘,現在用的比較多的是注解和javaConfig的形式配置,但換湯不換藥怯邪,只是容器獲取Map<name绊寻,beanDefition>的過程變了而已。這也是容器容器初始化步驟細化的一個好處悬秉。易于擴展澄步。
spring容器的啟動過程由spring框架封裝好了,并不需要我們手動編程和泌,但理解其啟動原理村缸,更有利于我們對spring的使用和擴展。