前言
本文是Spring源碼解析IoC部分的第一篇文章,以最簡單的IoC案例作為切入點,主要分析了XML配置文件是如何被加載的,Bean工廠是如何創(chuàng)建的
為了分析方便,本文創(chuàng)建了一個普通java類Student,有String類型name和int類型的age兩個屬性,此外還有一個公共無參,無返回值的void study()方法,初學(xué)時不管有多少種加載配置文件的方法new ClassPathXmlApplicationContext(String)必然是最常用的一種。我們在resources目錄下創(chuàng)建了名為beans.xml的Spring配置文件

圖1. 初始化beans.xml配置
在配置文件中使用
<bean>表明將Student交給Spring來管理,這里需要插一嘴,IoC最主要的作用是將繁瑣的創(chuàng)建對象的過程與代碼需要真正表達(dá)的業(yè)務(wù)邏輯解耦,所謂的創(chuàng)建對象過程還要分為兩部分:1.構(gòu)建出對象實體;2.建立對象間依賴關(guān)系,其中第二部分主要通過依賴注入DI實現(xiàn),所以說很多書上會將DI作為IoC實現(xiàn)的手段。完成配置后我們就可以通過一段簡單的代碼得到托管的Student,并調(diào)用其中的study()
圖2. 從Spring容器中得到托管對象
既然第二句已經(jīng)獲取被托管的對象,那么
IoC的主流程必然存在于第一句中,我們就以new ClassPathXmlApplicationContext(String)為入口,看看Spring是如何完成控制反轉(zhuǎn)的
圖3. ClassPathXmlApplicationContext構(gòu)造器
圖中最后一個構(gòu)造器才是邏輯真正的開始位置,第一個參數(shù)為配置文件數(shù)組,第二個參數(shù)表示是否需要刷新Spring上下文標(biāo)識,這里為true,第三個參數(shù)為當(dāng)前
context的父上下文。在進(jìn)一步深入之前我需要先給出ClassPathXmlApplicationContext的類圖,類圖就像指南針,當(dāng)我們深入細(xì)節(jié)迷失時會引導(dǎo)我們識別正確的道路
圖4. ClassPathXmlApplicationContext類圖
圖3中
setConfigLocations(configLocations)主要作用是解析ClassPathXmlApplicationContext(String)參數(shù)中的配置文件路徑,并將配置的單個或者多個配置文件存放在父類AbstractRefreshableConfigApplicationContext的成員變量configLocations數(shù)組內(nèi)。如果路徑中存在${}占位符,會用正確的值進(jìn)行替換,由于其處理過程和使用<context:property-placeholder>引入外部配置文件處理流程一致,所有將在后面解析<context:property-placeholder>原理時單獨講解。設(shè)置完配置文件后調(diào)用refresh()刷新整個Spring上下文信息
圖5. AbstractApplicationContext的refresh()
該方法包裹在同步代碼塊中,防止整個Spring上下文刷新時多個線程造成的沖突,在查看對象監(jiān)視器
startupShutdownMonitor的使用時發(fā)現(xiàn),在同類的close()中也使用了該對象監(jiān)視器的同步代碼塊,這也引出了另一層含義,刷新Spring上下文相當(dāng)于“創(chuàng)建”,而close()相當(dāng)于”銷毀”,共享同一個對象監(jiān)視器保證了對Spring上下文的兩種“侵入性”操作不能共存。此外該方法在AbstractApplicationContext中,很明顯的模板方法設(shè)計模式,prepareRefresh()進(jìn)行刷新前的準(zhǔn)備工作
圖6. AbstractApplicationContext的prepareRefresh()
方法中
initPropertySources()為空實現(xiàn)不用理會,getEnvironment()得到一個”標(biāo)準(zhǔn)環(huán)境對象”StandardEnvironment,而validateRequiredProperties()負(fù)責(zé)校驗加載時配置文件或者系統(tǒng)參數(shù)中存在必須配置但沒有配置的情況,所有必須配置的屬性存儲在AbstractPropertyResolver中的成員變量Set<String> requiredProperties中,如果檢測到需要配置而未配置的屬性拋出MissingRequiredPropertiesException。接著看圖5中obtainFreshBeanFactory()
圖7. AbstractApplicationContext的obtainFreshBeanFactory()
該方法顧名思義就是獲得新的
BeanFactory,總體實現(xiàn)思路超級簡單:1.創(chuàng)建BeanFactory,由refreshBeanFactory()完成;2.取出新的BeanFactory返回,由getBeanFactory()完成。先看看第一步
圖8. AbstractRefreshableApplicationContext的refreshBeanFactory()
首先根據(jù)
hasBeanFactory()判斷成員變量beanFactory是否為null,該方法由beanFactoryMonitor對象監(jiān)視器的同步塊包裹,如果已經(jīng)存在bean工廠先進(jìn)行銷毀,之后調(diào)用createBeanFactory()創(chuàng)建新的工廠,該方法中new了BeanFactory接口的子類DefaultListableBeanFactory,該類是bean工廠的對外暴露的核心類,這里同樣給出該類的類圖,供讀者在源碼中迷失時指引方向
圖9. DefaultListableBeanFactory的類圖
我們從圖中發(fā)現(xiàn)
DefaultListableBeanFactory不僅實現(xiàn)了BeanFactory接口,同時也實現(xiàn)了BeanDefinitionRegistry,在開篇中我們曾經(jīng)說過,對象存放在BeanFactory中,而“放”這個動作由BeanRegistry完成,現(xiàn)在兩者統(tǒng)一合成到了一個類中。這就像剛學(xué)Java面向?qū)ο笏枷氲臅r候老師舉過的一個例子,人去關(guān)燈這件事,如果要用面向?qū)ο蟮乃枷氡硎荆标P(guān)燈”這個動作應(yīng)該是封裝在燈對象中而不是人對象,隨著源碼分析的不斷深入,我們還會進(jìn)一步剖析DefaultListableBeanFactory 創(chuàng)建完后給bean工廠設(shè)置唯一標(biāo)識,該標(biāo)識和實現(xiàn)
Serializable后生成的serialVersionUID作用相似,都是保證序列化和反序列化后的對象一致性。
圖10. AbstractRefreshableApplicationContext的customizeBeanFactory(DefaultListableBeanFactory)
該方法用于設(shè)置
BeanFactory的自定義屬性,其實里面就兩個:allowBeanDefinitionOverriding,當(dāng)多個配置文件中存在同一個id或者name標(biāo)識的標(biāo)簽時是否用后加載配置文件中的內(nèi)容覆蓋先加載的,我們可以在創(chuàng)建ClassPathXmlApplicationContext后自主設(shè)置,注釋上說設(shè)為false就會發(fā)生覆蓋,設(shè)為true當(dāng)出現(xiàn)重復(fù)時會拋出異常,但恕我愚笨,無論怎么模擬都只發(fā)生了覆蓋但不拋出異常,希望讀者有知道的給我說說如何模擬;allowCircularReferences表示是否允許循環(huán)依賴,說到這個循環(huán)依賴我可是吃過它的虧。在第一家公司工作的時候項目組織的很混亂,Service層有時依賴Dao,有時又圖省事Service之間相互依賴,時不時出現(xiàn)循環(huán)依賴問題,造成項目無法啟動,關(guān)于這種問題如何解決我分享給大家一篇文章Circular Dependencies in Spring,里面提供了多種解決思路,這里不再贅述。最后一句代碼也是非常重要的,看著名字大家十有八九都能猜到它的功能與@Autowired和@Qualifier相關(guān)。在Spring中特別喜歡用xxxResolver、xxxHandler給類命名,表示對某個東西的處理類,注解的實現(xiàn)和運行原理也會專門開一篇詳述,這里不往下深入?;氐綀D8loadBeanDefinitions(DefaultListableBeanFactory)封裝了讀取配置文件并解析的流程
圖11. AbstractXmlApplicationContext的loadBeanDefinitions(DefaultListableBeanFactory)
該類的具體實現(xiàn)在父類
AbstractXmlApplicationContext中(如果迷失請看類圖),第一句創(chuàng)建了XmlBeanDefinitionReader的實例,和開篇我們推測的一樣,主要功能是將配置文件讀取到內(nèi)存中,之后為其設(shè)置“當(dāng)前運行環(huán)境”,其實就是上面設(shè)置過的StandardEnvironment。ResourceEntityResolver代表配置文件的解析器,xml的語法定義約束有兩種:DTD和XSD,在該解析器的父類DelegatingEntityResolver中就分別定義了兩種特定類型的解析器
圖12. DelegatingEntityResolver構(gòu)造器
此外在初始化
PluggableSchemaResolver中會加載默認(rèn)schemas,位置在每個Spring模塊的META-INF/spring.schemas,比如對于3.2.8.RELEASE的spring-beans模塊,默認(rèn)的schemas如下
圖13. 3.2.8.RELEASE版本spring-beans模塊的schemas
initBeanDefinitionReader(XmlBeanDefinitionReader)內(nèi)設(shè)置默認(rèn)解析xml配置文件需要驗證,流程走到loadBeanDefinitions(XmlBeanDefinitionReader)
圖14. AbstractXmlApplicationContext的loadBeanDefinition(XmlBeanDefinitionReader)
按照本文的例子啟動Spring容器時
configResources為空,所有的配置文件都存放在configLoactions數(shù)組中,當(dāng)然我們也可以啟動Spring容器的時候不傳入配置文件,如果不傳入的話getConfigLoactions()會調(diào)用子類XmlWebApplicationContext的getDefaultConfigLocations()返回默認(rèn)位置/WEB-INF/applicationContext.xml的配置文件。隨后用參數(shù)的reader加載配置文件
圖15. AbstractBeanDefinitionReader的loadBeanDefinitions(String, Set<Resource>)
流程走到
XmlBeanDefinitionReader的父類AbstractBeanDefinitionReader的上圖中方法,第一個參數(shù)是每一個配置文件,第二個參數(shù)為null。這里的resourceLoader就是ClassPathXmlApplicationContext的實例,因為該實例實現(xiàn)了ResourceLoader接口,具體的賦值過程在圖11中,流程進(jìn)入紅框處代碼,省略中間很多非重點調(diào)用,最終創(chuàng)建BeanDefinitionDocumentReader實例解析xml文件并進(jìn)行BeanDefinition的注冊
圖16. DefaultBeanDefinitionDocumentReader解析xml并注冊BeanDefinition入口
標(biāo)注1是進(jìn)來的第一步,
Document是Spring使用SAX解析xml之后得到的Document對象,然后獲取xml的根元素對象并傳入標(biāo)注2方法,首先處理Document中的profile屬性,使用該屬性的人可能不多,他就像maven中的<profiles><profile></profile></profiles>組合,可以根據(jù)運行的不同環(huán)境加載不同profile的值。之后創(chuàng)建一個委派類BeanDefinitionParserDelegate進(jìn)行xml文件解析的初始化,其中主要對頂層<beans>內(nèi)的屬性進(jìn)行解析,在BeanDefinitionParserDelegate中存儲了很多常量,每一個常量都與xml中一個標(biāo)簽或者屬性對應(yīng),其中就有一組<beans>中的屬性
圖17. BeanDefinitionParserDelegate中與<beans>屬性對應(yīng)的常量
屬性具體的用法請讀者翻閱Spring in Action或者其他資料,這里只分析流程不做詳細(xì)解釋,回到圖16。
preProcessor(Element)和postProcessXml(Element)分別用戶子類繼承重寫對xml解析進(jìn)行前置處理、后置處理。parseBeanDefinitions(Element, BeanDefinitionParserDelegate)對我們常用的,諸如<bean>、<context:property-placeholder>進(jìn)行解析
后記
為了突出主要流程,在文中留了很多可繼續(xù)深挖的擴(kuò)展點,這些擴(kuò)展點后期會用“外傳”的形式另起文章分析。隨著源碼閱讀的深入,對之前文章中知識點的理解必定也會有所不同,所以之后會隨時對Spring相關(guān)文章進(jìn)行修改更新