細說Spring——IoC詳解(IoC概覽)

一、前言

我之前寫過一個專欄Sping+SpringMVC+Mybatis學習筆記悠砚,這個專欄主是我在學習SSM的一些筆記晓勇,但是這個專欄中只講解了怎么使用Spring框架灌旧,但是卻很少涉及Spring原理性的知識,為了彌補這個不足枢泰,我決定再開一個專欄,專門記錄我對Spring原理性知識的了解衡蚂,希望能夠幫助Spring的初學者快速建立起對Spring框架的認知。
這里的很多知識都是通過閱讀《Spring揭秘》和Spring官方文檔總結(jié)而來毛甲,這里也著重推薦一下《Spring揭秘》這本書,這本書讓我對Spring框架的理解有了很大的提高玻募。

二、IoC思想

首先想說說IoC(Inversion of Control补箍,控制倒轉(zhuǎn))啸蜜。這是spring的核心辈挂,貫穿始終。所謂IoC终蒂,對于spring框架來說,就是由spring來負責控制對象的生命周期和對象間的關系拇泣。這是什么意思呢,舉個簡單的例子霉翔,我們是如何找女朋友的?常見的情況是债朵,我們到處去看哪里有長得漂亮身材又好的mm,然后打聽她們的興趣愛好序芦、qq號臭杰、電話號谚中、ip號、iq號………宪塔,想辦法認識她們,投其所好送其所要蝌麸,然后嘿嘿……這個過程是復雜深奧的,我們必須自己設計和面對每個環(huán)節(jié)来吩。傳統(tǒng)的程序開發(fā)也是如此,在一個對象中弟疆,如果要使用另外的對象,就必須得到它(自己new一個怠苔,或者從JNDI中查詢一個)同廉,使用完之后還要將對象銷毀(比如Connection等),對象始終會和其他的接口或類藕合起來迫肖。

那么IoC是如何做的呢?有點像通過婚介找女朋友蟆湖,在我和女朋友之間引入了一個第三者:婚姻介紹所∮缃颍婚介管理了很多男男女女的資料,我可以向婚介提出一個列表伦仍,告訴它我想找個什么樣的女朋友,比如長得像李嘉欣隧枫,身材像林熙雷,唱歌像周杰倫悠垛,速度像卡洛斯娜谊,技術(shù)像齊達內(nèi)之類的,然后婚介就會按照我們的要求纱皆,提供一個mm芭商,我們只需要去和她談戀愛派草、結(jié)婚就行了铛楣。簡單明了,如果婚介給我們的人選不符合要求簸州,我們就會拋出異常。整個過程不再由我自己控制搏存,而是有婚介這樣一個類似容器的機構(gòu)來控制矢洲。Spring所倡導的開發(fā)方式就是如此璧眠,所有的類都會在spring容器中登記,告訴spring你是個什么東西袁滥,你需要什么東西,然后spring會在系統(tǒng)運行到適當?shù)臅r候呻拌,把你要的東西主動給你睦焕,同時也把你交給其他需要你的東西藐握。所有的類的創(chuàng)建垃喊、銷毀都由spring來控制,也就是說控制對象生存周期的不再是引用它的對象本谜,而是spring。對于某個具體的對象而言溜在,以前是它控制其他對象,現(xiàn)在是所有對象都被spring控制掖肋,所以這叫控制反轉(zhuǎn)赏参。

IoC的一個重點是在系統(tǒng)運行中志笼,動態(tài)的向某個對象提供它所需要的其他對象把篓。這一點是通過DIDependency Injection,依賴注入)來實現(xiàn)的韧掩。比如對象A需要操作數(shù)據(jù)庫,以前我們總是要在A中自己編寫代碼來獲得一個Connection對象坊谁,有了 spring我們就只需要告訴springA中需要一個Connection呜袁,至于這個Connection怎么構(gòu)造简珠,何時構(gòu)造虹钮,A不需要知道。在系統(tǒng)運行時芙粱,spring會在適當?shù)臅r候制造一個Connection氧映,然后像打針一樣春畔,注射到A當中岛都,這樣就完成了對各個對象之間關系的控制。A需要依賴 Connection才能正常運行择份,而這個Connection是由spring注入到A中的,依賴注入的名字就這么來的荣赶。

三鸽斟、Spring IoC總覽

SpringIoC容器在實現(xiàn)控制反轉(zhuǎn)和依賴注入的過程中,可以劃分為兩個階段:

  • 容器啟動階段
  • Bean實例化階段

這兩個階段中,IoC容器分別作了以下這些事情:

這里寫圖片描述

這里可能會完全搞不懂上面這些東西是什么,不過不要緊,這里只是給大家一個基本的印象,知道SpringIoC容器在實現(xiàn)控制反轉(zhuǎn)和依賴注入功能的時候不是一蹴而就的,也分了兩個階段,并且大致對兩個階段所做的事情有一個印象,下面我要對每一個階段的每一項工作都進行深入的講解,請大家耐心的看下去.

四、容器啟動階段的講解

1富蓄、IOC的技術(shù)實現(xiàn)方式

“伙計,來杯啤酒躏吊!”當你來到酒吧氛改,想要喝杯啤酒的時候,通常會直接招呼服務生疆导,讓他為你
送來一杯清涼解渴的啤酒。同樣地澈段,作為被注入對象舰攒,要想讓IoC容器為其提供服務败富,并
將所需要的被依賴對象送過來孔祸,也需要通過某種方式通知對方蹋半。

  • 如果你是酒吧的常客泽本,或許你剛坐好规丽,服務生已經(jīng)將你最常喝的啤酒放到了你面前
  • 如果你是初次或偶爾光顧赌莺,也許你坐下之后還要招呼服務生雄嚣,“Waiter,Tsingdao, please.”
  • 還有一種可能缓升,你根本就不知道哪個牌子是哪個牌子港谊,這時歧寺,你只能打手勢或干脆畫出商標
    圖來告訴服務生你到底想要什么了吧斜筐!

不管怎樣顷链,你終究會找到一種方式來向服務生表達你的需求嗤练,以便他為你提供適當?shù)姆丈诽АD敲锤锎穑?code>IoC模式中,被注入對象又是通過哪些方式來通知IoC容器為其提供適當服務的呢?
常用的有兩種方式:構(gòu)造方法注入和setter方法注入旗扑,還有一種已經(jīng)退出歷史舞臺的接口注入方式臀防,下面就比較一下三種注入方式:

  • 接口注入袱衷。從注入方式的使用上來說致燥,接口注入是現(xiàn)在不甚提倡的一種方式嫌蚤,基本處于“退
    役狀態(tài)”脱吱。因為它強制被注入對象實現(xiàn)不必要的接口箱蝠,帶有侵入性垦垂。而構(gòu)造方法注入和setter
    方法注入則不需要如此劫拗。
  • 構(gòu)造方法注入杨幼。這種注入方式的優(yōu)點就是,對象在構(gòu)造完成之后汉嗽,即已進入就緒狀態(tài)饼暑,可以
    馬上使用。缺點就是诚纸,當依賴對象比較多的時候,構(gòu)造方法的參數(shù)列表會比較長井辆。而通過反
    射構(gòu)造對象的時候杯缺,對相同類型的參數(shù)的處理會比較困難萍肆,維護和使用上也比較麻煩匾鸥。而且
    在Java中勿负,構(gòu)造方法無法被繼承奴愉,無法設置默認值锭硼。對于非必須的依賴處理檀头,可能需要引入多個構(gòu)造方法暑始,而參數(shù)數(shù)量的變動可能造成維護上的不便廊镜。
  • setter方法注入嗤朴。因為方法可以命名雹姊,所以setter方法注入在描述性上要比構(gòu)造方法注入好一些吱雏。 另外坎背,setter方法可以被繼承得滤,允許設置默認值眨业,而且有良好的IDE支持龄捡。缺點當然就是對象無法在構(gòu)造完成后馬上進入就緒狀態(tài)聘殖。

其實奸腺,這些操作都是由IoC容器來做的突照,我們所要做的讹蘑,就是調(diào)用IoC容器來獲得對象而已座慰。

2角骤、IoC容器及IoC容器如何獲取對象間的依賴關系

Spring中提供了兩種IoC容器:

  • BeanFactory
  • ApplicationContext

這兩個容器間的關系如下圖:

這里寫圖片描述

我們可以看到,ApplicationContextBeanFactory的子類蝉揍,所以,ApplicationContext可以看做更強大的BeanFactory杖刷,他們兩個之間的區(qū)別如下:

  • BeanFactory滑燃”砭剑基礎類型IoC容器乐严,提供完整的IoC服務支持昂验。如果沒有特殊指定既琴,默認采用延遲初始化策略(lazy-load)。只有當客戶端對象需要訪問容器中的某個受管對象的時候填物,才對該受管對象進行初始化以及依賴注入操作滞磺。所以击困,相對來說阅茶,容器啟動初期速度較快蹦浦,所需要的資源有限盲镶。對于資源有限溉贿,并且功能要求不是很嚴格的場景宇色,BeanFactory是比較合適的IoC容器選擇。
  • ApplicationContext植影。ApplicationContextBeanFactory的基礎上構(gòu)建思币,是相對比較高級的容器實現(xiàn)谷饿,除了擁有BeanFactory的所有支持,ApplicationContext還提供了其他高級特性毅哗,比如事件發(fā)布虑绵、國際化信息支持等翅睛,ApplicationContext所管理的對象疏旨,在該類型容器啟動之后充石,默認全部初始化并綁定完成。所以坷剧,相對于BeanFactory來說,ApplicationContext要求更多的系統(tǒng)資源狞尔,同時偏序,因為在啟動時就完成所有初始化研儒,容
    器啟動時間較之BeanFactory也會長一些。在那些系統(tǒng)資源充足冲呢,并且要求更多功能的場景中招狸,ApplicationContext類型的容器是比較合適的選擇敬拓。

但是我們無論使用哪個容器,我們都需要通過某種方法告訴容器關于對象依賴的信息瓢颅,只有這樣恩尾,容器才能合理的創(chuàng)造出對象,否則挽懦,容器自己也不知道哪個對象依賴哪個對象,如果胡亂注入,那不是創(chuàng)造出一個四不像冀偶。理論上將我們可以通過任何方式來告訴容器對象依賴的信息醒第,比如我們可以通過語音告訴他,但是并沒有人實現(xiàn)這樣的代碼进鸠,所以我們還是老老實實使用Spring提供的方法吧:

  • 通過最基本的文本文件來記錄被注入對象和其依賴對象之間的對應關系
  • 通過描述性較強的XML文件格式來記錄對應信息
  • 通過編寫代碼的方式來注冊這些對應信息
  • 通過注解方式來注冊這些對應信息

雖然提供了四種方式稠曼,但是我們一般只使用xml文件方式和注解方式,所以客年,就重點講解這兩種方式霞幅。

3、萬里長征第一步:加載配置文件信息

我們在介紹了一些基本的概念后量瓜,終于要迎來容器創(chuàng)造對象的第一步司恳,那就是加載配置文件信息,我們已經(jīng)知道我們主要通過xml文件和注解的方式來告訴容器對象間的依賴信息绍傲,那么容器怎么才能從xml配置文件中得到對象依賴的信息呢扔傅?且聽我慢慢道來。(這里的容器指的是BeanFactory烫饼,至于ApplicationContext猎塞,以后會有相應的講解)

BeanFactory容器中,每一個注入對象都對應一個BeanDefinition實例對象杠纵,該實例對象負責保存注入對象的所有必要信息荠耽,包括其對應的對象的class類型、是否是抽象類淡诗、構(gòu)造方法參數(shù)以及其他屬性等骇塘。當客戶端向BeanFactory請求相應對象的時候,BeanFactory會通過這些信息為客戶端返回一個完備可用的對象實例韩容。

那么BeanDefinition實例對象的信息是從哪而來呢款违?這里就要引出一個專門加載解析配置文件的類了,他就是BeanDefinitionReader群凶,對應到xml配置文件插爹,就是他的子類XmlBeanDefinitionReaderXmlBeanDefinitionReader負責讀取Spring指定格式的XML配置文件并解析请梢,之后將解析后的文件內(nèi)容映射到相應的BeanDefinition赠尾。在我們了解了怎么得到對象依賴的信息,并知道這些信息最終保存在BeanDefinition之后毅弧,我們可能會想气嫁,那么容器怎么通過這些信息創(chuàng)造出一個可用的對象了呢?

4够坐、籠統(tǒng)講解容器中對象的創(chuàng)建和獲取

我們把容器創(chuàng)造一個對象的過程稱為Bean的注冊寸宵,實現(xiàn)Bean的注冊的接口為BeanDefinitionRegistry崖面,其實BeanFactory只是一個接口,他定義了如何獲取容器內(nèi)對象的方法梯影,我們所說的BeanFactory容器巫员,其實是這個接口的是實現(xiàn)類,但是具體的BeanFactory實現(xiàn)類同時也會實現(xiàn)BeanDefinitionRegistry接口甲棍,這樣我們才能通過容器注冊對象和獲取對象简识。我們通過BeanDefinitionRegistryrsgisterBeanDefinition(BeanDefinition beandefinition)方法來進行Bean的注冊

打個比方說感猛,BeanDefinitionRegistry就像圖書館的書架七扰,所有的書是放在書架上的。雖然你還書或者借書都是跟圖書館(也就是BeanFactory)打交道唱遭,但書架才是圖書館存放各類圖書的地方戳寸。所以,書架相對于圖書館來說拷泽,就是它的BeanDefinitionRegistry

下面是BeanFactory袖瞻、BeanDefinitionRegistry以及DefaultListableBeanFactory(一個具體的容器)的關系圖:

這里寫圖片描述

好了司致,我們來總結(jié)一下一個Bean是如何注冊到容器中,然后被我們獲取的:
首先我們需要配置該Bean的依賴信息聋迎,通常我們配置在xml文件中脂矫,然后我們通過XmlBeanDefinitionReader讀取文件內(nèi)容,然后將文件內(nèi)容映射到相應的BeanDefinition霉晕,然后我們可以通過BeanFactoryBeanDefinitionRegistry的具體實現(xiàn)類,比如DefaultListableBeanFactory實現(xiàn)Bean的注冊和獲取庭再。這里放一段代碼來演示一下這個過程:

public static void main(String[] args)
{
    //創(chuàng)建一個容器
     DefaultListableBeanFactory beanRegistry = new DefaultListableBeanFactory();
     //調(diào)用方法實現(xiàn)Bean的注冊
     BeanFactory container = (BeanFactory)bindViaCode(beanRegistry);
     //通過容器獲取對象
     FXNewsProvider newsProvider =  (FXNewsProvider)container.getBean("djNewsProvider");
}
public static BeanFactory bindViaCode(BeanDefinitionRegistry registry)
{
     AbstractBeanDefinition newsProvider = new RootBeanDefinition(FXNewsProvider.class,true);
 
     AbstractBeanDefinition newsListener = new RootBeanDefinition(DowJonesNewsListener.class,true);
 
     AbstractBeanDefinition newsPersister = new RootBeanDefinition(DowJonesNewsPersister.class,true);
 
     // 將bean定義注冊到容器中
     registry.registerBeanDefinition("djNewsProvider", newsProvider);
     registry.registerBeanDefinition("djListener", newsListener);
     registry.registerBeanDefinition("djPersister", newsPersister);
     // 指定依賴關系
     // 1. 可以通過構(gòu)造方法注入方式
     ConstructorArgumentValues argValues = new ConstructorArgumentValues();
     argValues.addIndexedArgumentValue(0, newsListener);
     argValues.addIndexedArgumentValue(1, newsPersister);
     newsProvider.setConstructorArgumentValues(argValues);
     // 2. 或者通過setter方法注入方式
     MutablePropertyValues propertyValues = new MutablePropertyValues();
     propertyValues.addPropertyValue(new ropertyValue("newsListener",newsListener));
     propertyValues.addPropertyValue(new PropertyValue("newPersistener",newsPersister));
     newsProvider.setPropertyValues(propertyValues);
     // 綁定完成
     return (BeanFactory)registry;
} 

第一篇博客就先講到這里,第二篇博客將繼續(xù)講解Spring中的IoC原理

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末牺堰,一起剝皮案震驚了整個濱河市拄轻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伟葫,老刑警劉巖恨搓,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異筏养,居然都是意外死亡斧抱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門渐溶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辉浦,“玉大人,你說我怎么就攤上這事茎辐∠芙迹” “怎么了眉睹?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長废膘。 經(jīng)常有香客問我竹海,道長,這世上最難降的妖魔是什么丐黄? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任斋配,我火速辦了婚禮,結(jié)果婚禮上灌闺,老公的妹妹穿的比我還像新娘艰争。我一直安慰自己,他們只是感情好桂对,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布甩卓。 她就那樣靜靜地躺著,像睡著了一般蕉斜。 火紅的嫁衣襯著肌膚如雪逾柿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天宅此,我揣著相機與錄音机错,去河邊找鬼。 笑死父腕,一個胖子當著我的面吹牛弱匪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播璧亮,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼萧诫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了枝嘶?” 一聲冷哼從身側(cè)響起帘饶,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎躬络,沒想到半個月后尖奔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡穷当,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年提茁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馁菜。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡茴扁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出汪疮,到底是詐尸還是另有隱情峭火,我是刑警寧澤毁习,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站卖丸,受9級特大地震影響纺且,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜稍浆,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一载碌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧衅枫,春花似錦嫁艇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至益楼,卻和暖如春猾漫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背偏形。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工静袖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人俊扭。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像坠陈,于是被迫代替她去往敵國和親萨惑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容