spring 系列 轉(zhuǎn)載自掘金 VipAugus https://juejin.cn/user/2348212565601415/posts
Spring
解析默認(rèn)標(biāo)簽~
[toc]
從上一篇筆記可以看出问词,在容器注冊(cè) bean
信息的時(shí)候吃引,做了很多解析操作,而 xml
文件中包含了很多標(biāo)簽粗悯、屬性,例如 bean
、 import
標(biāo)簽, meta
存捺、look-up
和 replace
等子元素屬性畔派。
上一篇主要介紹 Spring
容器的基礎(chǔ)結(jié)構(gòu)铅碍,沒有細(xì)說這些標(biāo)簽是如何解析的。
所以本篇是來(lái)進(jìn)行補(bǔ)坑的线椰,介紹這些標(biāo)簽在代碼中是如何識(shí)別和解析的~
本篇筆記的結(jié)構(gòu)大致如下:
- 介紹概念
- 展示
demo
代碼胞谈,如何使用 - 結(jié)合源碼分析
- 聊聊天和思考
再次說下,下載項(xiàng)目看完整注釋憨愉,跟著源碼一起分析~
在 Spring
中烦绳,標(biāo)簽有兩種,默認(rèn)和自定義:
-
默認(rèn)標(biāo)簽 這是我們最常使用到的標(biāo)簽類型了配紫,像我們一開始寫的
<bean id="book" class="domain.SimpleBook"/>
径密,它屬于默認(rèn)標(biāo)簽,除了這個(gè)標(biāo)簽外躺孝,還有其它四種標(biāo)簽(import
享扔、alias
、bean
植袍、beans
) -
自定義標(biāo)簽 自定義標(biāo)簽的用途惧眠,是為了給系統(tǒng)提供可配置化支持,例如事務(wù)標(biāo)簽
<tx:annotation-driven />
于个,它是Spring
的自定義標(biāo)簽氛魁,通過繼承NamespaceHandler
來(lái)完成自定義命名空間的解析。
先看源碼是如何區(qū)分這兩者:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
// 注釋 1.12 遍歷 doc 中的節(jié)點(diǎn)列表
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
// 注釋 1.13 識(shí)別出默認(rèn)標(biāo)簽的 bean 注冊(cè)
// 根據(jù)元素名稱厅篓,調(diào)用不同的加載方法秀存,注冊(cè) bean
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
復(fù)制代碼
可以看到,在代碼中羽氮,關(guān)鍵方法是 delegate.isDefaultNamespace(ele)
進(jìn)行判斷或链,識(shí)別掃描到的元素屬于哪種標(biāo)簽。
找到命名空間 NamespaceURI
變量档押,如果是 http://www.springframework.org/schema/beans
株扛,表示它是默認(rèn)標(biāo)簽,然后進(jìn)行默認(rèn)標(biāo)簽的元素解析汇荐,否者使用自定義標(biāo)簽解析洞就。
本篇筆記主要記錄的是默認(rèn)標(biāo)簽的解析,下來(lái)開始正式介紹~
默認(rèn)標(biāo)簽解析
parseDefaultElement
方法用來(lái)解析默認(rèn)標(biāo)簽掀淘,跟蹤下去旬蟋,發(fā)現(xiàn)對(duì)四種標(biāo)簽做了不同的處理,其中 bean
標(biāo)簽的解析最為艱難(對(duì)比其它三種)革娄,所以我們將 bean
標(biāo)簽解析吃透的話倾贰,其它三種標(biāo)簽的解析也能更好的熟悉冕碟。
入口方法:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
// 注釋 2.1 默認(rèn)標(biāo)簽解析
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
// 解析 import 標(biāo)簽
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
// 解析 alias 標(biāo)簽
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
// 解析 bean 標(biāo)簽的方法
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
// 解析 beans 標(biāo)簽,其實(shí)就是遞歸匆浙,重新對(duì)這個(gè) element 下的標(biāo)簽進(jìn)行注冊(cè)解析
doRegisterBeanDefinitions(ele);
}
}
復(fù)制代碼
Bean 標(biāo)簽解析入口
定位到上面第三個(gè)方法 processBeanDefinition(ele, delegate)
:
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
// 注釋 1.15 解析 bean 名稱的元素
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// Register the final decorated instance. (注釋 1.16 注冊(cè)最后修飾后的實(shí)例)
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// Send registration event. 通知相關(guān)的監(jiān)聽器安寺,表示這個(gè) bean 已經(jīng)加載完成
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
復(fù)制代碼
上一篇筆記只是簡(jiǎn)單描述這個(gè)方法的功能:將 xml
中配置的屬性對(duì)應(yīng)到 document
對(duì)象中,然后進(jìn)行注冊(cè)首尼,下面來(lái)完整描述這個(gè)方法的處理流程:
-
創(chuàng)建實(shí)例 bdHolder:首先委托
BeanDefinitionParserDelegate
類的parseBeanDefinitionElement
方法進(jìn)行元素解析挑庶,經(jīng)過解析后,bdHolder
實(shí)例已經(jīng)包含剛才我們?cè)谂渲梦募性O(shè)定的各種屬性软能,例如class
迎捺、id
、name
查排、alias
等屬性凳枝。 - 對(duì)實(shí)例 bdHolder 進(jìn)行裝飾:在這個(gè)步驟中,其實(shí)是掃描默認(rèn)標(biāo)簽下的自定義標(biāo)簽跋核,對(duì)這些自定義標(biāo)簽進(jìn)行元素解析岖瑰,設(shè)定自定義屬性。
-
注冊(cè) bdHolder 信息:解析完成了砂代,需要往容器的
beanDefinitionMap
注冊(cè)表注冊(cè)bean
信息蹋订,注冊(cè)操作委托給了BeanDefinitionReaderUtils.registerBeanDefinition
,通過工具類完成信息注冊(cè)泊藕。 -
發(fā)送通知事件:通知相關(guān)監(jiān)聽器辅辩,表示這個(gè)
bean
已經(jīng)加載完成
看到這里难礼,同學(xué)們應(yīng)該能看出娃圆,Spring
源碼的接口和方法設(shè)計(jì)都很簡(jiǎn)潔,上層接口描述了該方法要做的事情蛾茉,然后分解成多個(gè)小方法讼呢,在小方法中進(jìn)行邏輯處理,方法可以被復(fù)用谦炬。
所以看源碼除了能了解到框架的實(shí)現(xiàn)邏輯悦屏,更好的去使用和定位問題,還能夠?qū)W習(xí)到大佬們寫代碼時(shí)的設(shè)計(jì)模式键思,融入自己的工作或者學(xué)習(xí)中~
Bean 標(biāo)簽其它屬性的解析過程
在上篇筆記中础爬,已經(jīng)總結(jié)了對(duì)屬性 id
和 name
的解析,不再贅述吼鳞,下面講下對(duì)標(biāo)簽其它屬性的解析~
首先貼下源碼:
org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, java.lang.String, org.springframework.beans.factory.config.BeanDefinition)
public AbstractBeanDefinition parseBeanDefinitionElement(
Element ele, String beanName, @Nullable BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
String className = null;
// 注釋 2.3 解析 class 屬性
if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
}
String parent = null;
// 解析 parent 屬性
if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
parent = ele.getAttribute(PARENT_ATTRIBUTE);
}
...
// 創(chuàng)建 GenericBeanDefinition
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
// 解析默認(rèn) bean 的各種屬性
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
// 提取描述 desc
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
// 解析 meta 屬性
parseMetaElements(ele, bd);
// 解析 lookup-method 屬性
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
// 解析 replace-method 屬性
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
// 解析構(gòu)造函數(shù)
parseConstructorArgElements(ele, bd);
// 解析 property 子元素
parsePropertyElements(ele, bd);
// 解析 qualifier 子元素
parseQualifierElements(ele, bd);
bd.setResource(this.readerContext.getResource());
bd.setSource(extractSource(ele));
return bd;
...
}
復(fù)制代碼
這是一個(gè)完整的屬性解析過程看蚜,包含了 meta
、lookup-method
赔桌、replace-mthod
等其它屬性解析供炎。
雖然不常用到渴逻,但大家多學(xué)一個(gè)屬性,到時(shí)遇到適合使用的場(chǎng)景就能進(jìn)行使用音诫,還有遇到這些屬性的問題也不用慌張惨奕,我會(huì)先講有什么用,還有如何使用竭钝,讓大家有個(gè)印象~
創(chuàng)建 GenericBeanDefinition
關(guān)于 GenericBeanDefinition
的繼承體系上一篇已經(jīng)講過了梨撞,所以這里再簡(jiǎn)單解釋一下這個(gè)方法的用途:
createBeanDefinition(className, parent);
從方法名字就能看出,它的用途是創(chuàng)建一個(gè) beanDefinition
蜓氨,用于承載屬性的實(shí)例聋袋。
在最后一步實(shí)例化 GenericBeanDefinition
時(shí),還會(huì)判斷類加載器是非存在穴吹。如果存在的話幽勒,使用類加載器所在的 jvm
來(lái)加載類對(duì)象,否則只是簡(jiǎn)單記錄一下 className
港令。
解析默認(rèn) bean 的各種屬性
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
這個(gè)方法解析的代碼實(shí)現(xiàn)有點(diǎn)多啥容,所以感興趣的同學(xué),可以在我上傳的代碼庫(kù)中全局搜索找到該方法顷霹,里面有對(duì)它每個(gè)方法用途介紹~
簡(jiǎn)單描述的話咪惠,這個(gè)方法是用來(lái)解析 <bean>
標(biāo)簽中每一個(gè)基礎(chǔ)屬性,列表如下:
- scope
- abstract
- lazy-init
- autowire
- depends-on
- autowire-candidate
- primary
- init-method
- destroy-method
- factory-method
- factory-bean
可以清晰看到淋淀, Spring
完成了對(duì)所有 bean
屬性的解析遥昧,有些經(jīng)常使用到,例如 autowire
自動(dòng)織入朵纷、init-method
定義初始化調(diào)用哪個(gè)方法炭臭。而有些的話,就需要同學(xué)們自己深入學(xué)習(xí)了解~
解析 meta 屬性
先講下 meta
屬性的使用(汗袍辞,在沒了解前鞋仍,基本沒使用該屬性=-=)
<bean id="book" class="domain.SimpleBook">
<!-- 元標(biāo)簽 -->
<meta key="test_key" value="test_value"/>
</bean>
復(fù)制代碼
這個(gè)元屬性不會(huì)體現(xiàn)在對(duì)象的屬性中,而是一個(gè)額外的聲明搅吁,在 parseMetaElements(ele, bd);
方法中進(jìn)行獲取威创,具體實(shí)現(xiàn)是 element
對(duì)象的 getAttribute(key)
,將設(shè)定的元屬性放入 BeanMetadataAttributeAccessor
對(duì)象中
因?yàn)榇a比較簡(jiǎn)單谎懦,所以通過圖片進(jìn)行說明:
最終屬性值是以 key-value
形式保存在鏈表中 Map<String, Object> attributes
肚豺,之后使用只需要根據(jù) key
值就能獲取到 value
。想到之后在代碼設(shè)計(jì)上界拦,為了擴(kuò)展性吸申,也可以進(jìn)行 key-value
形式存儲(chǔ)和使用。
解析 lookup-method 屬性
這個(gè)屬性也是不常用,引用書中的描述
通常將它成為獲取器注入呛谜。獲取器注入是一個(gè)特殊的方法注入在跳,它是把一個(gè)方法聲明為返回某種類型的
bean
,但實(shí)際要返回的bean
是在配置文件里面配置的隐岛,次方法可用在設(shè)計(jì)有些可插拔的功能上猫妙,解除程序依賴。
代碼寫的有點(diǎn)多聚凹,我貼張圖片割坠,介紹一下關(guān)鍵信息:
首先我定義了一個(gè)基礎(chǔ)對(duì)象 BaseBook
和兩個(gè)繼承對(duì)象 SimpleBook
、 ComplexBook
妒牙,還新建一個(gè)抽象類彼哼,并且設(shè)定了一個(gè)方法 getDomain
,返回類型是基礎(chǔ)對(duì)象湘今。
我覺得是因?yàn)槌橄箢悷o(wú)法被實(shí)例化敢朱,必須要有具體實(shí)現(xiàn)類,所以在這個(gè)時(shí)候摩瞎,Spring
容器要加載 AbstractGetBookTest
對(duì)象拴签,可以用到 <lookup method>
屬性,通過注入特定實(shí)現(xiàn)類旗们,來(lái)完成類的加載蚓哩。
config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="getBookTest" class="base.label.parsing.AbstractGetBookTest">
<!-- 注釋 2.6 loop-up 屬性?? -->
<!-- 獲取器注入 name 表示方法,bean 表示要注入的類-->
<lookup-method name="getDomain" bean="complexBook"/>
</bean>
<bean id="book" class="domain.SimpleBook">
<!-- 元標(biāo)簽 -->
<meta key="test_key" value="test_value"/>
</bean>
<bean id="complexBook" class="domain.ComplexBook"/>
</beans>
復(fù)制代碼
Spring
會(huì)對(duì) bean
指定的 class
做動(dòng)態(tài)代理上渴,識(shí)別中 name
屬性所指定的方法岸梨,返回 bean
屬性指定的 bean
實(shí)例對(duì)象。
既然叫做獲取器注入稠氮,我們可以將 bean="complexBook"
替換一下曹阔,換成 bean="simpleBook"
,這樣注入的類就變成了 SimpleBook
對(duì)象了括袒,這樣只需要修改配置文件就能更換類的注入~
然后代碼對(duì) <lookup-method>
解析跟元屬性的解析很相近次兆,所以閱讀起來(lái)也很容易噢
解析 replaced-method 屬性
這個(gè)方法的用途:可以在運(yùn)行時(shí)用新的方法替換現(xiàn)有的方法稿茉。不僅可以動(dòng)態(tài)地替換返回實(shí)體 bean
锹锰,還能動(dòng)態(tài)地更改原有方法的邏輯。
簡(jiǎn)單來(lái)說漓库,就是將某個(gè)類定義的方法恃慧,在運(yùn)行時(shí)替換成另一個(gè)方法,例如明明看到代碼中調(diào)用的是 A
方法渺蒿,但實(shí)際運(yùn)行的卻是 B
方法痢士。
從圖片中看出,輸出框打印出我替換后的文案,實(shí)現(xiàn)起來(lái)也不難怠蹂,替換者需要實(shí)現(xiàn) org.springframework.beans.factory.support.MethodReplacer
接口善延,然后重寫 reimplement
方法,關(guān)鍵點(diǎn)在配置文件的 <replaced-method>
屬性:
<bean id="beforeMethodReplaced" class="base.label.parsing.BeforeMethodReplaced">
<!-- 注釋 2.7 方法替換 -->
<replaced-method name="printDefaultName" replacer="testMethodReplaced"/>
</bean>
復(fù)制代碼
同樣的城侧,Spring
會(huì)識(shí)別這個(gè) replaced-method
元素中的 name
屬性所指定的方法易遣,替換成指定 bean
實(shí)例對(duì)象的 reimplement
方法。
代碼解析過程中嫌佑,將識(shí)別到的屬性保存到 MethodOverrides
的 Set<MethodOverride> overrides
中豆茫,最終將會(huì)記錄在 AbstractBeanDefinition
的 methodOverrides
中。
個(gè)人并不推薦這種使用方法屋摇,如果常規(guī)工作中揩魂,業(yè)務(wù)驅(qū)動(dòng)比較強(qiáng)烈的情況,如果這樣寫炮温,會(huì)導(dǎo)致別人誤解這個(gè)方法的意圖火脉,如果想要調(diào)用查詢方法,卻被動(dòng)態(tài)代理柒啤,調(diào)用了刪除方法忘分,那就導(dǎo)致不必要的 BUG
(還好我沒遇到哈哈哈)。
解析 constructor-arg 屬性
解析構(gòu)造函數(shù)這個(gè)屬性是很常用的白修,但同時(shí)它的解析也很復(fù)雜妒峦,下面貼一個(gè)實(shí)例配置:
<bean id="testConstructorArg" class="base.label.parsing.TestConstructorArg">
<!-- 這里展示一個(gè)構(gòu)造函數(shù)的情況下,如果有兩個(gè)以上兵睛,解析會(huì)更復(fù)雜 -->
<constructor-arg index="0" value="JingQ"/>
<constructor-arg index="1" value="23"/>
</bean>
復(fù)制代碼
這個(gè)配置所實(shí)現(xiàn)的功能很簡(jiǎn)單肯骇,為 TestConstructorArg
自動(dòng)尋找對(duì)應(yīng)的構(gòu)造函數(shù),然后根據(jù)下標(biāo) index
為對(duì)應(yīng)的屬性注入 value
祖很,實(shí)現(xiàn)構(gòu)造函數(shù)笛丙。
具體解析在這個(gè)方法中:
/**
* 注釋 2.8 解析 構(gòu)造函數(shù) 子元素
* Parse constructor-arg sub-elements of the given bean element.
*/
public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
// 循環(huán)解析 constructor-arg 屬性
parseConstructorArgElement((Element) node, bd);
}
}
}
復(fù)制代碼
代碼太多也不貼出來(lái)啦,感興趣的同學(xué)定位到我寫注釋的地方詳細(xì)看下吧~
下面來(lái)梳理下解析構(gòu)造函數(shù)代碼的流程:
① 配置中指定了 index
屬性
- 解析
constructor-arg
的子元素 - 使用
ConstructorArgumentValues.ValueHolder(value)
類型來(lái)封裝解析出來(lái)的元素(包含type
name
index
屬性) -
addIndexedArgumentValue
方法假颇,將解析后的value
添加到當(dāng)前BeanDefinition
的ConstructorArgumentValues
的indexedArgumentValues
屬性中
① 配置中沒有指定了 index
屬性
- 解析
constructor-arg
的子元素 - 使用
ConstructorArgumentValues.ValueHolder(value)
類型來(lái)封裝解析出來(lái)的元素(包含type
name
index
屬性) -
addGenericArgumentValue
方法胚鸯,將解析后的value
添加到當(dāng)前BeanDefinition
的ConstructorArgumentValues
的genericArgumentValues
屬性中
這兩個(gè)流程區(qū)別點(diǎn)在于,最后解析到的屬性信息保存的位置不同笨鸡,指定下標(biāo)情況下姜钳,保存到 indexedArgumentValues
屬性,沒有指定下標(biāo)情況下形耗,將會(huì)保存到 genericArgumentValues
哥桥。
可以看到,這兩段代碼處理上激涤,第一步和第二部其實(shí)是一樣的邏輯拟糕,存在重復(fù)代碼的情況,我剛學(xué)習(xí)和工作時(shí),為了求快送滞,也有很多這種重復(fù)類型的代碼侠草。
在慢慢學(xué)習(xí)更多知識(shí)和設(shè)計(jì)模式后,回頭看之前寫的代碼犁嗅,都有種刪掉重寫的沖動(dòng)梦抢,所以如果如果在一開始寫的時(shí)候,就抽出相同處理代碼的邏輯愧哟,然后進(jìn)行代碼復(fù)用奥吩,減少代碼重復(fù)率,讓代碼更好看一些蕊梧,這樣就以后就不用被別人和自己吐槽了Σ(o?д?o?)
ref
value
屬性的處理比較簡(jiǎn)單霞赫,所以大家看代碼就能了解它是如何解析的,比較難的是子元素處理肥矢,例如下面的例子:
<constructor-arg>
<map>
<entry key="key" value="value" />
</map>
</constructor-arg>
復(fù)制代碼
具體解析子元素的方法是:org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertySubElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition, java.lang.String)
這個(gè)方法主要對(duì)各種子元素進(jìn)行解析端衰,包括 idref
value
array
set
map
等等子元素的機(jī)械,這里不細(xì)說甘改,同學(xué)們感興趣繼續(xù)去跟蹤吧~
解析 property 屬性
在配件文件中的使用方式:
<!-- property 解析 -->
<bean id="testPropertyParseElement" class="base.label.parsing.TestPropertyParseElement">
<property name="id" value="1"/>
<property name="name" value="JingQ"/>
</bean>
復(fù)制代碼
這個(gè)解析入口方法跟解析構(gòu)造函數(shù) constructor-arg
的入口方法很像旅东,代碼如下:
/**
* 注釋 2.10 解析 property 屬性
* Parse property sub-elements of the given bean element.
*/
public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
NodeList nl = beanEle.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
// 循環(huán)解析 property 屬性
parsePropertyElement((Element) node, bd);
}
}
}
復(fù)制代碼
這個(gè)入口方法提取到 property
所有子元素,然后調(diào)用 parsePropertyElement
方法進(jìn)行處理十艾,最后使用 PropertyValue
進(jìn)行封裝抵代,最后記錄在 BeanDefinition
中的 propertyValues
屬性中。
經(jīng)歷過上面復(fù)雜屬性的解析忘嫉,property
屬性的解析就顯得比較簡(jiǎn)單荤牍,都是一樣的套路,循環(huán)遍歷元素進(jìn)行解析庆冕,所以熟悉前面的解析邏輯后康吵,看后面的代碼就能更快理解~
解析 qualifer 屬性
大家更熟悉的應(yīng)該是 @qualifer
標(biāo)簽吧,它跟 qualifer
屬性的用途一樣访递。
在使用 Spring
框架進(jìn)行類注入的時(shí)候晦嵌,匹配的候選 bean
數(shù)目必須有且只有一個(gè),如果找不到一個(gè)匹配的 bean
時(shí)拷姿,容器就會(huì)拋出 BeanCreationException
異常惭载。
例如我們定義了一個(gè)抽象類 AbstractBook
,有兩個(gè)具體實(shí)現(xiàn)類 Book1
和 Book2
跌前,如果使用代碼:
@Autowired
private AbstractBook book;
復(fù)制代碼
這樣運(yùn)行時(shí)就會(huì)拋出剛才說的錯(cuò)誤異常棕兼,我們有兩種方式來(lái)消除歧義:
① 在配置文件中設(shè)定 quailfer
通過 qualifier
指定注入 bean
的名稱
<bean id="testBean" class="base.TestBean">
<qualifer type="org.Springframeword.beans.factory.annotation.Quailfier" value="book1"/>
</bean>
復(fù)制代碼
② 使用 @Qualifier("beanNeame")
@Qualifier("book1")
private AbstractBook book;
同樣的陡舅,代碼的解析過程跟前面的套路相近抵乓,留給同學(xué)們自己去分析吧~
總結(jié)
我們來(lái)回顧一下通用解析流程:
-
判斷元素類型:在每個(gè)入口方法中,都有個(gè)判斷方法
nodeNameEquals(node, XXXX_METHOD_ELEMENT)
,符合類型的才進(jìn)行解析 -
解析:解析一個(gè)子元素時(shí)灾炭,大多數(shù)情況下看到是
key-value
形式的屬性對(duì)茎芋,通過ele.getAttribute(NAME_ATTRIBUTE)
等形式進(jìn)行獲取 -
存儲(chǔ):將上一步解析的結(jié)果存儲(chǔ)
beanDefinition
對(duì)應(yīng)屬性中
這樣一看,是不是感覺清晰一點(diǎn)了蜈出,對(duì)于源碼的分析也沒這么害怕了田弥。
這次終于補(bǔ)了前一篇筆記的小坑,介紹了默認(rèn)標(biāo)簽的解析流程铡原,下一篇筆記介紹一下自定義標(biāo)簽的解析吧偷厦,下一篇再會(huì)~