前言
上一篇文章分析了Bean工廠的創(chuàng)建,其真正的實現(xiàn)類和核心為DefaultListableBeanFactory
谬莹,XML配置文件是封裝成了Resource
檩奠,由XmlBeanDefinitionReader
進行了加載约素,最后在BeanDefinitionParserDelegate
解析類中將XML根元素<beans>
中的屬性進行了處理。現(xiàn)在所有的準備工作完成笆凌,正式進入我們嚴格意義上的“有效”標簽的解析
前面說到,默認情況下DefaultBeanDefinitionDocumentReader
對于XML的前處理和后處理都為空實現(xiàn)士葫,真正的處理邏輯在parseBeanDefinitions(Element, DefaultBeanDefinitionDocumentReader)
中
兩次判斷紅框內(nèi)代碼判斷根元素和其下所有子元素是否處在默認名稱空間定義內(nèi)乞而,來看一下怎么判斷的
根據(jù)獲得的節(jié)點名稱空間判斷如果為空或者等于常量
BEANS_NAMESPACE_URI
即為默認名稱空間,該常量的值為http://www.springframework.org/schema/beans
慢显,正是XML最基礎(chǔ)的名稱空間爪模,回到圖1,根據(jù)當前節(jié)點是否屬于默認名稱空間下分別調(diào)用紅線處兩個不同的方法荚藻,目前我們只分析默認名稱空間下標簽解析流程我們根據(jù)判斷的四個常量可知屋灌,這里的默認標簽元素分別為
<import>
、<alias>
应狱、<bean>
和<beans>
處理
<bean>
大體可分為兩步:1. 解析<bean>
共郭;2. 注冊bean
實例,與本文名稱契合的這兩部分并不是為了讓大家更好的理解Spring運行流程特意杜撰的疾呻,而是Spring內(nèi)部確實是這么劃分的除嘹,上圖中的兩行代碼就對應(yīng)了這兩個部分,我們先來第一部分<bean>
解析流程經(jīng)過一段方法調(diào)用走到解析委派類的parseBeanDefinitionElement(Element, BeanDefinition)
中標注1解析
<bean>
的id和name屬性岸蜗,其中name屬性可以配置多個尉咕,會將多個name轉(zhuǎn)成alias數(shù)組。標注2判斷id屬性是否存在璃岳,如果存在賦值給beanName
年缎,不存在用別名數(shù)組中第一個別名作為beanName
的值,標注3方法判斷beanName
或者別名數(shù)組中的別名沒有使用過铃慷,也就是說一個XML配置文件中同id或者同名不能重復(fù)注冊也許有人產(chǎn)生疑問了单芜,在第一篇文章中命名說到可以存在同名的
<bean>
,這里怎么又不可以了呢枚冗?其實答案在保存使用過的名稱集合usedNames
中寫的很清楚缓溅,在同一層次<beans>
下的所有<bean>
只能存在唯一不重復(fù)的ids/names
,但是如果有多個XML配置文件赁温,兩個相同的ids/names
出現(xiàn)在不同的配置文件中坛怪,這種情況是可以被允許的。初始化加載時usedNames
內(nèi)沒有內(nèi)容股囊,每一次<bean>
的解析都會向內(nèi)存儲對應(yīng)的id/name
和所有的alias
袜匿。回到圖5標注4創(chuàng)建出bean
的實例標注1將當前
<bean>
壓入成員變量parseState
內(nèi)部棧的棧頂稚疹,該對象用于記錄解析到的一些重要標簽或者屬性對象居灯,當解析發(fā)生錯誤時就可以知道到底解析到哪個對象出現(xiàn)了問題祭务。標注2很明顯是解析class
和parent
屬性,其中的最后一句會生成AbstractBeanDefinition
的子類GenericBeanDefinition
怪嫌,其中根據(jù)是否設(shè)置bean
的類加載器決定該類中的變量beanClass
保存的是className
還是類的實例义锥。因為本文的例子中剛剛進入到解析XML和創(chuàng)建對應(yīng)BeanFactory
的流程,此時類加載器為null
岩灭,為了證明這點我特意debug截圖為證從上圖可以很清楚的看到此時
classLoader = null
拌倍,那么創(chuàng)建的GenericBeanDefinition
中保存真實對象的變量beanClass
保存的實際上是類的名稱而不是類的實例,記住這點非常關(guān)鍵否則下面沒法分析圖7標注3
parseBeanDefinitionAttributes(Element, String, BeanDefinition, AbstractBeanDefintion)
根據(jù)spring不同的版本解析諸如scope
噪径、abstract
柱恤、lazy-init
等屬性。標注4三個方法解析<meta>
找爱、<lookup-method>
和<replaced-method>
子標簽梗顺,其中<meta>
用于向BeanDefinition
中設(shè)置任意key-value
對;<lookup-method>
通常用于設(shè)置方法返回bean
的類型车摄;<replaced-method>
使得一個類的某個方法可以被實現(xiàn)MethodReplacer
接口的類的reimplement(Object, Method, Object[])
代替寺谤。標注5中第一句就是大家熟悉的通過構(gòu)造器實現(xiàn)注入的解析方式,對應(yīng)的標簽為<constructor-arg>
练般,代碼清單1
public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
// (1)
String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
// (2)
if (StringUtils.hasLength(indexAttr)) {
try {
int index = Integer.parseInt(indexAttr);
if (index < 0) {
error("'index' cannot be lower than 0", ele);
}
else {
try {
// (3)
this.parseState.push(new ConstructorArgumentEntry(index));
// (4)
Object value = parsePropertyValue(ele, bd, null);
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
valueHolder.setSource(extractSource(ele));
// (5)
if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
error("Ambiguous constructor-arg entries for index " + index, ele);
}
else {
bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
}
}
finally {
this.parseState.pop();
}
}
}
catch (NumberFormatException ex) {
error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
}
}
else {
try {
// (6)
this.parseState.push(new ConstructorArgumentEntry());
Object value = parsePropertyValue(ele, bd, null);
ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
if (StringUtils.hasLength(typeAttr)) {
valueHolder.setType(typeAttr);
}
if (StringUtils.hasLength(nameAttr)) {
valueHolder.setName(nameAttr);
}
valueHolder.setSource(extractSource(ele));
bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
}
finally {
this.parseState.pop();
}
}
}
我們可以根據(jù)參數(shù)的三種屬性之一進行注入矗漾,第一種為下標,初始從0開始薄料;第二種為根據(jù)參數(shù)類型敞贡,比如是java.lang.String
還是java.lang.Integer
,注意摄职,對于基本數(shù)據(jù)類型是無法通過構(gòu)造器注入的誊役,需要轉(zhuǎn)成基本數(shù)據(jù)類型的包裝數(shù)據(jù)類型;第三種是通過參數(shù)的名稱注入谷市,這種沒什么好說的蛔垢。只有采用第一種基于下標的注入方式才會進入到if
中,標注3首先構(gòu)建出一個Entry
的實現(xiàn)類ConstructorArgumentEntry
迫悠,并壓入棧頂鹏漆,這里的Entry
是變量parseState
的一個內(nèi)部標記接口,只有實現(xiàn)該接口的對象才能被放入parseState
內(nèi)部的棧內(nèi)
標注4主要做了兩件事:1. 處理<constructor-arg>
中對應(yīng)的ref
和value
等屬性创泄;2. 解析<constructor-arg>
內(nèi)部的子標簽艺玲,具體流程我們來看一下
標注1解析出類型為
Element
,標簽名不為description
也不為meta
的子標簽鞠抑,引用指向subElement
饭聚。標注2解析<constructor-arg>
的ref
和value
屬性,確保兩個屬性不能同時出現(xiàn)搁拙。標注3得到ref
的值封裝成RuntimeBeanReference
對象返回秒梳。標注4得到value
的值封裝成TypeStringValue
返回法绵。如果上面解析到<constructor-arg>
的子標簽元素就會調(diào)用標注5的方法,該方法內(nèi)包含一些大家可能還比較熟悉的標簽酪碘,比如<list>
朋譬、<map>
等,感興趣的讀者可以繼續(xù)往下深入兴垦,這里就不做分析了回到代碼清單1看標注5處此熬,上面說的在這個
if
中都是存在index
屬性的,這里就是判斷解析到的index
值是否已經(jīng)存在滑进,在ConstructorArgumentValues
對象中持有一個Map<Integer, ValueHolder>
集合,其中key
即為下標募谎,value
為下標對應(yīng)標簽值封裝的對象扶关,如果通過驗證就往map
中塞入對應(yīng)的鍵值對。標注6處對應(yīng)著else
的部分表示在沒有index
屬性時解析<constructor-arg>
的過程数冬,同樣調(diào)用parsePropertyValue(Element, BeanDefinition, String)
方法节槐,封裝ValueHolder
實例,根據(jù)屬性類型的不同進行不同的設(shè)置拐纱,之前存在index
屬性的標簽對象存放在map
中铜异,而不存在index
屬性的標簽對象放在鏈表中繞了一個大圈子終于分析完
<constructor-arg>
及其屬性、子標簽的解析流程秸架,回到圖7看標注5的第二句parsePropertyElements(Element, BeanDefinition)
揍庄,該方法和解析其他標簽一開始的思路一致,都是遍歷所有子標簽东抹,判斷標簽名稱是否為property
蚂子,是則進入parsePropertyElement(Element, BeanDefinition)
首先獲得
<Property>
必填屬性name
的值,如若沒有記錄錯誤日志并拋出異常缭黔,對于一個BeanDefinition
來說食茎,對象內(nèi)存在一個MutablePropertyValues
對象,該對象中維護了一個List<PropertyValue>
集合馏谨,該集合就保存了所有的<property>
的name-value/ref
對别渔,既然如此,在解析的過程中必然要判斷集合中是否已經(jīng)存在同名稱的PropertyValue
惧互,如果存在自然也會報錯哎媚,不存在就需要真正解析<property>
,而解析的過程和解析<constructor-arg>
調(diào)用的方法一樣壹哺,見圖9抄伍,與前者解析唯一不同的地方在于第三個參數(shù),對于<constructor-arg>
來說propertyName
為null
管宵,而<property>
自然就是屬性name
的值了截珍,解析并創(chuàng)建PropertyValue
對象后攀甚,同樣要使用parseMetaElement(Element, BeanMetadataAttributeAccessor)
解析內(nèi)嵌的<meta>
標簽,最后塞入List<PropertyValue>
中圖7標注5中最后一句用來解析
<qualifer>
岗喉,在注解注入大行其道的編程界秋度,相信也很少人使用這種方式進行注入操作,在這里就不展開分析了钱床,關(guān)于幾種注解注入的方式的源碼解析后面會有單獨文章分析荚斯。至此我們終于又回到了圖5解析<bean>
大綱流程,看一看標注4和標注5之間的代碼做了什么查牌,如果我們在配置<bean>
時既沒有填寫id
事期,也沒有填寫name
,Spring會為我們生成一個name
纸颜,由于containingBean
在這里為null
兽泣,因此最終的流程會走到else
內(nèi),最終使用BeanDefinitionReaderUtils
來生成beanName
首先獲得
BeanDefinition
中的class name
胁孙,因為上面的步驟已經(jīng)解析過<bean class="">
唠倦,此時必然有值,再有第三個參數(shù)isInnerBean = false
(至于為什么請讀者順著流程走一次便知)涮较,因此最后形成返回值的公式即為while
中的generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
稠鼻,其中的常量值為#
,舉個例子狂票,如果<bean class = "com.xiaomi.Student/>"
候齿,那么最終生成的beanName
即為com.xiaomi.Student#0
。將解析的所有這一些按圖5標注5代碼封裝成BeanDefinitionHolder
后標志著<bean>
全部解析完下面我們開始
Bean
實例的注冊流程進入圖3第二個下劃線代碼內(nèi)闺属,在正式分析之前我們需要先看幾個重要的成員變量毛肋,第一篇文章中曾經(jīng)說過BeanFactory
的核心實現(xiàn)類為DefaultListableBeanFactory
,在該類中有幾個非常重要的成員變量先來說兩個現(xiàn)在要用到的屋剑,
beanDefinitionMap
和beanDefinitionNames
润匙,前者保存有id/beanName-bean實例
鍵值對,后者保存所有的beanName
注冊
bean
的邏輯遠遠沒有解析<bean>
的復(fù)雜唉匾,上圖中的紅框內(nèi)即為核心代碼孕讳。首先從beanDefinitionMap
根據(jù)beanName
查找是否存在對應(yīng)的實例,如果存在判斷是否開啟了實例覆蓋標識巍膘,沒有開啟拋出異常厂财,再將beanName
和對應(yīng)實例放入beanDefinitionMap
中,最后一句的作用是清除所有beanName
所屬實例及其衍生類的本地緩存信息
后記
即便有意隱去的非重點流程峡懈,<bean>
解析及注冊的調(diào)用關(guān)系依然很深璃饱,我們宏觀上只需要記住經(jīng)過本文對應(yīng)的處理,Spring解析了所有XML配置文件肪康,生成的bean工廠荚恶,但此時bean工廠中<bean>
對應(yīng)的實例并沒有真正創(chuàng)建撩穿。回想Spring解析之IoC:XML配置文件的加載及BeanFactory的創(chuàng)建中圖5谒撼,Spring初始化的總綱食寡,現(xiàn)在也才走到第二步,漫漫長路剛剛開始廓潜,繼續(xù)努力吧抵皱!