天天看點

el-input使用value标簽值不可以修改_Spring 源碼學習(二)-預設标簽解析

Bean 标簽解析入口建立 GenericBeanDefinition解析 meta 屬性解析 lookup-method 屬性解析 constructor-arg 屬性解析 qualifer 屬性參考資料

從上一篇筆記可以看出,在容器注冊 bean 資訊的時候,做了很多解析操作,而 xml 檔案中包含了很多标簽、屬性,例如 bean 、 import 标簽, meta 、look-up 和 replace等子元素屬性。

上一篇主要介紹 Spring 容器的基礎結構,沒有細說這些标簽是如何解析的。

是以本篇是來進行補坑的,介紹這些标簽在代碼中是如何識别和解析的~

本篇筆記的結構大緻如下:

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

再次說下,下載下傳項目看完整注釋,跟着源碼一起分析~

碼雲 Gitee 位址

Github 位址

在 Spring 中,标簽有兩種,預設和自定義:

  • 預設标簽 這是我們最常使用到的标簽類型了,像我們一開始寫的 ,它屬于預設标簽,除了這個标簽外,還有其它四種标簽(import、 alias、 bean、 beans)
  • 自定義标簽 自定義标簽的用途,是為了給系統提供可配置化支援,例如事務标簽 ,它是 Spring 的自定義标簽,通過繼承 NamespaceHandler 來完成自定義命名空間的解析。

先看源碼是如何區分這兩者:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {    if (delegate.isDefaultNamespace(root)) {        // 注釋 1.12 周遊 doc 中的節點清單        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 識别出預設标簽的 bean 注冊                    // 根據元素名稱,調用不同的加載方法,注冊 bean                    parseDefaultElement(ele, delegate);                }                else {                    delegate.parseCustomElement(ele);                }            }        }    }    else {        delegate.parseCustomElement(root);    }}
           

可以看到,在代碼中,關鍵方法是 delegate.isDefaultNamespace(ele) 進行判斷,識别掃描到的元素屬于哪種标簽。

找到命名空間 NamespaceURI 變量,如果是 http://www.springframework.org/schema/beans,表示它是預設标簽,然後進行預設标簽的元素解析,否者使用自定義标簽解析。

本篇筆記主要記錄的是預設标簽的解析,下來開始正式介紹~

Bean 标簽解析入口

定位到上面第三個方法 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 注冊最後修飾後的執行個體)                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());            }            catch (BeanDefinitionStoreException ex) {                getReaderContext().error("Failed to register bean definition with name '" +                        bdHolder.getBeanName() + "'", ele, ex);            }            // Send registration event. 通知相關的監聽器,表示這個 bean 已經加載完成            getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));        }    }
           

上一篇筆記隻是簡單描述這個方法的功能:将 xml 中配置的屬性對應到 document 對象中,然後進行注冊,下面來完整描述這個方法的處理流程:

  • 建立執行個體 bdHolder:首先委托 BeanDefinitionParserDelegate 類的 parseBeanDefinitionElement 方法進行元素解析,經過解析後,bdHolder 執行個體已經包含剛才我們在配置檔案中設定的各種屬性,例如 class、 id、 name、 alias等屬性。
  • 對執行個體 bdHolder 進行裝飾:在這個步驟中,其實是掃描預設标簽下的自定義标簽,對這些自定義标簽進行元素解析,設定自定義屬性。
  • 注冊 bdHolder 資訊:解析完成了,需要往容器的 beanDefinitionMap 系統資料庫注冊 bean 資訊,注冊操作委托給了 BeanDefinitionReaderUtils.registerBeanDefinition,通過工具類完成資訊注冊。
  • 發送通知事件:通知相關監聽器,表示這個 bean 已經加載完成

看到這裡,同學們應該能看出,Spring 源碼的接口和方法設計都很簡潔,上層接口描述了該方法要做的事情,然後分解成多個小方法,在小方法中進行邏輯處理,方法可以被複用。

是以看源碼除了能了解到架構的實作邏輯,更好的去使用和定位問題,還能夠學習到大佬們寫代碼時的設計模式,融入自己的工作或者學習中~

建立 GenericBeanDefinition

關于 GenericBeanDefinition 的繼承體系上一篇已經講過了,是以這裡再簡單解釋一下這個方法的用途:

createBeanDefinition(className, parent);

從方法名字就能看出,它的用途是建立一個 beanDefinition ,用于承載屬性的執行個體。

在最後一步執行個體化 GenericBeanDefinition 時,還會判斷類加載器是非存在。如果存在的話,使用類加載器所在的 jvm 來加載類對象,否則隻是簡單記錄一下 className。

解析 meta 屬性

先講下 meta 屬性的使用(汗,在沒了解前,基本沒使用該屬性=-=)

這個元屬性不會展現在對象的屬性中,而是一個額外的聲明,在 parseMetaElements(ele, bd); 方法中進行擷取,具體實作是 element 對象的 getAttribute(key),将設定的元屬性放入 BeanMetadataAttributeAccessor 對象中

el-input使用value标簽值不可以修改_Spring 源碼學習(二)-預設标簽解析

因為代碼比較簡單,是以通過圖檔進行說明:

最終屬性值是以 key-value 形式儲存在連結清單中 Map attributes,之後使用隻需要根據 key 值就能擷取到 value 。想到之後在代碼設計上,為了擴充性,也可以進行 key-value 形式存儲和使用。

解析 lookup-method 屬性

這個屬性也是不常用,引用書中的描述

通常将它成為擷取器注入。擷取器注入是一個特殊的方法注入,它是把一個方法聲明為傳回某種類型的 bean,但實際要傳回的 bean 是在配置檔案裡面配置的,次方法可用在設計有些可插拔的功能上,解除程式依賴。

代碼寫的有點多,我貼張圖檔,介紹一下關鍵資訊:

el-input使用value标簽值不可以修改_Spring 源碼學習(二)-預設标簽解析

首先我定義了一個基礎對象 BaseBook 和兩個繼承對象 SimpleBook、 ComplexBook,還建立一個抽象類,并且設定了一個方法 getDomain,傳回類型是基礎對象。

我覺得是因為抽象類無法被執行個體化,必須要有具體實作類,是以在這個時候,Spring 容器要加載 AbstractGetBookTest 對象,可以用到 屬性,通過注入特定實作類,來完成類的加載。

config.xml
<?xml version="1.0" encoding="UTF-8"?>
           

Spring 會對 bean 指定的 class做動态代理,識别中 name 屬性所指定的方法,傳回 bean 屬性指定的 bean 執行個體對象。

既然叫做擷取器注入,我們可以将 bean="complexBook" 替換一下,換成 bean="simpleBook",這樣注入的類就變成了 SimpleBook 對象了,這樣隻需要修改配置檔案就能更換類的注入~

然後代碼對 解析跟元屬性的解析很相近,是以閱讀起來也很容易噢

解析 constructor-arg 屬性

解析構造函數這個屬性是很常用的,但同時它的解析也很複雜,下面貼一個執行個體配置:

這個配置所實作的功能很簡單,為 TestConstructorArg 自動尋找對應的構造函數,然後根據下标 index 為對應的屬性注入 value,實作構造函數。

具體解析在這個方法中:

/** * 注釋 2.8 解析 構造函數 子元素 * 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)) {            // 循環解析 constructor-arg 屬性            parseConstructorArgElement((Element) node, bd);        }    }}
           

代碼太多也不貼出來啦,感興趣的同學定位到我寫注釋的地方詳細看下吧~

下面來梳理下解析構造函數代碼的流程:

① 配置中指定了 index 屬性

  • 解析 constructor-arg 的子元素
  • 使用 ConstructorArgumentValues.ValueHolder(value) 類型來封裝解析出來的元素(包含type name index 屬性)
  • addIndexedArgumentValue 方法,将解析後的 value 添加到目前 BeanDefinition 的 ConstructorArgumentValues 的 indexedArgumentValues 屬性中

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

  • 解析 constructor-arg 的子元素
  • 使用 ConstructorArgumentValues.ValueHolder(value) 類型來封裝解析出來的元素(包含type name index 屬性)
  • addGenericArgumentValue 方法,将解析後的 value 添加到目前 BeanDefinition 的 ConstructorArgumentValues 的 genericArgumentValues 屬性中

這兩個流程差別點在于,最後解析到的屬性資訊儲存的位置不同,指定下标情況下,儲存到 indexedArgumentValues 屬性,沒有指定下标情況下,将會儲存到 genericArgumentValues。

可以看到,這兩段代碼處理上,第一步和第二部其實是一樣的邏輯,存在重複代碼的情況,我剛學習和工作時,為了求快,也有很多這種重複類型的代碼。

在慢慢學習更多知識和設計模式後,回頭看之前寫的代碼,都有種删掉重寫的沖動,是以如果如果在一開始寫的時候,就抽出相同處理代碼的邏輯,然後進行代碼複用,減少代碼重複率,讓代碼更好看一些,這樣就以後就不用被别人和自己吐槽了Σ(o゚д゚oノ)

ref value 屬性的處理比較簡單,是以大家看代碼就能了解它是如何解析的,比較難的是子元素處理,例如下面的例子:

具體解析子元素的方法是:org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertySubElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition, java.lang.String)

這個方法主要對各種子元素進行解析,包括 idref value array set map 等等子元素的機械,這裡不細說,同學們感興趣繼續去跟蹤吧~

解析 qualifer 屬性

大家更熟悉的應該是 @qualifer 标簽吧,它跟 qualifer 屬性的用途一樣。

在使用 Spring 架構進行類注入的時候,比對的候選 bean 數目必須有且隻有一個,如果找不到一個比對的 bean 時,容器就會抛出 BeanCreationException 異常。

例如我們定義了一個抽象類 AbstractBook,有兩個具體實作類 Book1 和 Book2,如果使用代碼:

@Autowiredprivate AbstractBook book;
           

這樣運作時就會抛出剛才說的錯誤異常,我們有兩種方式來消除歧義:

① 在配置檔案中設定 quailfer

通過 qualifier 指定注入 bean 的名稱

② 使用 @Qualifier("beanNeame")

@Qualifier("book1")private AbstractBook book;
           

同樣的,代碼的解析過程跟前面的套路相近,留給同學們自己去分析吧~