Java面試題:Spring IOC容器啟動流程附源碼

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ī)范铡原,如下圖所示


11.png

具體而言,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的類繼承結構


12.jpg

關鍵的幾個類已經用紅色箭頭標注了出來。

  • 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 容器啟動流程的不同階段

為了更好的進行講解,可以將容器啟動的整個流程劃分為以下五個階段


33.png

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


191.jpg
  • ApplicationContext

擴展于BeanFactory赊舶,擁有更豐富的功能睁搭。例如:添加事件發(fā)布機制、父子級容器笼平,一般都是直接使用ApplicationContext园骆。


12.jpg
  • Resource

bean配置文件,一般為xml文件寓调⌒客伲可以理解為保存bean信息的文件。


13.jpg
  • BeanDefinition

beandifinition定義了bean的基本信息夺英,根據它來創(chuàng)造bean


14.jpg

4.4 容器啟動過程

(1)資源定位:找到配置文件Resource

(2)BeanDefinition載入和解析: 將配置文件解析成BeanDefinition

(3)BeanDefinition注冊:將BeanDefinition向Map中注冊 Map<name晌涕,beandefinition>

(4)bean的實例化和依賴注入

 此過程由getBean()方法觸發(fā)
15.jpg
16.jpg

創(chuàng)造 bean


17.jpg

實現依賴注入


18.jpg
19.jpg

此過程根據上述的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,并完成以來注入


44.jpg

以上是根據傳統的xml形式配置Bean戈次,現在很少用轩勘,現在用的比較多的是注解和javaConfig的形式配置,但換湯不換藥怯邪,只是容器獲取Map<name绊寻,beanDefition>的過程變了而已。這也是容器容器初始化步驟細化的一個好處悬秉。易于擴展澄步。

spring容器的啟動過程由spring框架封裝好了,并不需要我們手動編程和泌,但理解其啟動原理村缸,更有利于我們對spring的使用和擴展。
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末武氓,一起剝皮案震驚了整個濱河市王凑,隨后出現的幾起案子,更是在濱河造成了極大的恐慌聋丝,老刑警劉巖索烹,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異弱睦,居然都是意外死亡百姓,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門况木,熙熙樓的掌柜王于貴愁眉苦臉地迎上來垒拢,“玉大人旬迹,你說我怎么就攤上這事∏罄啵” “怎么了奔垦?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長尸疆。 經常有香客問我椿猎,道長,這世上最難降的妖魔是什么寿弱? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任犯眠,我火速辦了婚禮,結果婚禮上症革,老公的妹妹穿的比我還像新娘筐咧。我一直安慰自己,他們只是感情好噪矛,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布量蕊。 她就那樣靜靜地躺著,像睡著了一般艇挨。 火紅的嫁衣襯著肌膚如雪残炮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天雷袋,我揣著相機與錄音吉殃,去河邊找鬼。 笑死楷怒,一個胖子當著我的面吹牛蛋勺,可吹牛的內容都是我干的。 我是一名探鬼主播鸠删,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼抱完,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了刃泡?” 一聲冷哼從身側響起巧娱,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎烘贴,沒想到半個月后禁添,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡桨踪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年老翘,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡铺峭,死狀恐怖墓怀,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情卫键,我是刑警寧澤傀履,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站莉炉,受9級特大地震影響钓账,放射性物質發(fā)生泄漏。R本人自食惡果不足惜呢袱,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一官扣、第九天 我趴在偏房一處隱蔽的房頂上張望翅敌。 院中可真熱鬧羞福,春花似錦、人聲如沸蚯涮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遭顶。三九已至张峰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間棒旗,已是汗流浹背喘批。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铣揉,地道東北人饶深。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像逛拱,于是被迫代替她去往敵國和親敌厘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內容