不管是自己做項目煤篙,還是工作中,Spring都是使用最多的框架猎荠。但是一直都沒有好好的了解一下,本篇博客的目的就是為了深入分析Spring最核心的概念之一:IOC容器的實現(xiàn)原理
通常我們在獲取Bean的時候炮车,會這么去做:
//創(chuàng)建Spring容器
?ApplicationContext ctx=new?ClassPathXmlApplicationContext("classpath*:beans.xml");??
//通過getBean()方法獲取Bean實例??
Person?person=(Person)?ctx.getBean("person");??
我們來看看這個ClassPathXmlApplicationContext里面是怎么樣的
下圖是ClassPathXmlApplicationContext的繼承體系:
下面我們就深入的來看看new ClassPathXmlApplicationContext()是做了什么:
一步步看矩屁,先看看super(parent);是在干什么:
? 1. super(parent):
結(jié)尾是在AbstractApplicationContext的構(gòu)造方法中吝秕,來看看這個構(gòu)造方法
?1.1 this():
? ? ? ? ? ? ? ? ? 首先是調(diào)用了上面的那個構(gòu)造器,我們先來看看this.resourcePatternResolover是什么飛機(jī)
也就是this的目的其實就是為了給這個resourcePatternResolver變量賦值鬓梅,我們來看看具體是怎么做的:
這里是直接new PathMatchingResourcePatternResolver(this); 話不多說繼續(xù)往下點
PathMatchingResourcePatternResolver :
一個ResourcePatternResolver 能夠解析指定資源位置到一個或者多個匹配資源的路徑绽快,源路徑可以是一個簡單的路經(jīng)芥丧,它具有到目標(biāo)為Resource,或者可能包含特殊的 classpath*: 前綴和 / 或者內(nèi)部Ant-style正則表達(dá)式
沒有通配符:
在簡單情況下坊罢,如果指定的位置路經(jīng)不是以 “classpath*:”為前綴续担,并且不包含PathMatcher模式,這個解析器將通過getResource()調(diào)用基礎(chǔ)ResourceLoader艘绍。例如 file:C:/context.xml赤拒,偽URL 例如: classpath:/context.xml,以及簡單的不固定路經(jīng) /WEB-INF/context.xml
Ant-style模式:
/WEB-INF/*-context.xml
com/mycompany/*applicationContext.xml
file:C:/some/path/*-context.xml
classpath:com/mycompany/*applicationContext.xml
第一步是設(shè)置給parent這個成員變量诱鞠,這里就不用說了挎挖,來看下Environment是個什么類吧
Environment :
表示當(dāng)前應(yīng)用程序運行環(huán)境的接口,為應(yīng)用程序環(huán)境的兩個關(guān)鍵方面建模 profiles 和 properties航夺,與屬性訪問相關(guān)的方法通過PropertyResolver 超級接口蕉朵。
profile :是要注冊的命名的邏輯Bean定義組,只有當(dāng)給定的配置文件處于活動狀態(tài)時阳掐,才使用容器始衅,可以分配bean到一個配置文件,無論是Xml定義的還是通過注解定義的 或者 @profile 注解缭保,與配置文件相關(guān)的Environment對象的角色是確認(rèn)哪些配置文件(如果有) 當(dāng)前是getActiveProfiles 汛闸,如果沒有則 getDefaultProfiles,這里大家應(yīng)該都有用到過applicationContext-dev.xml艺骂,applicationContext-prod.xml 這種選擇測試跟線上環(huán)境的方法诸老,就是在配置文件中添加
Properties:幾乎在所有應(yīng)用程序中發(fā)揮重要作用,并且可能源于各種源: 屬性文件钳恕,JVM系統(tǒng)屬性别伏,系統(tǒng)環(huán)境變量,JNDI忧额,servlet上下文參數(shù)厘肮,特殊屬性對象,Maps 等等.與屬性相關(guān)的環(huán)境對象的角色是為用戶提供方便的環(huán)境接口睦番,用于配置屬性源并且從中解析屬性
ApplicationContext 內(nèi)管理的bean可注冊為 EnvironmentAware 或者 Environment 以便直接查詢配置文件狀態(tài)或解析屬性类茂。
然而在大多情況下,application級別的bean不需要與Environment交互,但是必須有 ${...} 屬性 由屬性占位符配置程序PropertySourcesPlaceholderConfigurer巩检,其本身就是EnvironmentAware,從Spring 3.1開始恬涧,在使用時默認(rèn)注冊? <context:property-placeholder/>
環(huán)境對象的配置必須通過 ConfigurableEnvironment接口,返回來自所有AbstractApplicationContext子類 getEnvironment 方法
反正這個玩意看圖就知道了
到這里溯捆,setSuper(parent);這個方法就算是完了厦瓢√嶙幔總結(jié)一下:
? ??????1.在PathMatchingResourcePatternResolver中設(shè)置AbstractApplicationContext為resourceLoader
????????2.AbstractApplicationContext設(shè)置ResourcePatternResolver
? ? ? ? 3.?AbstractApplicationContext?設(shè)置ApplicationContext
先不要管為啥要這樣子設(shè)置,看到后面就知道了....
2. setConfigLocations(configLocations):
? ? ? ? ? ? ? ? 先不著急說什么煮仇,先把前面幾個方法給貼上劳跃,反正也沒幾個方法
在我們通過AbstractEnvironmentt的resolveRequiredPlaceholders方法再進(jìn)入到AbstractPropertyResolver中的過程這里再截圖說明一下:
將MutablePropertySources 設(shè)置進(jìn)PropertySourcesPropertyResolver的propertySources屬性
這個地方咱們還得回到之前的地方看看
總結(jié):
1.設(shè)置AbstractApplicationContext中的?ConfigurableEnvironment environment陕见;
2.解析我們ClassPathXmlApplicationContext傳入的資源中的占位符
3秘血、refresh()
好了味抖,終于到了最后的refresh方法了
先從第一個方法開始看吧 ==!
1仔涩、prepareRefresh()
首先是設(shè)置AbstractApplicationContext的一些初始化變量
initPropertySources();????????????????????????????在上下文環(huán)境中初始化任何占位符屬性源佩研,留給子類拓展的
這個方法主要是留給子類或者我們來自己拓展的柑肴,其實就是往AbstractPropertyResolver的 requiredProperties集合中添加元素,這個集合是用來存儲初始化時旬薯,必須存在的環(huán)境變量
接下里我們就來手動實驗一下這個方法該怎么來玩:
然后我們貼上測試圖:
2. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()
在Spring源碼深度解析這本書中是這么介紹以下這幾個類的:
? ? 1. BeanFactory: 定義獲取bean及bean的各種屬性
? ? 2. HierarchicalBeanFactory: 繼承BeanFactory,也就是BeanFactory定義的功能的基礎(chǔ)上增加了對? ? ? ? ? ? ? ? ? ? ? ????????parentFactory的支持阶捆。
? ? 3. ListableBeanFactory: 根據(jù)各種條件獲取bean的配置清單
? ? 4. ConfigurableBeanFactory: 根據(jù)配置Factory的各種方法
? ? 5. ConfigurableListableBeanFactory: beanFactroy配置清單凌节,指定忽略類型以及接口等。
? ? 6. DefaultListableBeanFactory:綜合上面的所有功能洒试,主要是對bean注冊后的處理.
XmlBeanFactory對DefaultListableBeanFactory類進(jìn)行了拓展倍奢,主要是用以從Xml文檔中讀取beanDefinition,對于注冊以及獲取Bean都是使用從父類DefaultListableBeanFactory繼承的方法去實現(xiàn),而唯獨與父類不同的個性化實現(xiàn)就是增加了XmlBeanDefinitionReader類型的reader屬性垒棋,在XmlBeanFactory中主要使用reader屬性對資源文件進(jìn)行讀取和注冊娱挨。
先看看這個getInternalParentBeanFactory()這個方法是在搞什么飛機(jī)
接著我們再來看看new DefaultListableBeanFactory的時候干了啥
小結(jié):
new DefaultListableBeanFactory:
? ? 1.new?AbstractBeanFactory()
? ? 2.AbstractBeanFactory中設(shè)置parentBeanFactory為ApplicationContext
? ? 3.AbstractAutowireCapableBeanFactory的忽略依賴關(guān)系接口集合屬性中添加了?BeanNameAware,BeanFactoryAware元潘,BeanClassLoaderAware
繼續(xù)往下看
然后到了最后也是obtainFreshBeanFactory方法最重要的一步了----->loadBeanDefinitions方法君仆,該方法的實現(xiàn)在子類AbstractXmlApplicationContext中翩概,因為這是個抽象方法
我們先進(jìn)這個new XmlBeanDefinitionReader這個里頭看看
這里先說一下EnvironmentCapable這個類(注釋翻譯):
接口牍鞠,指示包含并公開Environment引用的組件。
所有Spring應(yīng)用程序上下文都支持EnvironmentCapable评姨, 并且該接口主要用于在框架方法中執(zhí)行instanceof檢查
?這些框架方法接受可能是或可能不是應(yīng)用程序上下文實例的BeanFactory實例难述,以便與環(huán)境交互(如果確實可用)。
ApplicationContext擴(kuò)展了環(huán)境功能吐句,因此公開了 getenvironment()方法龄广;ConfigurableApplicationContext重新定義了getEnvironment方法 并縮小簽名以返回一個ConfigurableEnvironment,其效果是環(huán)境對象是“只讀”的蕴侧,直到從ConfigurableApplicationContext訪問它為止择同,此時也可以對其進(jìn)行配置。
到這里new XmlBeanDefinitionReader(beanFactory); 這個過程就結(jié)束了
總結(jié):
1.AbstractBeanDefinitionReader中設(shè)置registry屬性為DefaultListableBeanFactory
2.AbstractBeanDefinitionReader中設(shè)置resourceLoader 為PathMatchingResourcePatternResolver
3.AbstractBeanDefinitionReader中設(shè)置environment 為StandardEnvironment
4.設(shè)置XmlBeanDefinitionReader的Environment屬性為AbstractApplicationContext中的StandardEnvironment (在我們之前super(parent)這一步的時候給new的)
5.設(shè)置XmlBeanDefinitionReader的ResourceLoader為AbstractXmlApplicationContext
6.設(shè)置XmlBeanDefinitionReader的EntityResolver為ResourceEntityResolver
? ? ResourceEntityResolver:?EntityResolver實現(xiàn)净宵,嘗試通過ResourceLoader(通常是相對于ApplicationContext的資源庫)解析實體引用(如果適用),擴(kuò)展DelegatingEntityResolver以同時提供DTD和XSD查找敲才。允許使用標(biāo)準(zhǔn)XML實體將XML片段包含到應(yīng)用程序上下文定義中,例如將大型XML文件拆分為多個模塊择葡。include路徑可以像往常一樣相對于應(yīng)用程序上下文的資源庫紧武,而不是相對于JVM工作目錄(XML解析器的默認(rèn)值)。
注意:除了相對路徑之外敏储,在當(dāng)前系統(tǒng)根目錄(即jvm工作目錄)中指定文件的每個URL也將相對于應(yīng)用程序上下文進(jìn)行解釋阻星。
EntityResolver:對于解析一個xml,sax 首先會讀取該xml文檔上的聲明,根據(jù)聲明去尋找相應(yīng)的dtd定義,以便對文檔的進(jìn)行驗證,默認(rèn)的尋找規(guī)則,(即:通過網(wǎng)絡(luò),實現(xiàn)上就是聲明DTD的地址URI地址來下載DTD聲明),并進(jìn)行認(rèn)證,下載的過程是一個漫長的過程,而且當(dāng)網(wǎng)絡(luò)不可用時,這里會報錯,就是應(yīng)為相應(yīng)的dtd沒找到,詳細(xì)的用法跟說明大家可以看看這個博客:https://www.cnblogs.com/ghgyj/p/4027796.html已添,我就不再去演示了
initBeanDefinitionReader(beanDefinitionReader):
loadBeanDefinitions(beanDefinitionReader):
這里我們主要是看reader.loadBeanDefinitions(configLocations)這里尚胞,首先這個getConfigLocations(),我們在第二步setConfigLocations()的時候解总,其實就是把我們在new ClassPathXmlApplicationContext時所傳的資源付給了AbstractRefreshableConfigApplicationContext的this.configLocations,所以這里只是把我們之前給設(shè)置進(jìn)來的資源給拿出來而已 ==?
流程開始:
1.?getResourceLoader(): 這一步在new XmlBeanDefinitionReader的時候刊头,就已經(jīng)把resourceLoader給賦值為PathMatchingResourcePatternResolver這個對象黍瞧,在上面就總結(jié)就可以看得到
2.?因為PathMatchingResourcePatternResolver它實現(xiàn)了ResourcePatternResolver接口,所以這里我們直接看getResources(location)這個方法
PathMatcher : 實現(xiàn)Ant-style的路徑模式
映射使用以下規(guī)則匹配URL:
原杂?:匹配一個字符
* :匹配零個或多個字符
** : 匹配路徑中的零個或多個目錄
spring:[a-z]+}: 將regexp {[a-z]+} 作為名為“spring”的路徑變量匹配
這里我們就先看這個findAllClassPathResources這個方法:
先來看看這個getClassLoader()方法印颤,這個getResourceLoader也是獲取PathMatchingResourcePatternResolver類中的ResourceLoader,在第一步super(parent); 這里我們已經(jīng)為PathMatchingResourcePatternResolver設(shè)置好了resourceLoader
這個地方我們打個斷點往下看污尉,這樣可以看得更清晰一點:
那么在findAllClassPathResources這個方法的目的就是返回我們資源文件的磁盤路經(jīng)? ?ψ(*`ー′)ψ
現(xiàn)在回到我們AbstractBeanDefinitionReader中
我們接著下一個loadBeanDefinitions(resources)這個方法往下看
1.獲取當(dāng)前線程中的EncodedResource集合
2.如果添加失敗被碗,則認(rèn)為是該encodedResource在循環(huán)加載
3.獲取資源文件InputStream對象
doLoadDocument : 使用配置的DocumentLoader實際加載指定的文檔某宪。
這個地方我覺得有必要提一下getEntityResolver這個方法:
接著來看下一個方法
int count = registerBeanDefinitions(doc, resource);
BeanDefinitionDocumentReader:用于解析包含Spring bean定義的XML文檔,由XmlBeanDefinitionReader用于實際解析DOM文檔。
int countBefore = getRegistry().getBeanDefinitionCount() : 獲取bean定義對象Map的大小
getRegistry其實就是獲取之前我們給XmlBeanDefinitionReader設(shè)置的DefaultListableBeanFactory,是在AbstractXmlApplicationContext類的loadBeanDefinitions方法中new XmlBeanDefinitionReader的時候給賦值了
documentReader.registerBeanDefinitions(doc, createReaderContext(resource)):
先來看看這個createReaderContext方法是做了什么吧:
DefaultNamespaceHandlerResolver:NamespaceHandlerResolver接口的默認(rèn)實現(xiàn)慈俯。根據(jù)映射文件中包含的映射將名稱空間uri解析為實現(xiàn)類。
? ? ? ? ? ? ? ? ReaderContext (設(shè)置屬性):
? ? ? ? ? ? ? ? ? ? ? ? 1.resource =?Resource
? ? ? ? ? ? ? ? ? ? ? ? 2.problemReporter =?FailFastProblemReporter
? ? ? ? ? ? ? ? ? ? ? ? 3.?eventListener? ?=??EmptyReaderEventListener
? ? ? ? ? ? ? ? ? ? ? ? 4.?sourceExtractor? =?NullSourceExtractor
? ??????????????XmlReaderContext (設(shè)置屬性):
? ? ? ? ? ? ? ? ? ? ? ? 1. reader? =??XmlBeanDefinitionReader
? ? ? ? ? ? ? ? ? ? ? ? 2.?namespaceHandlerResolver =??DefaultNamespaceHandlerResolver
? ? ? ? ? ? ? ? createReaderContext() : 創(chuàng)建XmlReaderContext對象,接下來我們回到registerBeanDefinitions方法:
this.delegate = createDelegate(getReaderContext(), root, parent):
1.創(chuàng)建一個與所提供的相關(guān)聯(lián)的新的BeanDefinitionParserDelegate
this.defaults =?DocumentDefaultsDefinition對象 (在標(biāo)準(zhǔn)的Spring XML bean定義文檔中畜吊,簡單的JavaBean包含在<beans>級別指定的默認(rèn)值:default-lazy-init,default-autowire户矢,etc)
populateDefaults :?
到這里populateDefaults這個方法就算是結(jié)束了挂洛,來個很有儀式感的總結(jié)吧:?設(shè)置Xml資源文件根節(jié)點某些屬性默認(rèn)的值
DocumentDefaultsDefinition :這個使我們傳入populateDefaults方法中的this.defaults礼预,該DocumentDefaultsDefinition 是不可變的,所以接下來的設(shè)置是有必要給記錄下來的
1.DocumentDefaultsDefinition 設(shè)置lazyInit為false
2.DocumentDefaultsDefinition 設(shè)置merge為false
3.DocumentDefaultsDefinition 設(shè)置autowire為no
4.DocumentDefaultsDefinition? 設(shè)置source 為null
this.readerContext.fireDefaultsRegistered(this.defaults):
調(diào)用XmlReaderContext的方法虏劲,在父類ReaderContext中找到實現(xiàn)
先打開這個類來看看是咋樣的吧
? ? 這個地方我可以確定我沒有搞錯托酸,因為這個地方反正一定是從創(chuàng)建XmlReaderContext的時候給賦值的
那這里就啥都沒干....然后返回delegate....那 initDefaults方法就走完了......┓(;′_`)┏.....
接下來是獲取beans的profile屬性,并且從環(huán)境中去對比是否是spring.profiles.active對于的資源文件柒巫,這個大家應(yīng)該都有用過這種方式励堡,我簡單說下吧
1.校驗文件不為空并且不以 ! 開頭
2.獲取一個設(shè)置好的profile資源集合堡掏,下圖中有實現(xiàn)方式
3.校驗是否存在activeProfiles集合中应结,或者設(shè)置好的profile資源集合為空,校驗currentActiveProfiles為空 并且 defaultProfiles集合中存在該profile(doGetDefaultProfiles方法的原理與doGetActiveProfiles一樣)
下面我們就寫個Demo來玩玩這個profile :
既然是在環(huán)境中找迎卤,那么我們往環(huán)境中加上這個映射不就可以了嗎
好了,再回到我們的doRegisterBeanDefinitions方法中接著往下看:
preProcessXml(root):
parseBeanDefinitions(root, this.delegate):
1.import
一開始是判斷resource屬性是否為空,大家應(yīng)該也都用過import企软,我們通常會使用import來分割規(guī)劃我們的applicationContext資源文件庐扫,而resource就是指向這些分割的文件的,那當(dāng)然是不能為空的(雖然很廢話仗哨,但還是得說)
location = getReaderContext().getEnvironment().resolveRequiredPlaceholders(location): 從AbstractBeanDefinitionReader中獲取environment形庭,我們在看obtainFreshBeanFactory該步驟的源碼中,AbstractBeanDefinitionReader的environment給設(shè)置為了StandardEnvironment對象厌漂,還有就是resolveRequiredPlaceholders這個方法我們在看setConfigLocations的源碼時也說過萨醒,這個地方就是在解析我們傳入的占位符(${JAVA_HOME}),大家還記得我寫的那個demo嗎苇倡。new ClassPathXmlApplicationContext("classpath*:applicationContext.xml;${JAVA_HOME}");
absoluteLocation = ResourcePatternUtils.isUrl(location) || ResourceUtils.toURI(location).isAbsolute() : 校驗該資源路徑是否是相對路徑或者絕對路徑
1.返回給定的資源位置是否是URL:一個特殊的“類路徑”或“類路徑*”偽URL或一個標(biāo)準(zhǔn)URL富纸。(不等于null并且以classpath*:開頭)
2.直接使用URL對象的isAbsolute方法來判斷是否是一個絕對路徑
Resource[] actResArray = actualResources.toArray(new Resource[0])
getReaderContext().fireImportProcessed(location, actResArray, extractSource(ele))
現(xiàn)在來看最后這個地方是在處理啥:
2.alias
首先也不用說了,那就是判斷alias標(biāo)簽的兩個必須要填的屬性是否為空钩乍,這個大家肯定是經(jīng)常都會使用到的辞州,就算現(xiàn)在沒有使用過,以前肯定也是使用過的寥粹,直接進(jìn)方法看實現(xiàn)吧
getReaderContext().getRegistry().registerAlias(name, alias):
首先我們知道這里獲取的reader是我們的XmlBeanDefinitionReader對象变过,然后我們調(diào)用了它的getRegistry方法埃元,然后就走到了AbstractBeanDefinitionReader中,然后獲取它的registry屬性媚狰,這里我還是要多嘴一句岛杀,還是我們在看obtainFreshBeanFactory()方法的源碼時給設(shè)置的屬性,屬性值為DefaultListableBeanFactory,實現(xiàn)方法是在SimpleAliasRegistry類中
checkForAliasCircle(name, alias):檢查給定名稱是否已經(jīng)指向另一個方向的給定別名作為別名
this.aliasMap.put(alias, name);? 添加進(jìn)alias與 id 的映射
字?jǐn)?shù)太多辨宠,沒辦法發(fā)布 ==遗锣,后面的寫在第二篇,感謝瀏覽喲~~