1. 什么是IOC/DI郑叠?
IOC(InversionofControl)控制反轉(zhuǎn):所謂控制反轉(zhuǎn),就是把原先我們代碼里面需要實(shí)現(xiàn)的對(duì)象創(chuàng)建、依賴的代碼讹俊,反轉(zhuǎn)給容器來(lái)幫忙實(shí)現(xiàn)。那么必然的我們需要?jiǎng)?chuàng)建一個(gè)容器煌抒,同時(shí)需要一種描述來(lái)讓容器知道需要?jiǎng)?chuàng)建的對(duì)象與對(duì)象的關(guān)系仍劈。這個(gè)描述最具體表現(xiàn)就是我們可配置的文件。
DI(DependencyInjection)依賴注入:就是指對(duì)象是被動(dòng)接受依賴類而不是自己主動(dòng)去找寡壮,換句話說(shuō)就是指對(duì)象不是從容器中查找它依賴的類贩疙,而是在容器實(shí)例化對(duì)象的時(shí)候主動(dòng)將它依賴的類注入給它。
先從我們自己設(shè)計(jì)這樣一個(gè)視角來(lái)考慮:
對(duì)象和對(duì)象關(guān)系怎么表示况既?
可能是classpath屋群,filesystem,或者是URL網(wǎng)絡(luò)資源坏挠,servletContext等。
回到正題邪乍,有了配置文件降狠,還需要對(duì)配置文件解析对竣。
不同的配置文件對(duì)對(duì)象的描述不一樣,如標(biāo)準(zhǔn)的榜配,自定義聲明式的否纬,如何統(tǒng)一?在內(nèi)部需要有一個(gè)統(tǒng)一的關(guān)于對(duì)象的定義蛋褥,所有外部的描述都必須轉(zhuǎn)化成統(tǒng)一的描述定義临燃。
如何對(duì)不同的配置文件進(jìn)行解析?需要對(duì)不同的配置文件語(yǔ)法烙心,采用不同的解析器膜廊。
2. Spring容器作用及整體實(shí)現(xiàn)
Spring的IoC容器所起的作用,就像上圖所展示的那樣淫茵,它會(huì)以某種方式加載Configuration Metadata(通常也就是XML格式的配置信息)爪瓜,然后根據(jù)這些信息綁定整個(gè)系統(tǒng)的對(duì)象,最終組裝成一個(gè)可用的基于輕量級(jí)容器的應(yīng)用系統(tǒng)匙瘪。
Spring的IoC容器在實(shí)現(xiàn)的時(shí)候,充分運(yùn)用了這兩個(gè)實(shí)現(xiàn)階段的不同特點(diǎn)碍论,在每個(gè)階段都加入了相應(yīng)的容器擴(kuò)展點(diǎn)谅猾,以便我們可以根據(jù)具體場(chǎng)景的需要加入自定義的擴(kuò)展邏輯。
-
容器啟動(dòng)階段
容器啟動(dòng)伊始骑冗,首先會(huì)通過(guò)某種途徑加載Configuration MetaData赊瞬。除了代碼方式比較直接,在大部分情況下贼涩,容器需要依賴某些工具類(BeanDefinitionReader)對(duì)加載的Configuration MetaData進(jìn)行解析和分析巧涧,并將分析后的信息編組為相應(yīng)的BeanDefinition,最后把這些保存了bean定義必要信息的BeanDefinition遥倦,注冊(cè)到相應(yīng)的BeanDefinitionRegistry谤绳,這樣容器啟動(dòng)工作就完成了。下圖演示了這個(gè)階段的主要工作
總地來(lái)說(shuō)袒哥,該階段所做的工作可以認(rèn)為是準(zhǔn)備性的缩筛,重點(diǎn)更加側(cè)重于對(duì)象管理信息的收集。當(dāng)然堡称,一些驗(yàn)證性或者輔助性的工作也可以在這個(gè)階段完成瞎抛。 Bean實(shí)例化階段
經(jīng)過(guò)第一階段,現(xiàn)在所有的bean定義信息都通過(guò)BeanDefinition的方式注冊(cè)到了BeanDefini- tionRegistry中却紧。當(dāng)某個(gè)請(qǐng)求方通過(guò)容器的getBean方法明確地請(qǐng)求某個(gè)對(duì)象桐臊,或者因依賴關(guān)系容器需要隱式地調(diào)用getBean方法時(shí)胎撤,就會(huì)觸發(fā)第二階段的活動(dòng)。
該階段断凶,容器會(huì)首先檢查所請(qǐng)求的對(duì)象之前是否已經(jīng)初始化伤提。如果沒有,則會(huì)根據(jù)注冊(cè)的BeanDefinition所提供的信息實(shí)例化被請(qǐng)求對(duì)象认烁,并為其注入依賴肿男。如果該對(duì)象實(shí)現(xiàn)了某些回調(diào)接口,也會(huì)根據(jù)回調(diào)接口的要求來(lái)裝配它却嗡。當(dāng)該對(duì)象裝配完畢之后舶沛,容器會(huì)立即將其返回請(qǐng)求方使用。如果說(shuō)第一階段只是根據(jù)圖紙裝配生產(chǎn)線的話稽穆,那么第二階段就是使用裝配好的生產(chǎn)線來(lái)生產(chǎn)具體的產(chǎn)品了冠王。
3. Spring核心容器體系結(jié)構(gòu)及核心組件
1. BeanFactory
先簡(jiǎn)單地了解一下上面類圖中的各個(gè)類的作用。
AliasRegistry:定義對(duì) alias 的簡(jiǎn)單增刪改等操作舌镶。
SimpleAliasRegistry:主要使用 map 作為 alias 的緩存柱彻,并對(duì)接口 AliasRegistry 進(jìn)行實(shí)現(xiàn)。
SingletonBeanRegistry:定義對(duì)單例的注冊(cè)及獲取餐胀。
BeanFactory :定義獲取 bean 及 bean 的各種屬性哟楷。
DefaultSingletonBeanRegistry:對(duì)接口 SingletonBeanRegistry各函數(shù)的實(shí)現(xiàn),主要實(shí)現(xiàn)了getSingleton()以及對(duì)循環(huán)依賴的處理否灾。
HierarchicalBeanFactory:繼承 BeanFactory卖擅,也就是在 BeanFactory 定義的功能的基礎(chǔ)上增加了對(duì) parentBeanFactory 的支持。
BeanDefinitionRegistry:定義對(duì) BeanDefinition的各種增刪改操作墨技。
FactoryBeanRegistrySupport:在 DefaultSingletonBeanRegistry的基礎(chǔ)上增加了對(duì)FactoryBean的特殊處理功能惩阶。
ConfigurableBeanFactory:提供配置 Factory 的各種方法。
ListableBeanFactory:根據(jù)各種條件獲取 bean 的配置清單扣汪。
AbstractBeanFactory:綜合 FactoryBeanRegistrySupport 和 ConfigurableBeanFactory 的功能断楷,主要實(shí)現(xiàn)了getBean()。
AutowireCapableBeanFactory: 提供創(chuàng)建 bean崭别、 自動(dòng)注入冬筒、初始化以及應(yīng)用 bean 的后處理。
AbstractAutowireCapableBeanFactory: 綜合 AbstractBeanFactory 并對(duì)接口 AutowireCapableBeanFactory 進(jìn)行實(shí)現(xiàn)茅主,主要實(shí)現(xiàn)了createBean()及依賴注入舞痰。
ConfigurableListableBeanFactory:BeanFactory 配置清單,指定忽略類型及接口等诀姚。
DefaultListableBeanFactory:綜合上面所有功能响牛,主要是對(duì)BeanDefinition的注冊(cè)以及對(duì) Bean 注冊(cè)后的處理。
其中BeanFactory作為最頂層的一個(gè)接口類,它定義了IOC容器的基本功能規(guī)范娃善,BeanFactory有三個(gè)子類:ListableBeanFactory论衍、HierarchicalBeanFactory和AutowireCapableBeanFactory。但是從上圖中我們可以發(fā)現(xiàn)最終的默認(rèn)實(shí)現(xiàn)類是DefaultListableBeanFactory聚磺,他實(shí)現(xiàn)了所有的接口。那為何要定義這么多層次的接口呢炬丸?查閱這些接口的源碼和說(shuō)明發(fā)現(xiàn)瘫寝,每個(gè)接口都有他使用的場(chǎng)合,它主要是為了區(qū)分在Spring內(nèi)部在操作過(guò)程中對(duì)象的傳遞和轉(zhuǎn)化過(guò)程中稠炬,對(duì)對(duì)象的數(shù)據(jù)訪問(wèn)所做的限制焕阿。例如ListableBeanFactory接口表示這些Bean是可列表的,而HierarchicalBeanFactory表示的是這些Bean是有繼承關(guān)系的首启,也就是每個(gè)Bean有可能有父Bean暮屡。AutowireCapableBeanFactory接口定義Bean的自動(dòng)裝配規(guī)則。這四個(gè)接口共同定義了Bean的集合毅桃、Bean之間的關(guān)系褒纲、以及Bean行為。
DefaultListableBeanFactory 是整個(gè)bean 加載的核心部分 钥飞,是 Spring 注冊(cè)及加載 Bean的默認(rèn)實(shí)現(xiàn)莺掠。DefaultListableBeanFactory 繼承了 AbstractAutowireCapableBeanFactory 并實(shí)現(xiàn)了 ContigurableListableBeanFactory 以及 BeanDefinitionRegistry 接口。
最基本的IOC容器接口BeanFactory
在BeanFactory里只對(duì)IOC容器的基本行為作了定義读宙,根本不關(guān)心你的Bean是如何定義怎樣加載的彻秆。正如我們只關(guān)心工廠里得到什么的產(chǎn)品對(duì)象,至于工廠是怎么生產(chǎn)這些對(duì)象的结闸,這個(gè)基本的接口不關(guān)心唇兑。
而要知道工廠是如何產(chǎn)生對(duì)象的,我們需要看具體的IOC容器實(shí)現(xiàn)桦锄,Spring提供了許多IOC容器的實(shí)現(xiàn)扎附。比如ClasspathXmlApplicationContext等。
ApplicationContext是Spring提供的一個(gè)高級(jí)的IOC容器察纯,它除了能夠提供IOC容器的基本功能外帕棉,還為用戶提供了以下的附加服務(wù)。
從ApplicationContext接口的實(shí)現(xiàn)饼记,我們看出其特點(diǎn):
- 支持信息源香伴,可以實(shí)現(xiàn)國(guó)際化。(實(shí)現(xiàn)MessageSource接口)
- 訪問(wèn)資源具则。(實(shí)現(xiàn)ResourcePatternResolver接口即纲,后面章節(jié)會(huì)講到)
- 支持應(yīng)用事件。(實(shí)現(xiàn)ApplicationEventPublisher接口)
2. BeanDefinition
BeanDefinition 是一個(gè)接口 博肋,在 Spring 中存在三種實(shí)現(xiàn) :RootBeanDefinition 低斋、ChildBeanDefinition 以及 GenericBeanDefinition 蜂厅。三種實(shí)現(xiàn)均繼承了 AbstractBeanDefiniton ,其中 BeanDefinition 是配置文件<bean>元素標(biāo)簽在容器中的內(nèi)部表示形式膊畴。<bean>元素標(biāo)簽擁有 class掘猿、scope、lazy-init 等配置屬性唇跨,BeanDefinition 則提供了相應(yīng)的 beanClass稠通、scope、lazylnit屬性买猖,BeanDefinition 和<bean>中的屬性是一一對(duì)應(yīng)的改橘。其中RootBeanDefinition 是最常用的實(shí)現(xiàn)類 ,它對(duì)應(yīng)一般性的<bean>元素標(biāo)簽 玉控,GenericBeanDefinition 是自2.5 版本以后新加入的 bean 文件配置屬性定義類飞主,是一站式服務(wù)類。
在配置文件中可以定義父<bean>和子<bean>高诺,父<bean>用 RooteanDefinition 表示 碌识,而子<bean>用 ChildBeanDefiniton 表示 ,而沒有父<bean>的<bean>就使用 RootBeanDefinition 表示 懒叛。AbstractBeanDefinition 對(duì)兩者共同的類信息進(jìn)行抽象丸冕。
3. 配置文件封裝
Spring 的配置文件讀取是通過(guò) ClassPathResource 進(jìn)行封裝的,如 new ClassPathResource("beanFactoryTest.xml") 罢杉,那么 ClassPathResource 完成了什么功能呢趟畏?
在 Java 中,將不同來(lái)源的資源滩租、抽象成 URL 赋秀,通過(guò)注冊(cè)不同的 handler ( URLStreamHandler ) 來(lái)處理不同來(lái)源的資源的讀取邏輯 ,一般 handler 的類型使用不同前綴 ( 協(xié)議律想,Protocol ) 來(lái)識(shí)別猎莲,如 “file:”、“http:” 技即、“'jar:” 等著洼,然而 URL 沒有默認(rèn)定義相對(duì) ClassPath或 ServletContext 等資源的 handler ,雖然可以注冊(cè)自己的 URLStreamHandler 來(lái)解析特定的 URL 前綴 ( 協(xié)議 ), 比如 “classpath:”身笤,然而這需要了解 URL 的實(shí)現(xiàn)機(jī)制豹悬,而且 URL 也沒有提供一些基本的方法 , 如檢查當(dāng)前資源是否存在液荸、檢查當(dāng)前資源是否可讀等方法瞻佛。因而 Spring 對(duì)其內(nèi)部使用到的資源實(shí)現(xiàn)了自己的抽象結(jié)構(gòu) :Resource 接口來(lái)封裝底層資源。
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
boolean exists();
boolean isReadable();
boolean isOpen();
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String var1) throws IOException;
String getFilename();
String getDescription();
}
lnputStreamSource 封裝任何能返回 lnputStream 的類 莹弊,比如 File 涤久、Classpath 下的資源和 Byte Array 等。它只有一個(gè)方法定義 :getlnputStream()忍弛,該方法返回一個(gè)新的 lnputStream 對(duì)象。
Resource 接口抽象了所有 Spring 內(nèi)部使用到的底層資源 :File 考抄、URL 细疚、Classpath 等。首先川梅, 它定義了 3 個(gè)判斷當(dāng)前資源狀態(tài)的方法 :存在性 ( exists )疯兼、可讀性 ( isReadable )、是否處于打開狀態(tài) ( isOpen )贫途。另外吧彪,Resource 接口還提供了不同資源到 URL 、URI 丢早、File 類型的轉(zhuǎn)換姨裸,以及獲取 lastModified 屬性、文件名 ( 不帶路徑信息的文件名怨酝,getFilename() )的方法傀缩。為了便于操作,Resource 還提供了基于當(dāng)前資源农猬、創(chuàng)建一個(gè)相對(duì)資源的方法 :createRelative()赡艰。在錯(cuò)誤處理中需耍詳細(xì)地打印出錯(cuò)的資源文件 ,因而 Resource 還提供了getDescription()方法用于在錯(cuò)誤處理中的打印信息斤葱。
在日常的開發(fā)工作中惯驼,資源文件的加載也是經(jīng)常用到的,可以直接使用 Spring 提供的類, 比如在希望加載文件時(shí)可以使用以下代碼:
Resource resource = new ClassPathResurce("beanFactoryTest.xml");
InputStream inputStream = resource.getInputStream () ;
得到 inputStream 后祟牲,我們就可以按照以前的開發(fā)方式進(jìn)行實(shí)現(xiàn)了隙畜,并且我們已經(jīng)可以利用Resource 及其子類為我們提供好的諸多特性。
有了Resource 接口便可以對(duì)所有資源说贝、文件進(jìn)行統(tǒng)一處理议惰。至于實(shí)現(xiàn) ,其實(shí)是非常簡(jiǎn)單的乡恕, 以getlnputStream 為例言询,ClassPathResource 中的實(shí)現(xiàn)方式便是通過(guò) class 或者 classLoader 提供的底層方法進(jìn)行調(diào)用,而對(duì)于 FileSystemResource 的實(shí)現(xiàn)其實(shí)更簡(jiǎn)單傲宜,直接使用 FilelnputStream 對(duì)文件進(jìn)行實(shí)例化运杭。
當(dāng)通過(guò) Resource 相關(guān)類完成了對(duì)配置文件進(jìn)行封裝后配置文件的讀取工作就全權(quán)交給XmlBeanDefinitionReader 來(lái)處理了。
4. XML 配置文件的讀取
XML 配置文件的讀取是 Spring 中重要的功能函卒,因?yàn)?Spring 的大部分功能都是以配置作為切入點(diǎn)的辆憔,那么我們可以從 XmlBeanDefinitionReader 中梳理一下資源文件讀取、解析及注冊(cè)的大致脈絡(luò)报嵌,首先我們看著各個(gè)類的功能虱咧。
- ResourceLoader :定義資源、加載器 锚国,主要應(yīng)用于根據(jù)給定的資源文件地址返回對(duì)應(yīng)的Resource腕巡。
- BeanDefinitionReader:主要定義資源文件讀取并轉(zhuǎn)換為 BeanDefinition 的各個(gè)功能。
- EnvironmentCapable:定義獲取 Environment的方法血筑。
- DocumentLoader:定義從資源文件加載到轉(zhuǎn)換為 Documnent 的功能
- AbstractBeanDefinitionReader:對(duì) EnvironmentCapable绘沉、BeanDefinitionReader 類定義的功能進(jìn)行實(shí)現(xiàn)。
- BeanDefinitionDocurnentReader :定義讀取 Docuernnt 并注冊(cè) BeanDefinition 功能云挟。
- BeanDefinitionParserDelegate:定義解析 Element 的各種方法梆砸。
經(jīng)過(guò)以上分析,我們可以梳理出整個(gè)XML配置文件讀取的大致流程 园欣,如圖所示帖世,在
(1)通過(guò)繼承自AbstractBeanDefinitionReader 中的方法沸枯,來(lái)使用 ResourLoader 將資源文件路徑轉(zhuǎn)換為對(duì)應(yīng)的Resource文件闹伪。
(2)通過(guò) DocumentLoader 對(duì) Resource 文件進(jìn)行轉(zhuǎn)換浦辨,將 Resource 文件轉(zhuǎn)換為 Document文件。
(3)通過(guò)實(shí)現(xiàn)接口BeanDefinitionDocumentReader 的DefaultBeanDefinitionDocumentReader 類對(duì) Document 進(jìn)行解析,并使用 BeanDefinitionParserDelegate 對(duì) Element 進(jìn)行解析庇忌。
下面重點(diǎn)分析一下ResourceLoader相關(guān)的內(nèi)容
org.springframework.core.io.ResourceLoader接口是資源查找定位策略的統(tǒng)一抽象乡范,具體的資源查找定 位策略則由相應(yīng)的ResourceLoader實(shí)現(xiàn)類給出困后。ResourceLoader定義如下:
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
Resource getResource(String location);
ClassLoader getClassLoader();
}
其中最主要的就是Resource getResource(String location);方法豆胸,通過(guò)它杨耙,我們就可以根據(jù)指定的資源位置,定位到具體的資源實(shí)例飘痛。
DefaultResourceLoader:
ResourceLoader有一個(gè)默認(rèn)的實(shí)現(xiàn)類珊膜,即org.springframework.core.io.DefaultResource-
Loader,該類默認(rèn)的資源查找處理邏輯如下宣脉。
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
//如果是類路徑的方式车柠,那需要使用ClassPathResource來(lái)得到Bean文件的資源對(duì)象。
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
//如果是URL方式塑猖,使用URLResource來(lái)得到Bean文件的資源對(duì)象竹祷。
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
//都不是,調(diào)用容器本身的getResourceByPath(location)獲取Resource.
return getResourceByPath(location);
}
}
}
(1) 首先檢查資源路徑是否以classpath:前綴打頭羊苟,如果是塑陵,則嘗試構(gòu)造ClassPathResource類 型資源并返回。
(2) 否則蜡励,(a) 嘗試通過(guò)URL猿妈,根據(jù)資源路徑來(lái)定位資源,如果沒有拋出MalformedURLException巍虫, 有則會(huì)構(gòu)造UrlResource類型的資源并返回;
(b) 如果還是無(wú)法根據(jù)資源路徑定位指定的資源鳍刷,則委派 getResourceByPath(String) 方法來(lái)定位占遥, DefaultResourceLoader的getResourceByPath(String)方法默認(rèn)實(shí)現(xiàn)邏輯是,構(gòu)造ClassPathResource類型的資源并返回输瓜。
FileSystemResourceLoader:
為了避免DefaultResourceLoader在最后getResourceByPath(String)方法上的不恰當(dāng)處理瓦胎, 我們可以使用org.springframework.core.io.FileSystemResourceLoader,它繼承自Default- ResourceLoader尤揣,但覆寫了 getResourceByPath(String)方法搔啊,使之從文件系統(tǒng)加載資源并以FileSystemResource類型返回。
FileSystemResourceLoader 在 ResourceLoader 家族中的兄弟 FileSystemXmlApplicationContext北戏,也是覆寫了getResourceByPath(String)方法的邏輯负芋,以改變DefaultResourceLoader的默認(rèn)資源加載行為,最終從文件系統(tǒng)中加載并返回FileSystemResource類型的資源嗜愈。
ResourcePatternResolver——批量查找的ResourceLoader:
ResourcePatternResolver是ResourceLoader的擴(kuò)展旧蛾,ResourceLoader每次只能根據(jù)資源路徑 返回確定的單個(gè)Resource實(shí)例,而ResourcePatternResolver則可以根據(jù)指定的資源路徑匹配模式蠕嫁, 每次返回多個(gè)Resource實(shí)例锨天。接口org.springframework.core.io.support.ResourcePattern-
Resolver定義如下:
public interface ResourcePatternResolver extends ResourceLoader {
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";
Resource[] getResources(String locationPattern) throws IOException;
}
ResourcePatternResolver在繼承ResourceLoader原有定義的基礎(chǔ)上,又引入了Resource[] getResources(String)方法定義剃毒,以支持根據(jù)路徑匹配模式返回多個(gè)Resources的功能病袄。它同時(shí)還引入了一種新的協(xié)議前綴classpath*:搂赋,針對(duì)這一點(diǎn)的支持,將由相應(yīng)的子類實(shí)現(xiàn)益缠。
ResourcePatternResolver最常用的一個(gè)實(shí)現(xiàn)是 org.springframework.core.io.support.PathMatchingResourcePatternResolver脑奠,該實(shí)現(xiàn)類支持ResourceLoader級(jí)別的資源加載,支持基于Ant風(fēng)格的路徑匹配模式左刽,支持ResourcePatternResolver新增加的classpath*:前綴等捺信,基本上集所有技能于一身。
在構(gòu)造PathMatchingResourcePatternResolver實(shí)例的時(shí)候欠痴,可以指定一個(gè)ResourceLoader迄靠, 如果不指定的話, 則PathMatchingResourcePatternResolver內(nèi)部會(huì)默認(rèn)構(gòu)造一個(gè)DefaultResourceLoader實(shí)例喇辽。PathMatchingResourcePatternResolver內(nèi)部會(huì)將匹配后確定的資源路徑掌挚, 委派給它的ResourceLoader來(lái)查找和定位資源。這樣菩咨,如果不指定任何ResourceLoader的話吠式,PathMatchingResourcePatternResolver在加載資源的行為上會(huì)與DefaultResourceLoader基本相同, 只存在返回的Resource數(shù)量上的差異抽米。
ApplicationContext與ResourceLoader:
ApplicationContext繼承了ResourcePatternResolver标捺,當(dāng)然就間接實(shí)現(xiàn)了ResourceLoader接口懊纳。所以,任何的ApplicationContext實(shí)現(xiàn)都可以看作是一個(gè) ResourceLoader甚至ResourcePatternResolver亡容。而這就是ApplicationContext支持Spring內(nèi)統(tǒng)一 資源加載策略的真相嗤疯。
通常,所有的ApplicationContext 實(shí)現(xiàn)類會(huì)直接或者間接地繼承 org.springframework.context.support.AbstractApplicationContext闺兢,從這個(gè)類上茂缚,我們就可以看到ApplicationContext與ResourceLoader之間的所有關(guān)系。AbstractApplicationContext繼承了DefaultResourceLoader列敲,那么阱佛,它的getResource(String)當(dāng)然就直接用DefaultResourceLoader的了。剩下需要它“效勞”的戴而,就是ResourcePatternResolver的Resource[] getResources (String)凑术,當(dāng)然,AbstractApplicationContext也不負(fù)眾望所意,當(dāng)即拿下淮逊。AbstractApplicationContext類的內(nèi)部聲明有一個(gè)resourcePatternResolver催首,類型是ResourcePatternResolver,對(duì)應(yīng)的實(shí)例類型為PathMatchingResourcePatternResolver 泄鹏。之前我們說(shuō)過(guò) PathMatchingResourcePatternResolver構(gòu)造的時(shí)候會(huì)接受一個(gè)ResourceLoader郎任,而AbstractApplicationContext本身又繼承自 DefaultResourceLoader,當(dāng)然就直接把自身給“貢獻(xiàn)”了备籽。這樣舶治,整個(gè)ApplicationContext的實(shí)現(xiàn)類就完全可以支持ResourceLoader或者ResourcePatternResolver接口。說(shuō)白了车猬,ApplicationContext的實(shí)現(xiàn)類在作為ResourceLoader或者ResourcePatternResolver時(shí)候的行為霉猛,完全就是委派給了PathMatchingResourcePatternResolver和DefaultResourceLoader來(lái)做。下圖給出了AbstractApplicationContext與 ResourceLoader和ResourcePatternResolver之間的類層次關(guān)系珠闰。
4. 容器初始化
IOC容器的初始化包括BeanDefinition的Resource定位惜浅、載入和注冊(cè)這三個(gè)基本的過(guò)程。我們以ApplicationContext為例講解伏嗜,ApplicationContext系列容器也許是我們最熟悉的坛悉,因?yàn)閃eb項(xiàng)目中使用的XmlWebApplicationContext就屬于這個(gè)繼承體系,還有ClasspathXmlApplicationContext等承绸,其繼承體系如下圖所示:ApplicationContext允許上下文嵌套裸影,通過(guò)保持父上下文可以維持一個(gè)上下文體系。對(duì)于Bean的查找可以在這個(gè)上下文體系中發(fā)生军熏,首先檢查當(dāng)前上下文空民,其次是父上下文,逐級(jí)向上羞迷,這樣為不同的Spring應(yīng)用提供了一個(gè)共享的Bean定義環(huán)境。
下面我們分別簡(jiǎn)單地演示一下兩種IOC容器的創(chuàng)建過(guò)程:
-
通過(guò)DefaultListableBeanFactory創(chuàng)建通過(guò)DefaultListableBeanFactory創(chuàng)建.png
- 通過(guò)ClassPathXmlApplicationContext創(chuàng)建:
ApplicationContext context = new ClassPathXmlApplicationContext("application-context.xml");
通過(guò)ApplicationContext初始化容器源碼剖析
1. 調(diào)用構(gòu)造方法
其實(shí)際調(diào)用的構(gòu)造函數(shù)為:2. 設(shè)置資源加載器和資源定位
通過(guò)分析ClassPathXmlApplicationContext的源代碼可以知道画饥,在創(chuàng)建ClassPathXmlApplicationContext容器時(shí)衔瓮,構(gòu)造方法做以下兩項(xiàng)重要工作:
首先,調(diào)用父類容器的構(gòu)造方法(super(parent)方法)為容器設(shè)置好Bean資源加載器抖甘。
然后热鞍,再調(diào)用父類AbstractRefreshableConfigApplicationContext的setConfigLocations(configLocations)方法設(shè)置Bean定義資源文件的定位路徑。
ClasspathResourceres=newClasspathResource(“a.xml,b.xml,......”);多個(gè)資源文件路徑之間可以是用”,;\t\n”等分隔柄沮。
B.ClasspathResourceres=newClasspathResource(newString[]{“a.xml”,”b.xml”,......});至此回梧,SpringIOC容器在初始化時(shí)將配置的Bean定義資源文件定位為Spring封裝的Resource废岂。
3. AbstractApplicationContext的refresh函數(shù)載入Bean定義過(guò)程:
SpringIOC容器對(duì)Bean定義資源的載入是從refresh()函數(shù)開始的,refresh()是一個(gè)模板方法狱意,refresh()方法的作用是:在創(chuàng)建IOC容器前湖苞,如果已經(jīng)有容器存在,則需要把已有的容器銷毀和關(guān)閉详囤,以保證在refresh之后使用的是新建立起來(lái)的IOC容器财骨。refresh的作用類似于對(duì)IOC容器的重啟,在新建立好的容器中對(duì)容器進(jìn)行初始化藏姐,對(duì)Bean定義資源進(jìn)行載入隆箩。
refresh()方法的作用是:在創(chuàng)建IOC容器前问畅,如果已經(jīng)有容器存在娃属,則需要把已有的容器銷毀和關(guān)閉,以保證在refresh之后使用的是新建立起來(lái)的IOC容器护姆。refresh的作用類似于對(duì)IOC容器的重啟矾端,在新建立好的容器中對(duì)容器進(jìn)行初始化,對(duì)Bean定義資源進(jìn)行載入卵皂。
4. AbstractApplicationContext的obtainFreshBeanFactory()方法調(diào)用子類容器的refreshBeanFactory()方法秩铆,啟動(dòng)容器載入Bean定義資源文件的過(guò)程
代碼如下:5. AbstractRefreshableApplicationContext子類的loadBeanDefinitions方法:
AbstractRefreshableApplicationContext中只定義了抽象的loadBeanDefinitions方法刃泌,容器真正調(diào)用的是其子類AbstractXmlApplicationContext對(duì)該方法的實(shí)現(xiàn)凡壤,AbstractXmlApplicationContext的主要源碼如下:loadBeanDefinitions方法同樣是抽象方法,是由其子類實(shí)現(xiàn)的耙替,也即在AbstractXmlApplicationContext中亚侠。XmlBean讀取器(XmlBeanDefinitionReader)調(diào)用其父類AbstractBeanDefinitionReader的reader.loadBeanDefinitions方法讀取Bean定義資源。由于我們使用FileSystemXmlApplicationContext作為例子分析俗扇,因此getConfigResources的返回值為null硝烂,因此程序執(zhí)行reader.loadBeanDefinitions(configLocations)分支。
6. AbstractBeanDefinitionReader讀取Bean定義資源,在其抽象父類AbstractBeanDefinitionReader中定義了載入過(guò)程铜幽。
AbstractBeanDefinitionReader的loadBeanDefinitions方法源碼如下:loadBeanDefinitions(Resource...resources)方法和上面分析的3個(gè)方法類似钢坦,同樣也是調(diào)用XmlBeanDefinitionReader的loadBeanDefinitions方法究孕。
從對(duì)AbstractBeanDefinitionReader的loadBeanDefinitions方法源碼分析可以看出該方法做了以下兩件事:
7. 資源加載器獲取要讀入的資源:
XmlBeanDefinitionReader通過(guò)調(diào)用其父類DefaultResourceLoader的getResource方法獲取要加載的資源,其源碼如下:這樣搅方,就可以從文件系統(tǒng)路徑上對(duì)IOC配置文件進(jìn)行加載比吭,當(dāng)然我們可以按照這個(gè)邏輯從任何地方加載,在Spring中我們看到它提供的各種資源抽象姨涡,比如URLResource,FileSystemResource等來(lái)供我們使用衩藤。上面我們看到的是定位Resource的一個(gè)過(guò)程,而這只是加載過(guò)程的一部分涛漂。
8. XmlBeanDefinitionReader加載Bean定義資源:
繼續(xù)回到XmlBeanDefinitionReader的loadBeanDefinitions(Resource...)方法看到代表bean文件的資源定義以后的載入過(guò)程赏表。通過(guò)源碼分析,載入Bean定義資源文件的最后一步是將Bean定義資源轉(zhuǎn)換為Document對(duì)象匈仗,該過(guò)程由documentLoader實(shí)現(xiàn)瓢剿。
9. DocumentLoader將Bean定義資源轉(zhuǎn)換為Document對(duì)象:
DocumentLoader將Bean定義資源轉(zhuǎn)換成Document對(duì)象的源碼如下:該解析過(guò)程調(diào)用JavaEE標(biāo)準(zhǔn)的JAXP標(biāo)準(zhǔn)進(jìn)行處理。
至此SpringIOC容器根據(jù)定位的Bean定義資源文件悠轩,將其加載讀入并轉(zhuǎn)換成為Document對(duì)象過(guò)程完成跋选。接下來(lái)我們要繼續(xù)分析SpringIOC容器將載入的Bean定義資源文件轉(zhuǎn)換為Document對(duì)象之后,是如何將其解析為SpringIOC管理的Bean對(duì)象并將其注冊(cè)到容器中的哗蜈。
10. XmlBeanDefinitionReader解析載入的Bean定義資源文件:
XmlBeanDefinitionReader類中的doLoadBeanDefinitions方法是從特定XML文件中實(shí)際載入Bean定義資源的方法,該方法在載入Bean定義資源之后將其轉(zhuǎn)換為Document對(duì)象坠韩,接下來(lái)調(diào)用registerBeanDefinitions啟動(dòng)SpringIOC容器對(duì)Bean定義的解析過(guò)程距潘,registerBeanDefinitions方法源碼如下:Bean定義資源的載入解析分為以下兩個(gè)過(guò)程:
首先,通過(guò)調(diào)用XML解析器將Bean定義資源文件轉(zhuǎn)換得到Document對(duì)象只搁,但是這些Document對(duì)象并沒有按照Spring的Bean規(guī)則進(jìn)行解析音比。這一步是載入的過(guò)程。
其次氢惋,在完成通用的XML解析之后洞翩,按照Spring的Bean規(guī)則對(duì)Document對(duì)象進(jìn)行解析稽犁。
按照Spring的Bean規(guī)則對(duì)Document對(duì)象解析的過(guò)程是在接口BeanDefinitionDocumentReader的實(shí)現(xiàn)類DefaultBeanDefinitionDocumentReader中實(shí)現(xiàn)的。
11. DefaultBeanDefinitionDocumentReader對(duì)Bean定義的Document對(duì)象解析:
BeanDefinitionDocumentReader接口通過(guò)registerBeanDefinitions方法調(diào)用其實(shí)現(xiàn)類DefaultBeanDefinitionDocumentReader對(duì)Document對(duì)象進(jìn)行解析骚亿,解析的代碼如下:通過(guò)上述SpringIOC容器對(duì)載入的Bean定義Document解析可以看出已亥,我們使用Spring時(shí),在Spring配置文件中可以使用<import>元素來(lái)導(dǎo)入IOC容器所需要的其他資源来屠,SpringIOC容器在解析時(shí)會(huì)首先將指定導(dǎo)入的資源加載進(jìn)容器中虑椎。使用<ailas>別名時(shí),SpringIOC容器首先將別名元素所定義的別名注冊(cè)到容器中俱笛。
對(duì)于既不是<import>元素捆姜,又不是<alias>元素的元素,即Spring配置文件中普通的<bean>元素的解析由BeanDefinitionParserDelegate類的parseBeanDefinitionElement方法來(lái)實(shí)現(xiàn)迎膜。
12. BeanDefinitionParserDelegate解析Bean定義資源文件中的<bean>元素:
Bean定義資源文件中的<import>和<alias>元素解析在DefaultBeanDefinitionDocumentReader中已經(jīng)完成泥技,對(duì)Bean定義資源文件中使用最多的<bean>元素交由BeanDefinitionParserDelegate來(lái)解析,其解析實(shí)現(xiàn)的源碼如下:只要使用過(guò)Spring磕仅,對(duì)Spring配置文件比較熟悉的人珊豹,通過(guò)對(duì)上述源碼的分析,就會(huì)明白我們?cè)赟pring配置文件中<Bean>元素的中配置的屬性就是通過(guò)該方法解析和設(shè)置到Bean中去的宽涌。
注意:在解析<Bean>元素過(guò)程中沒有創(chuàng)建和實(shí)例化Bean對(duì)象平夜,只是創(chuàng)建了Bean對(duì)象的定義類BeanDefinition,將<Bean>元素中的配置信息設(shè)置到BeanDefinition中作為記錄卸亮,當(dāng)依賴注入時(shí)才使用這些記錄信息創(chuàng)建和實(shí)例化具體的Bean對(duì)象忽妒。
上面方法中一些對(duì)一些配置如元信息(meta)、qualifier等的解析兼贸,我們?cè)赟pring中配置時(shí)使用的也不多段直,我們?cè)谑褂肧pring的<Bean>元素時(shí),配置最多的是<property>屬性溶诞,因此我們下面繼續(xù)分析源碼鸯檬,了解Bean的屬性在解析時(shí)是如何設(shè)置的。
13. BeanDefinitionParserDelegate解析<property>元素:
BeanDefinitionParserDelegate在解析<Bean>調(diào)用parsePropertyElements方法解析<Bean>元素中的<property>屬性子元素螺垢,解析源碼如下:通過(guò)對(duì)上述源碼的分析喧务,我們可以了解在Spring配置文件中,<Bean>元素中<property>元素的相關(guān)配置是如何處理的:
a.ref被封裝為指向依賴對(duì)象一個(gè)引用枉圃。
b.value配置都會(huì)封裝成一個(gè)字符串類型的對(duì)象功茴。
c.ref和value都通過(guò)“解析的數(shù)據(jù)類型屬性值.setSource(extractSource(ele));”方法將屬性值/引用與所引用的屬性關(guān)聯(lián)起來(lái)。
在方法的最后對(duì)于<property>元素的子元素通過(guò)parsePropertySubElement方法解析孽亲,我們繼續(xù)分析該方法的源碼坎穿,了解其解析過(guò)程。
14. 解析<property>元素的子元素:
在BeanDefinitionParserDelegate類中的parsePropertySubElement方法對(duì)<property>中的子元素解析,源碼如下:通過(guò)上述源碼分析玲昧,我們明白了在Spring配置文件中栖茉,對(duì)<property>元素中配置的array、list孵延、set吕漂、map、prop等各種集合子元素的都通過(guò)上述方法解析隙袁,生成對(duì)應(yīng)的數(shù)據(jù)對(duì)象痰娱,比如ManagedList、ManagedArray菩收、ManagedSet等梨睁,這些Managed類是Spring對(duì)象BeanDefiniton的數(shù)據(jù)封裝,對(duì)集合數(shù)據(jù)類型的具體解析有各自的解析方法實(shí)現(xiàn)娜饵,解析方法的命名非常規(guī)范坡贺,一目了然,我們對(duì)<list>集合元素的解析方法進(jìn)行源碼分析箱舞,了解其實(shí)現(xiàn)過(guò)程遍坟。
15. 解析<list>子元素:
在BeanDefinitionParserDelegate類中的parseListElement方法就是具體實(shí)現(xiàn)解析<property>元素中的<list>集合子元素,源碼如下:經(jīng)過(guò)對(duì)SpringBean定義資源文件轉(zhuǎn)換的Document對(duì)象中的元素層層解析晴股,SpringIOC現(xiàn)在已經(jīng)將XML形式定義的Bean定義資源文件轉(zhuǎn)換為SpringIOC所識(shí)別的數(shù)據(jù)結(jié)構(gòu)——BeanDefinition愿伴,它是Bean定義資源文件中配置的POJO對(duì)象在SpringIOC容器中的映射,我們可以通過(guò)AbstractBeanDefinition為入口电湘,看到了IOC容器進(jìn)行索引隔节、查詢和操作。
通過(guò)SpringIOC容器對(duì)Bean定義資源的解析后寂呛,IOC容器大致完成了管理Bean對(duì)象的準(zhǔn)備工作怎诫,即初始化過(guò)程,但是最為重要的依賴注入還沒有發(fā)生贷痪,現(xiàn)在在IOC容器中BeanDefinition存儲(chǔ)的只是一些靜態(tài)信息幻妓,接下來(lái)需要向容器注冊(cè)Bean定義信息才能全部完成IOC容器的初始化過(guò)程。
16. 解析過(guò)后的BeanDefinition在IOC容器中的注冊(cè):
讓我們繼續(xù)跟蹤程序的執(zhí)行順序劫拢,接下來(lái)我們來(lái)分析DefaultBeanDefinitionDocumentReader對(duì)Bean定義轉(zhuǎn)換的Document對(duì)象解析的流程中肉津,在其parseDefaultElement方法中完成對(duì)Document對(duì)象的解析后得到封裝BeanDefinition的BeanDefinitionHold對(duì)象,然后調(diào)用BeanDefinitionReaderUtils的registerBeanDefinition方法向IOC容器注冊(cè)解析的Bean舱沧,BeanDefinitionReaderUtils的注冊(cè)的源碼如下:當(dāng)調(diào)用BeanDefinitionReaderUtils向IOC容器注冊(cè)解析的BeanDefinition時(shí)妹沙,真正完成注冊(cè)功能的是DefaultListableBeanFactory。
17. DefaultListableBeanFactory向IOC容器注冊(cè)解析后的BeanDefinition:
DefaultListableBeanFactory中使用一個(gè)HashMap的集合對(duì)象存放IOC容器中注冊(cè)解析的BeanDefinition狗唉,向IOC容器注冊(cè)的主要源碼如下:至此,Bean定義資源文件中配置的Bean被解析過(guò)后涡真,已經(jīng)注冊(cè)到IOC容器中分俯,被容器管理起來(lái)肾筐,真正完成了IOC容器初始化所做的全部工作。現(xiàn)在IOC容器中已經(jīng)建立了整個(gè)Bean的配置信息缸剪,這些BeanDefinition信息已經(jīng)可以使用吗铐,并且可以被檢索,IOC容器的作用就是對(duì)這些注冊(cè)的Bean定義信息進(jìn)行處理和維護(hù)杏节。這些的注冊(cè)的Bean定義信息是IOC容器控制反轉(zhuǎn)的基礎(chǔ)唬渗,正是有了這些注冊(cè)的數(shù)據(jù),容器才可以進(jìn)行依賴注入奋渔。
總結(jié):
現(xiàn)在通過(guò)上面的代碼镊逝,總結(jié)一下IOC容器初始化的基本步驟:
(1).初始化的入口在容器實(shí)現(xiàn)中的refresh()調(diào)用來(lái)完成。
(2).對(duì)bean定義載入IOC容器使用的方法是loadBeanDefinition,其中的大致過(guò)程如下:
通過(guò)ResourceLoader來(lái)完成資源文件位置的定位嫉鲸,DefaultResourceLoader是默認(rèn)的實(shí)現(xiàn)撑蒜,同時(shí)上下文本身就給出了ResourceLoader的實(shí)現(xiàn),可以從類路徑玄渗,文件系統(tǒng),URL等方式來(lái)定為資源位置座菠。如果是XmlBeanFactory作為IOC容器,那么需要為它指定bean定義的資源藤树,也就是說(shuō)bean定義文件時(shí)通過(guò)抽象成Resource來(lái)被IOC容器處理的浴滴,容器通過(guò)BeanDefinitionReader來(lái)完成定義信息的解析和Bean信息的注冊(cè),往往使用的是XmlBeanDefinitionReader來(lái)解析bean的xml定義文件-實(shí)際的處理過(guò)程是委托給BeanDefinitionParserDelegate來(lái)完成的,從而得到bean的定義信息岁钓,這些信息在Spring中使用BeanDefinition對(duì)象來(lái)表示-這個(gè)名字可以讓我們想到loadBeanDefinition,RegisterBeanDefinition這些相關(guān)方法-他們都是為處理BeanDefinitin服務(wù)的升略,容器解析得到BeanDefinition以后,需要把它在IOC容器中注冊(cè)甜紫,這由IOC實(shí)現(xiàn)BeanDefinitionRegistry接口來(lái)實(shí)現(xiàn)降宅。注冊(cè)過(guò)程就是在IOC容器內(nèi)部維護(hù)的一個(gè)HashMap來(lái)保存得到的BeanDefinition的過(guò)程。這個(gè)HashMap是IOC容器持有Bean信息的場(chǎng)所囚霸,以后對(duì)Bean的操作都是圍繞這個(gè)HashMap來(lái)實(shí)現(xiàn)的腰根。
然后我們就可以通過(guò)BeanFactory和ApplicationContext來(lái)享受到SpringIOC的服務(wù)了,在使用IOC容器的時(shí)候,我們注意到除了少量粘合代碼拓型,絕大多數(shù)以正確IOC風(fēng)格編寫的應(yīng)用程序代碼完全不用關(guān)心如何到達(dá)工廠额嘿,因?yàn)槿萜鲗堰@些對(duì)象與容器管理的其他對(duì)象鉤在一起×哟欤基本的策略是把工廠放到已知的地方册养,最好是放在對(duì)預(yù)期使用的上下文有意義的地方,以及代碼將實(shí)際需要訪問(wèn)工廠的地方压固。Spring本身提供了對(duì)聲明式載入web應(yīng)用程序用法的應(yīng)用程序上下文,并將其存儲(chǔ)在ServletContext中的框架實(shí)現(xiàn)球拦。
BeanFactory和FactoryBean,其中BeanFactory指的是IOC容器的編程抽象,比如ApplicationContext坎炼,XmlBeanFactory等愧膀,這些都是IOC容器的具體表現(xiàn),需要使用什么樣的容器由客戶決定,但Spring為我們提供了豐富的選擇谣光。FactoryBean只是一個(gè)可以在IOC而容器中被管理的一個(gè)Bean,是對(duì)各種處理過(guò)程和資源使用的抽象,FactoryBean在需要時(shí)產(chǎn)生另一個(gè)對(duì)象檩淋,而不返回FactoryBean本身,我們可以把它看成是一個(gè)抽象工廠,對(duì)它的調(diào)用返回的是工廠生產(chǎn)的產(chǎn)品萄金。所有的FactoryBean都實(shí)現(xiàn)特殊的org.springframework.beans.factory.FactoryBean接口蟀悦,當(dāng)使用容器中FactoryBean的時(shí)候,該容器不會(huì)返回FactoryBean本身,而是返回其生成的對(duì)象氧敢。Spring包括了大部分的通用資源和服務(wù)訪問(wèn)抽象的FactoryBean的實(shí)現(xiàn)日戈,其中包括:對(duì)JNDI查詢的處理,對(duì)代理對(duì)象的處理福稳,對(duì)事務(wù)性代理的處理涎拉,對(duì)RMI代理的處理等,這些我們都可以看成是具體的工廠,看成是Spring為我們建立好的工廠的圆。也就是說(shuō)Spring通過(guò)使用抽象工廠模式為我們準(zhǔn)備了一系列工廠來(lái)生產(chǎn)一些特定的對(duì)象,免除我們手工重復(fù)的工作鼓拧,我們要使用時(shí)只需要在IOC容器里配置好就能很方便的使用了。