【死磕 Spring】----- IOC 之解析自定義標籤
在部落格 【死磕Spring】----- IOC/">IOC 之 註冊 BeanDefinition 中提到:獲取 Document 物件後,會根據該物件和 Resource 資源物件呼叫 registerBeanDefinitions()
方法,開始註冊 BeanDefinitions 之旅。在註冊 BeanDefinitions 過程中會呼叫 parseBeanDefinitions()
開啟 BeanDefinition 的解析過程。在該方法中,它會根據名稱空間的不同調用不同的方法進行解析,如果是預設的名稱空間,則呼叫 parseDefaultElement()
進行預設標籤解析,否則呼叫 parseCustomElement()
方法進行自定義標籤解析。前面 6 篇部落格都是分析預設標籤的解析工作,這篇部落格分析自定義標籤的解析過
預設標籤的解析部落格如下:
-
ofollow,noindex"> 【死磕 Spring】—– IOC 之解析Bean:解析 import 標籤
-
【死磕 Spring】—– IOC 之解析 bean 標籤:meta、lookup-method、replace-method
在分析自定義標籤的解析之前,我們有必要了解自定義標籤的使用。
使用自定義標籤
擴充套件 Spring 自定義標籤配置一般需要以下幾個步驟:
-
建立一個需要擴充套件的元件
-
定義一個 XSD 檔案,用於描述元件內容
-
建立一個實現 AbstractSingleBeanDefinitionParser 介面的類,用來解析 XSD 檔案中的定義和元件定義
-
建立一個 Handler,繼承 NamespaceHandlerSupport ,用於將元件註冊到 Spring 容器
-
編寫 Spring.handlers 和 Spring.schemas 檔案
下面就按照上面的步驟來實現一個自定義標籤元件。
建立元件
該元件就是一個普通的 JavaBean,沒有任何特別之處。
定義 XSD 檔案
上面除了對 User 這個 JavaBean 進行了描述外,還定義了 xmlns="http://www.cmsblogs.com/schema/user"targetNamespace="http://www.cmsblogs.com/schema/user"
這兩個值,這兩個值在後面是有大作用的。
Parser 類
定義一個 Parser 類,該類繼承 AbstractSingleBeanDefinitionParser ,並實現 getBeanClass()
和 doParse()
兩個方法。主要是用於解析 XSD 檔案中的定義和元件定義。
Handler 類
定義 Handler 類,繼承 NamespaceHandlerSupport ,主要目的是將元件註冊到 Spring 容器中。
Spring.handlers
Spring.schemas
經過上面幾個步驟,就可以使用自定義的標籤了。在 xml 配置檔案中使用如下:
測試:
執行結果:

解析自定義標籤
上面已經演示了 Spring 自定義標籤的使用,下面就來分析自定義標籤的解析過程。
DefaultBeanDefinitionDocumentReader.parseBeanDefinitions()
負責標籤的解析工作,其中它根據名稱空間的不同進行不同標籤的解析,其中自定義標籤由 delegate.parseCustomElement()
實現。如下:
呼叫 parseCustomElement()
方法,如下:
處理過程分為三步:
-
獲取 namespaceUri
-
根據 namespaceUri 獲取相應的 Handler
-
呼叫自定義的 Handler 處理
這個處理過程很簡單明瞭,根據 namespaceUri 獲取 Handler,這個對映關係我們在 Spring.handlers 中已經定義了,所以只需要找到該類,然後初始化返回,最後呼叫該 Handler 物件的 parse()
方法處理,該方法我們也提供了實現。所以上面的核心就在於怎麼找到該 Handler 類。呼叫方法為: this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri)
getNamespaceHandlerResolver()
方法返回的名稱空間的解析器,該解析定義在 XmlReaderContext
中,如下:
這裡直接返回,那是在什麼時候初始化的呢?這裡需要回退到博文:【死磕Spring】----- IOC 之 註冊 BeanDefinition ,在這篇部落格中提到在註冊 BeanDefinition 時,首先是通過 createBeanDefinitionDocumentReader()
獲取 Document 解析器 BeanDefinitionDocumentReader 例項,然後呼叫該例項 registerBeanDefinitions()
方法進行註冊。 registerBeanDefinitions()
方法需要提供兩個引數,一個是 Document 例項 doc,一個是 XmlReaderContext 例項 readerContext,readerContext 例項物件由 createReaderContext()
方法提供。namespaceHandlerResolver 例項物件就是在這個時候初始化的。如下:
XmlReaderContext 建構函式中最後一個引數就是 NamespaceHandlerResolver 物件,該物件由 getNamespaceHandlerResolver()
提供,如下:
所以 getNamespaceHandlerResolver().resolve(namespaceUri)
呼叫的就是 DefaultNamespaceHandlerResolver 的 resolve()
。如下:
首先呼叫 getHandlerMappings()
獲取所有配置檔案中的對映關係 handlerMappings ,該關係為 <名稱空間,類路徑>,然後根據名稱空間 namespaceUri 從對映關係中獲取相應的資訊,如果為空或者已經初始化了就直接返回,否則根據反射對其進行初始化,同時呼叫其 init()
方法,最後將該 Handler 物件快取。
init()
方法主要是將自定義標籤解析器進行註冊,如我們自定義的 init()
:
直接呼叫父類的 registerBeanDefinitionParser()
方法進行註冊:
其實就是將對映關係放在一個 Map 結構的 parsers 物件中: privatefinalMap<String,BeanDefinitionParser>parsers
。
完成後返回 NamespaceHandler 物件,然後呼叫其 parse()
方法開始自定義標籤的解析,如下:
呼叫 findParserForElement()
方法獲取 BeanDefinitionParser 例項,其實就是獲取在 init()
方法裡面註冊的例項物件。如下:
獲取 localName,在上面的例子中就是 : user,然後從 Map 例項 parsers 中獲取 BeanDefinitionParser 物件。返回 BeanDefinitionParser 物件後,呼叫其 parse()
,該方法在 AbstractBeanDefinitionParser 中實現:
核心在方法 parseInternal()
為什麼這麼說,以為該方法返回的是 AbstractBeanDefinition 物件,從前面預設標籤的解析工作中我們就可以判斷該方法就是將標籤解析為 AbstractBeanDefinition ,且後續程式碼都是將 AbstractBeanDefinition 轉換為 BeanDefinitionHolder,所以真正的解析工作都交由 parseInternal()
實現,如下:
在該方法中我們主要關注兩個方法: getBeanClass()
、 doParse()
。對於 getBeanClass()
方法,AbstractSingleBeanDefinitionParser 類並沒有提供具體實現,而是直接返回 null,意味著它希望子類能夠重寫該方法,當然如果沒有重寫該方法,這會去呼叫 getBeanClassName()
,判斷子類是否已經重寫了該方法。對於 doParse()
則是直接空實現。所以對於 parseInternal()
而言它總是期待它的子類能夠實現 getBeanClass()
、 doParse()
,其中 doParse()
尤為重要,如果你不提供實現,怎麼來解析自定義標籤呢?最後將自定義的解析器:UserDefinitionParser 再次回觀。
至此,自定義標籤的解析過程已經分析完成了。其實整個過程還是較為簡單:首先會載入 handlers 檔案,將其中內容進行一個解析,形成這樣的一個對映,然後根據獲取的 namespaceUri 就可以得到相應的類路徑,對其進行初始化等到相應的 Handler 物件,呼叫 parse()
方法,在該方法中根據標籤的 localName 得到相應的 BeanDefinitionParser 例項物件,呼叫 parse()
,該方法定義在 AbstractBeanDefinitionParser 抽象類中,核心邏輯封裝在其 parseInternal()
中,該方法返回一個 AbstractBeanDefinition 例項物件,其主要是在 AbstractSingleBeanDefinitionParser 中實現,對於自定義的 Parser 類,其需要實現 getBeanClass()
或者 getBeanClassName()
和 doParse()
。

原文釋出時間為:2018-10-24
本文作者:Java技術驛站