Spring 源碼學(xué)習(xí)(二)默認(rèn)標(biāo)簽解析

spring 系列 轉(zhuǎn)載自掘金 VipAugus https://juejin.cn/user/2348212565601415/posts

Spring 解析默認(rèn)標(biāo)簽~

[toc]


從上一篇筆記可以看出问词,在容器注冊(cè) bean 信息的時(shí)候吃引,做了很多解析操作,而 xml 文件中包含了很多標(biāo)簽粗悯、屬性,例如 beanimport 標(biāo)簽, meta 存捺、look-upreplace等子元素屬性畔派。

上一篇主要介紹 Spring 容器的基礎(chǔ)結(jié)構(gòu)铅碍,沒有細(xì)說這些標(biāo)簽是如何解析的。

所以本篇是來(lái)進(jìn)行補(bǔ)坑的线椰,介紹這些標(biāo)簽在代碼中是如何識(shí)別和解析的~

本篇筆記的結(jié)構(gòu)大致如下:

  • 介紹概念
  • 展示 demo 代碼胞谈,如何使用
  • 結(jié)合源碼分析
  • 聊聊天和思考

再次說下,下載項(xiàng)目看完整注釋憨愉,跟著源碼一起分析~

碼云 Gitee 地址

Github 地址


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享扔、 aliasbean植袍、 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迎捺、 idname查排、 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ì)屬性 idname 的解析,不再贅述吼鳞,下面講下對(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è)完整的屬性解析過程看蚜,包含了 metalookup-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ì)象 SimpleBookComplexBook妒牙,還新建一個(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í)別到的屬性保存到 MethodOverridesSet<MethodOverride> overrides 中豆茫,最終將會(huì)記錄在 AbstractBeanDefinitionmethodOverrides中。

個(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)前 BeanDefinitionConstructorArgumentValuesindexedArgumentValues 屬性中

① 配置中沒有指定了 index 屬性

  • 解析 constructor-arg 的子元素
  • 使用 ConstructorArgumentValues.ValueHolder(value) 類型來(lái)封裝解析出來(lái)的元素(包含type name index 屬性)
  • addGenericArgumentValue 方法胚鸯,將解析后的 value 添加到當(dāng)前 BeanDefinitionConstructorArgumentValuesgenericArgumentValues 屬性中

這兩個(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)類 Book1Book2跌前,如果使用代碼:

@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ì)~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市燕刻,隨后出現(xiàn)的幾起案子只泼,更是在濱河造成了極大的恐慌,老刑警劉巖卵洗,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件请唱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡过蹂,警方通過查閱死者的電腦和手機(jī)十绑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)酷勺,“玉大人本橙,你說我怎么就攤上這事〈嗨撸” “怎么了勋功?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)库说。 經(jīng)常有香客問我狂鞋,道長(zhǎng),這世上最難降的妖魔是什么潜的? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任骚揍,我火速辦了婚禮,結(jié)果婚禮上啰挪,老公的妹妹穿的比我還像新娘信不。我一直安慰自己,他們只是感情好亡呵,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布抽活。 她就那樣靜靜地躺著,像睡著了一般锰什。 火紅的嫁衣襯著肌膚如雪下硕。 梳的紋絲不亂的頭發(fā)上丁逝,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音梭姓,去河邊找鬼霜幼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛誉尖,可吹牛的內(nèi)容都是我干的罪既。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼铡恕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼琢感!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起探熔,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤猩谊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后祭刚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牌捷,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年涡驮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了暗甥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捉捅,死狀恐怖撤防,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情棒口,我是刑警寧澤寄月,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站无牵,受9級(jí)特大地震影響漾肮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜茎毁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一克懊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧七蜘,春花似錦谭溉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至碧库,卻和暖如春柜与,著一層夾襖步出監(jiān)牢的瞬間巧勤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工旅挤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留踢关,地道東北人伞鲫。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓粘茄,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親秕脓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子柒瓣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容