一.什么是Ioc /DI
ioc容器:主要是完成了對象的創(chuàng)建和依賴的管理注入
二.Spring IoC體系結(jié)構(gòu)
?(1)BeanFactory
?????????????Spring?Bean的創(chuàng)建是典型的工廠模式,這一系列的Bean工廠,也即IOC容器為開發(fā)者管理對象間的依賴關(guān)系提供了很多便利和基礎(chǔ)服務(wù),在Spring中有許多的IOC容器的實(shí)現(xiàn)供用戶選擇和使用,其相互關(guān)系如下:
其中BeanFactory作為最頂層的一個(gè)接口類,它定義了IOC容器的基本功能規(guī)范,BeanFactory?有三個(gè)子類:ListableBeanFactory、HierarchicalBeanFactory?和AutowireCapableBeanFactory。但是從上圖中我們可以發(fā)現(xiàn)最終的默認(rèn)實(shí)現(xiàn)類是?DefaultListableBeanFactory,他實(shí)現(xiàn)了所有的接口。那為何要定義這么多層次的接口呢?查閱這些接口的源碼和說明發(fā)現(xiàn),每個(gè)接口都有他使用的場合,它主要是為了區(qū)分在?Spring?內(nèi)部在操作過程中對象的傳遞和轉(zhuǎn)化過程中,對對象的數(shù)據(jù)訪問所做的限制。例如?ListableBeanFactory?接口表示這些?Bean?是可列表的,而?HierarchicalBeanFactory?表示的是這些?Bean?是有繼承關(guān)系的,也就是每個(gè)Bean?有可能有父?Bean。AutowireCapableBeanFactory?接口定義?Bean?的自動(dòng)裝配規(guī)則。這四個(gè)接口共同定義了?Bean?的集合、Bean?之間的關(guān)系、以及?Bean?行為.
最基本的IOC容器接口BeanFactory
在BeanFactory里只對IOC容器的基本行為作了定義,根本不關(guān)心你的bean是如何定義怎樣加載的。正如我們只關(guān)心工廠里得到什么的產(chǎn)品對象,至于工廠是怎么生產(chǎn)這些對象的,這個(gè)基本的接口不關(guān)心。
而要知道工廠是如何產(chǎn)生對象的,我們需要看具體的IOC容器實(shí)現(xiàn),spring提供了許多IOC容器的實(shí)現(xiàn)。比如XmlBeanFactory,ClasspathXmlApplicationContext等。其中XmlBeanFactory就是針對最基本的ioc容器的實(shí)現(xiàn),這個(gè)IOC容器可以讀取XML文件定義的BeanDefinition(XML文件中對bean的描述),下面以ApplicationContext說明
ApplicationContext是spring提供的一個(gè)高級的IoC容器,它除了提供IoC容器的基本功能外,還未用戶提供了以下功能
?????????1.??支持信息源,可以實(shí)現(xiàn)國際化。(實(shí)現(xiàn)MessageSource接口)
? ? ? ? ?2.??訪問資源。(實(shí)現(xiàn)ResourcePatternResolver接口,這個(gè)后面要講)
?????????3.??支持應(yīng)用事件。(實(shí)現(xiàn)ApplicationEventPublisher接口)
(2)?BeanDefinition
SpringIOC容器管理了我們定義的各種Bean對象及其相互的關(guān)系,Bean對象在Spring實(shí)現(xiàn)中是以BeanDefinition來描述的,其繼承體系如下
bean的解析過程非常復(fù)雜,功能被分的很細(xì),因?yàn)檫@里需要被擴(kuò)展的地方很多,必須保證有足夠的靈活性,以應(yīng)對可能的變化.bean的解析主要就是對?Spring?配置文件的解析。這個(gè)解析過程主要通過下圖中的類完成:
三.IoC的初始化
? ?IoC容器的初始化包括BeanDefinition的Resource定位,載入和注冊這三個(gè)基本的過程.以ApplicationContext為例進(jìn)行分析。ApplicationContext系列容器也許是我們最熟悉的,因?yàn)閣eb項(xiàng)目中使用的XmlWebApplicationContext就屬于這個(gè)繼承體系,還有ClasspathXmlApplicationContext等,其繼承體系如下圖所示:
2.FileSystemXmlApplicationContext的IOC容器流程
1???ApplicationContext?=new?FileSystemXmlApplicationContext(xmlPath);
構(gòu)造函數(shù)
2、設(shè)置資源加載器和資源定位
通過分析FileSystemXmlApplicationContext的源代碼可以知道,在創(chuàng)建FileSystemXmlApplicationContext容器時(shí),構(gòu)造方法做以下兩項(xiàng)重要工作:
首先,調(diào)用父類容器的構(gòu)造方法(super(parent)方法)為容器設(shè)置好Bean資源加載器。
然后,再調(diào)用父類AbstractRefreshableConfigApplicationContext的setConfigLocations(configLocations)方法設(shè)置Bean定義資源文件的定位路徑。
通過追蹤FileSystemXmlApplicationContext的繼承體系,發(fā)現(xiàn)其父類的父類AbstractApplicationContext中初始化IoC容器所做的主要源碼如下
靜態(tài)初始化,整個(gè)創(chuàng)建過程執(zhí)行一次
AbstractApplicationContext構(gòu)造方法中調(diào)用PathMatchingResourcePatternResolver的構(gòu)造方法創(chuàng)建Spring資源加載器:
設(shè)置spring資源加載器
在設(shè)置容器的資源加載之后,接下來FileSystemXmlApplicationContent執(zhí)行setConfigLocations方法通過調(diào)用其父類AbstractRefreshableConfigApplicationContext的方法進(jìn)行對bean定義資源文件的定位,源碼分析如下
通過這倆個(gè)方法可以就看出,我們可以使用一個(gè)字符串來配置多個(gè)spring bean定義資源文件,也可以使用字符串?dāng)?shù)組,即下面?zhèn)z種方式
a.?ClasspathResource?res?=?new?ClasspathResource(“a.xml,b.xml,……”);
多個(gè)資源文件路徑之間可以是用”?,;?/t/n”等分隔。
b.????ClasspathResource?res?=?new?ClasspathResource(newString[]{“a.xml”,”b.xml”,……});
至此,Spring?IoC容器在初始化時(shí)將配置的Bean定義資源文件定位為Spring封裝的Resource。
3、AbstractApplicationContext的refresh函數(shù)載入Bean定義過程:
Spring?IoC容器對Bean定義資源的載入是從refresh()函數(shù)開始的,refresh()是一個(gè)模板方法,refresh()函數(shù)的作用在于:在創(chuàng)建Ioc容器前,如果已經(jīng)有容器存在,則需要把已有的容器銷毀和關(guān)閉,以保證在refresh之后使用的是新建立起來的IoC容器。refresh的作用類似于對IoC容器的重啟,在新建立好的容器中對容器進(jìn)行初始化,對Bean定義資源進(jìn)行載入。
FileSystemXmlApplicationContext通過調(diào)用其父類的AbstractApplicationContext的refresh()函數(shù)啟動(dòng)整個(gè)ioc容器對Bea定義的載入的過程
refresh()方法主要為IoC容器Bean的生命周期管理提供條件,Spring?IoC容器載入Bean定義資源文件從其子類容器的refreshBeanFactory()方法啟動(dòng),所以整個(gè)refresh()中“ConfigurableListableBeanFactory?beanFactory?=obtainFreshBeanFactory();”這句以后代碼的都是注冊容器的信息源和生命周期事件,載入過程就是從這句代碼啟動(dòng)。
?refresh()方法的作用是:在創(chuàng)建IoC容器前,如果已經(jīng)有容器存在,則需要把已有的容器銷毀和關(guān)閉,以保證在refresh之后使用的是新建立起來的IoC容器。refresh的作用類似于對IoC容器的重啟,在新建立好的容器中對容器進(jìn)行初始化,對Bean定義資源進(jìn)行載入
AbstractApplicationContext的obtainFreshBeanFactory()方法調(diào)用子類容器的refreshBeanFactory()方法,啟動(dòng)容器載入Bean定義資源文件的過程,代碼如下:
AbstractApplicationContext子類的refreshBeanFactory()方法:
?AbstractApplicationContext類中只抽象定義了refreshBeanFactory()方法,容器真正調(diào)用的是其子類AbstractRefreshableApplicationContext實(shí)現(xiàn)的????refreshBeanFactory()方法,方法的源碼如下:
在這個(gè)方法中,先判斷BeanFactory是否存在,如果存在則先銷毀beans并關(guān)閉beanfactor,接著創(chuàng)建DefaultListableBeanFactory,并調(diào)用loadBeanDefinitions(beanFactory)裝載bean定義。
5.AbstractRefreshableApplicationContext子類的loadBeanDefintions方法:
AbstractRefreshableApplicationContext中只定義了抽象的loadBeanDefinitions方法,容器真正調(diào)用的是其子類AbstractXmlApplicationContext對該方法的實(shí)現(xiàn),AbstractXmlApplicationContext的主要源碼如下:
loadBeanDefinitions方法同樣是抽象方法,是由其子類實(shí)現(xiàn)的,也即在AbstractXmlApplicationContext中。
Xml?Bean讀取器(XmlBeanDefinitionReader)調(diào)用其父類AbstractBeanDefinitionReader的?reader.loadBeanDefinitions方法讀取Bean定義資源。
由于我們使用FileSystemXmlApplicationContext作為例子分析,因此getConfigResources的返回值為null,因此程序執(zhí)行reader.loadBeanDefinitions(configLocations)分支。
6、AbstractBeanDefinitionReader讀取Bean定義資源:
AbstractBeanDefinitionReader的loadBeanDefinitions方法源碼如下
在其抽象父類AbstractBeanDefinitionReader中定義了載入過程
loadBeanDefinitions(Resource...resources)方法和上面分析的3個(gè)方法類似,同樣也是調(diào)用XmlBeanDefinitionReader的loadBeanDefinitions方法。
從對AbstractBeanDefinitionReader的loadBeanDefinitions方法源碼分析可以看出該方法做了以下兩件事:
首先,調(diào)用資源加載器的獲取資源方法resourceLoader.getResource(location),獲取到要加載的資源。
其次,真正執(zhí)行加載功能是其子類XmlBeanDefinitionReader的loadBeanDefinitions方法。
7、資源加載器獲取要讀入的資源:
XmlBeanDefinitionReader通過調(diào)用其父類DefaultResourceLoader的getResource方法獲取要加載的資源,其源碼如下
FileSystemXmlApplicationContext容器提供了getResourceByPath方法的實(shí)現(xiàn),就是為了處理既不是classpath標(biāo)識(shí),又不是URL標(biāo)識(shí)的Resource定位這種情況。
這樣,就可以從文件系統(tǒng)路徑上對IOC?配置文件進(jìn)行加載?-?當(dāng)然我們可以按照這個(gè)邏輯從任何地方加載,在Spring?中我們看到它提供?的各種資源抽象,比如ClassPathResource,?URLResource,FileSystemResource?等來供我們使用。上面我們看到的是定位Resource?的一個(gè)過程,而這只是加載過程的一部分
8、XmlBeanDefinitionReader加載Bean定義資源:
?Bean定義的Resource得到了
繼續(xù)回到XmlBeanDefinitionReader的loadBeanDefinitions(Resource?…)方法看到代表bean文件的資源定義以后的載入過程。
通過源碼分析,載入Bean定義資源文件的最后一步是將Bean定義資源轉(zhuǎn)換為Document對象,該過程由documentLoader實(shí)現(xiàn)
其實(shí)現(xiàn)類DefaultDocumentLoader具體源碼
該解析過程調(diào)用JavaEE標(biāo)準(zhǔn)的JAXP標(biāo)準(zhǔn)進(jìn)行處理。
至此Spring?IoC容器根據(jù)定位的Bean定義資源文件,將其加載讀入并轉(zhuǎn)換成為Document對象過程完成。
接下來我們要繼續(xù)分析Spring?IoC容器將載入的Bean定義資源文件轉(zhuǎn)換為Document對象之后,是如何將其解析為Spring?IoC管理的Bean對象并將其注冊到容器中的。
10、XmlBeanDefinitionReader解析載入的Bean定義資源文件:
?XmlBeanDefinitionReader類中的doLoadBeanDefinitions方法是從特定XML文件中實(shí)際載入Bean定義資源的方法,該方法在載入Bean定義資源之后將其轉(zhuǎn)換為Document對象,接下來調(diào)用registerBeanDefinitions啟動(dòng)Spring?IoC容器對Bean定義的解析過程,registerBeanDefinitions方法源碼如下:
Bean定義資源的載入解析分為以下兩個(gè)過程:
首先,通過調(diào)用XML解析器將Bean定義資源文件轉(zhuǎn)換得到Document對象,但是這些Document對象并沒有按照Spring的Bean規(guī)則進(jìn)行解析。這一步是載入的過程
其次,在完成通用的XML解析之后,按照Spring的Bean規(guī)則對Document對象進(jìn)行解析。
按照Spring的Bean規(guī)則對Document對象解析的過程是在接口BeanDefinitionDocumentReader的實(shí)現(xiàn)類DefaultBeanDefinitionDocumentReader中實(shí)現(xiàn)的。
11、DefaultBeanDefinitionDocumentReader對Bean定義的Document對象解析:
BeanDefinitionDocumentReader接口通過registerBeanDefinitions方法調(diào)用其實(shí)現(xiàn)類DefaultBeanDefinitionDocumentReader對Document對象進(jìn)行解析,解析的代碼如下:
沒搞懂
通過上述Spring?IoC容器對載入的Bean定義Document解析可以看出,我們使用Spring時(shí),在Spring配置文件中可以使用元素來導(dǎo)入IoC容器所需要的其他資源,Spring?IoC容器在解析時(shí)會(huì)首先將指定導(dǎo)入的資源加載進(jìn)容器中。使用別名時(shí),Spring?IoC容器首先將別名元素所定義的別名注冊到容器中。
對于既不是元素,又不是元素的元素,即Spring配置文件中普通的元素的解析由BeanDefinitionParserDelegate類的parseBeanDefinitionElement方法來實(shí)現(xiàn)。
只要使用過Spring,對Spring配置文件比較熟悉的人,通過對上述源碼的分析,就會(huì)明白我們在Spring配置文件中元素的中配置的屬性就是通過該方法解析和設(shè)置到Bean中去的。
注意:在解析元素過程中沒有創(chuàng)建和實(shí)例化Bean對象,只是創(chuàng)建了Bean對象的定義類BeanDefinition,將元素中的配置信息設(shè)置到BeanDefinition中作為記錄,當(dāng)依賴注入時(shí)才使用這些記錄信息創(chuàng)建和實(shí)例化具體的Bean對象。
上面方法中一些對一些配置如元信息(meta)、qualifier等的解析,我們在Spring中配置時(shí)使用的也不多,我們在使用Spring的元素時(shí),配置最多的是屬性。解析過程太多,復(fù)雜不做詳細(xì)描述
主要的類為上圖所寫,。自己去看代碼
<Bean>元素中的<property>元素的相關(guān)配置是這樣處理的
a.ref被封裝為指向依賴對象一個(gè)引用
b.value會(huì)被封裝成一個(gè)字符串類型的對象
c.ref和value都通過"解析的數(shù)據(jù)類型屬性值.setSource(extractSource(ele))"方法將屬性值/引用和引用值關(guān)聯(lián)的過程.
14 解析<property>元素的子元素
在BeanDefinitionParserDelegate類中的parsePeopertySubElement方法對<property>中子元素進(jìn)行解析
通過上述源碼分析,我們明白了在Spring配置文件中,對元素中配置的Array、List、Set、Map、Prop等各種集合子元素的都通過上述方法解析,生成對應(yīng)的數(shù)據(jù)對象,比如ManagedList、ManagedArray、ManagedSet等,這些Managed類是Spring對象BeanDefiniton的數(shù)據(jù)封裝,對集合數(shù)據(jù)類型的具體解析有各自的解析方法實(shí)現(xiàn),解析方法的命名非常規(guī)范,一目了然,我們對集合元素的解析方法進(jìn)行源碼分析,了解其實(shí)現(xiàn)過程。
15.解析過后的BeanDefinition在IoC容器中的注冊:
讓我們繼續(xù)跟蹤程序的執(zhí)行順序,接下來會(huì)到我們第3步中分析DefaultBeanDefinitionDocumentReader對Bean定義轉(zhuǎn)換的Document對象解析的流程中,在其parseDefaultElement方法中完成對Document對象的解析后得到封裝BeanDefinition的BeanDefinitionHold對象,然后調(diào)用BeanDefinitionReaderUtils的registerBeanDefinition方法向IoC容器注冊解析的Bean,BeanDefinitionReaderUtils的注冊的源碼如下:
當(dāng)調(diào)用BeanDefinitionReaderUtils向IoC容器注冊解析的BeanDefinition時(shí),真正完成注冊功能的是DefaultListableBeanFactory。
16.DefaultListableBeanFactory向IoC容器注冊解析后的BeanDefinition:
DefaultListableBeanFactory中使用一個(gè)HashMap的集合對象存放IoC容器中注冊解析的BeanDefinition
大量的hashmap改為ConcurrentHashMap類型
至此,Bean定義資源文件中配置的Bean被解析過后,已經(jīng)注冊到IoC容器中,被容器管理起來,真正完成了IoC容器初始化所做的全部工作。現(xiàn)??在IoC容器中已經(jīng)建立了整個(gè)Bean的配置信息,這些BeanDefinition信息已經(jīng)可以使用,并且可以被檢索,IoC容器的作用就是對這些注冊的Bean定義信息進(jìn)行處理和維護(hù)。這些的注冊的Bean定義信息是IoC容器控制反轉(zhuǎn)的基礎(chǔ),正是有了這些注冊的數(shù)據(jù),容器才可以進(jìn)行依賴注入。
總結(jié):
現(xiàn)在通過上面的代碼,總結(jié)一下IOC容器初始化的基本步驟:
u?初始化的入口在容器實(shí)現(xiàn)中的?refresh()調(diào)用來完成
u?對?bean?定義載入?IOC?容器使用的方法是?loadBeanDefinition,其中的大致過程如下:通過?ResourceLoader?來完成資源文件位置的定位,DefaultResourceLoader?是默認(rèn)的實(shí)現(xiàn),同時(shí)上下文本身就給出了?ResourceLoader?的實(shí)現(xiàn),可以從類路徑,文件系統(tǒng),?URL?等方式來定為資源位置。如果是?XmlBeanFactory作為?IOC?容器,那么需要為它指定?bean?定義的資源,也就是說?bean?定義文件時(shí)通過抽象成?Resource?來被?IOC?容器處理的,容器通過?BeanDefinitionReader來完成定義信息的解析和?Bean?信息的注冊,往往使用的是XmlBeanDefinitionReader?來解析?bean?的?xml?定義文件?-?實(shí)際的處理過程是委托給?BeanDefinitionParserDelegate?來完成的,從而得到?bean?的定義信息,這些信息在?Spring?中使用?BeanDefinition?對象來表示?-?這個(gè)名字可以讓我們想到loadBeanDefinition,RegisterBeanDefinition??這些相關(guān)的方法?-?他們都是為處理?BeanDefinitin?服務(wù)的,?容器解析得到?BeanDefinitionIoC?以后,需要把它在?IOC?容器中注冊,這由?IOC?實(shí)現(xiàn)?BeanDefinitionRegistry?接口來實(shí)現(xiàn)。注冊過程就是在?IOC?容器內(nèi)部維護(hù)的一個(gè)ConcurrentHashMap?來保存得到的?BeanDefinition?的過程。這個(gè) ConcurrentHashMap?是?IoC?容器持有?bean?信息的場所,以后對?bean?的操作都是圍繞這個(gè)ConcurrentHashMap?來實(shí)現(xiàn)的.(
u?然后我們就可以通過?BeanFactory?和?ApplicationContext?來享受到?Spring?IOC?的服務(wù)了,在使用?IOC?容器的時(shí)候,我們注意到除了少量粘合代碼,絕大多數(shù)以正確?IoC?風(fēng)格編寫的應(yīng)用程序代碼完全不用關(guān)心如何到達(dá)工廠,因?yàn)槿萜鲗堰@些對象與容器管理的其他對象鉤在一起。基本的策略是把工廠放到已知的地方,最好是放在對預(yù)期使用的上下文有意義的地方,以及代碼將實(shí)際需要訪問工廠的地方。?Spring?本身提供了對聲明式載入?web?應(yīng)用程序用法的應(yīng)用程序上下文,并將其存儲(chǔ)在ServletContext?中的框架實(shí)現(xiàn)。具體可以參見以后的文章?
在使用?Spring?IOC?容器的時(shí)候我們還需要區(qū)別兩個(gè)概念:
???????Beanfactory?和?Factory?bean,其中?BeanFactory?指的是?IOC?容器的編程抽象,比如?ApplicationContext,?XmlBeanFactory?等,這些都是?IOC?容器的具體表現(xiàn),需要使用什么樣的容器由客戶決定,但?Spring?為我們提供了豐富的選擇。?FactoryBean?只是一個(gè)可以在?IOC而容器中被管理的一個(gè)?bean,是對各種處理過程和資源使用的抽象,Factory?bean?在需要時(shí)產(chǎn)生另一個(gè)對象,而不返回?FactoryBean本身,我們可以把它看成是一個(gè)抽象工廠,對它的調(diào)用返回的是工廠生產(chǎn)的產(chǎn)品。所有的?Factory?bean?都實(shí)現(xiàn)特殊的org.springframework.beans.factory.FactoryBean?接口,當(dāng)使用容器中?factory?bean?的時(shí)候,該容器不會(huì)返回?factory?bean?本身,而是返回其生成的對象。Spring?包括了大部分的通用資源和服務(wù)訪問抽象的?Factory?bean?的實(shí)現(xiàn),其中包括:對?JNDI?查詢的處理,對代理對象的處理,對事務(wù)性代理的處理,對?RMI?代理的處理等,這些我們都可以看成是具體的工廠,看成是SPRING?為我們建立好的工廠。也就是說?Spring?通過使用抽象工廠模式為我們準(zhǔn)備了一系列工廠來生產(chǎn)一些特定的對象,免除我們手工重復(fù)的工作,我們要使用時(shí)只需要在?IOC?容器里配置好就能很方便的使用了