一、前言
我之前寫過一個專欄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)的向某個對象提供它所需要的其他對象把篓。這一點是通過DI
(Dependency Injection
,依賴注入)來實現(xiàn)的韧掩。比如對象A
需要操作數(shù)據(jù)庫,以前我們總是要在A
中自己編寫代碼來獲得一個Connection
對象坊谁,有了 spring
我們就只需要告訴spring
,A
中需要一個Connection
呜袁,至于這個Connection
怎么構(gòu)造简珠,何時構(gòu)造虹钮,A
不需要知道。在系統(tǒng)運行時芙粱,spring
會在適當?shù)臅r候制造一個Connection
氧映,然后像打針一樣春畔,注射到A
當中岛都,這樣就完成了對各個對象之間關系的控制。A
需要依賴 Connection
才能正常運行择份,而這個Connection
是由spring
注入到A
中的,依賴注入的名字就這么來的荣赶。
三鸽斟、Spring IoC總覽
Spring
的IoC
容器在實現(xiàn)控制反轉(zhuǎn)和依賴注入的過程中,可以劃分為兩個階段:
- 容器啟動階段
-
Bean
實例化階段
這兩個階段中,IoC
容器分別作了以下這些事情:
這里可能會完全搞不懂上面這些東西是什么,不過不要緊,這里只是給大家一個基本的印象,知道
Spring
的IoC
容器在實現(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
這兩個容器間的關系如下圖:
我們可以看到,
ApplicationContext
是BeanFactory
的子類蝉揍,所以,ApplicationContext
可以看做更強大的BeanFactory
杖刷,他們兩個之間的區(qū)別如下:
-
BeanFactory
滑燃”砭剑基礎類型IoC容器
乐严,提供完整的IoC
服務支持昂验。如果沒有特殊指定既琴,默認采用延遲初始化策略(lazy-load
)。只有當客戶端對象需要訪問容器中的某個受管對象的時候填物,才對該受管對象進行初始化以及依賴注入操作滞磺。所以击困,相對來說阅茶,容器啟動初期速度較快蹦浦,所需要的資源有限盲镶。對于資源有限溉贿,并且功能要求不是很嚴格的場景宇色,BeanFactory
是比較合適的IoC容器
選擇。 -
ApplicationContext
植影。ApplicationContext
在BeanFactory
的基礎上構(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
配置文件插爹,就是他的子類XmlBeanDefinitionReader
,XmlBeanDefinitionReader
負責讀取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
接口甲棍,這樣我們才能通過容器注冊對象和獲取對象简识。我們通過BeanDefinitionRegistry
的rsgisterBeanDefinition(BeanDefinition beandefinition)
方法來進行Bean的注冊
。
打個比方說感猛,BeanDefinitionRegistry
就像圖書館的書架七扰,所有的書是放在書架上的。雖然你還書或者借書都是跟圖書館(也就是BeanFactory
)打交道唱遭,但書架才是圖書館存放各類圖書的地方戳寸。所以,書架相對于圖書館來說拷泽,就是它的BeanDefinitionRegistry
。
下面是BeanFactory
袖瞻、BeanDefinitionRegistry
以及DefaultListableBeanFactory
(一個具體的容器)的關系圖:
好了司致,我們來總結(jié)一下一個Bean
是如何注冊到容器中,然后被我們獲取的:
首先我們需要配置該Bean
的依賴信息聋迎,通常我們配置在xml
文件中脂矫,然后我們通過XmlBeanDefinitionReader
讀取文件內(nèi)容,然后將文件內(nèi)容映射到相應的BeanDefinition
霉晕,然后我們可以通過BeanFactory
和BeanDefinitionRegistry
的具體實現(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原理