背景
在 Dubbo 中,可以使用 XML 配置相關資訊,也可以用來引入服務或者匯出服務。配置完成,啟動工程,Spring 會讀取配置檔案,生成注入 相關 Bean。那 Dubbo 如何實現自定義 XML 被 Spring 載入讀取?
Spring XML Schema 擴充套件機制。從 Spring 2.0 開始,Spring 開始提供了一種基於 XML Schema 格式擴充套件機制,用於定義和配置 bean。
Spring XML Schema 擴充套件機制
實現 Spring XML Schema 擴充套件,其實非常簡單,只需要完成下面四步。
- 建立 XML Schema 檔案,由於該檔案字尾名為 xsd,下面稱為 XSD 檔案。
- 編寫實現一個或多個
BeanDefinitionParser
。 - 編寫
NamespaceHandler
實現類。 - 註冊
NamespaceHandler
以及 XSD 檔案。
我們按照以上步驟,最終完整 Spring 解析如下配置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:demo="http://www.test.com/demo"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.test.com/demo http://www.test.com/demo/demo.xsd">
<demo:application name="test" id="test"/>
</beans>
建立 XSD 檔案
XSD 檔案,主要用來定義 XML 格式,用來驗證 XML 合法性。在 IDE 中,匯入 XSD 檔案,編輯 XML 檔案可以獲得相關提示。
下面我們生成一個 XSD 檔案。
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.test.com/demo"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:beans="http://www.springframework.org/schema/beans"
targetNamespace="http://www.test.com/demo"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:element name="application">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="beans:identifiedType">
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
</xsd:schema>
上面 XSD 檔案中 http://www.test.com/demo 為自定義名稱空間地址,下面將會使用到。
實現 BeanDefinitionParser
這裡實現 BeanDefinitionParser,真正解析 XML 動作在這裡完成。
由於上面的例子比較簡單,我們可以直接繼承 Spring 提供的抽象類 AbstractSingleBeanDefinitionParser
,然後實現相關方法就可以了。
public class DemoBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
/**
* 返回最會需要注入 Spring Bean 的型別
* @param element
* @return
*/
@Override
protected Class<?> getBeanClass(Element element) {
return DemoApplication.class;
}
/***
* 這個方法完成真正解析動作
* @param element
* @param builder
*/
@Override
protected void doParse(Element element, BeanDefinitionBuilder builder) {
String name=element.getAttribute("name");
builder.addPropertyValue("name",name);
}
}
當然也可以直接實現 BeanDefinitionParser,這樣更加靈活,但是這樣相比於上面這個就比較複雜了。
public class BeanApplicationDefinitionParser implements BeanDefinitionParser {
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
String name=element.getAttribute("name");
// Bean 定義,最後根據這個生產 Bean
RootBeanDefinition rootBeanDefinition=new RootBeanDefinition();
rootBeanDefinition.setBeanClass(DemoApplication.class);
rootBeanDefinition.setLazyInit(false);
// 新增解析的屬性
rootBeanDefinition.getPropertyValues().add("name",name);
// 將生成的 BeanDefinition 註冊,少了這一步將會導致最後生成 Bean 時報錯
parserContext.getRegistry().registerBeanDefinition("application",rootBeanDefinition);
return rootBeanDefinition;
}
}
實現 NamespaceHandler
這一步實現 NamespaceHandler,開發者自定義 NamespaceHandler 只要繼承 NamespaceHandlerSupport
抽象類,實現 init
方法。在這個方法中註冊上面一步實現 BeanDefinitionParser
。
public class DemoNameSpaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// elementName 為名稱空間
registerBeanDefinitionParser("application",new BeanApplicationDefinitionParser());
}
}
註冊 XSD 以及 NamespaceHandler
這一步我們需要在 META-INF 中生成兩個配置檔案,分別為 spring.handlers
,spring.schemas
。
spring.schemas
指定 XSD 檔案路徑。
http\://www.test.com/demo/demo.xsd=com/spring/learning/xml/schemas/autoring/leanrn/demo.xsd
spring.handlers
指定 NamespaceHandler
完整類名,既包含前面的包名。
這裡需要注意的是
:
需要進行轉義
測試執行
首先我們生產 Spring 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"
xmlns:demo="http://www.test.com/demo"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.test.com/demo http://www.test.com/demo/demo.xsd">
<demo:application name="test" id="test"/>
</beans>
這裡需要注意需要使用 XSD 檔案中定義 http://www.test.com/demo
。
接著我們使用 SpringBoot ,匯入 XML 檔案,然後執行。
@SpringBootApplication
@ImportResource(locations = {"classpath:applicationContext.xml"})
public class XmlSchemaApplication {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(XmlSchemaApplication.class, args);
DemoApplication demoApplication=applicationContext.getBean(DemoApplication.class);
System.out.println("application name is "+demoApplication.getName());
}
}
輸出結果為:
application name is test
Spring XML 擴充套件機制原始碼研究
這裡我們主要研究自定義 XML 擴充套件檔案如何被 Spring 載入。
Spring 啟動過程中會通過 BeanDefinitionDocumentReader
讀取 beans 標籤裡面所有配置,這個過程將會通過 BeanDefinitionParserDelegate#parseCustomElement
解析自定義元素。
上面解析過程可以獲得自定義 NamespaceHandler
,然後呼叫 parse
方法解析。
接著我們檢視 NamespaceHandlerResolver#resolve
方法,檢視如何獲取自定義 NamespaceHandler
。
在這個方法中,主要是從 handlerMappings
快取中獲取 NamespaceHandler
。而該快取來源於 getHandlerMappings
方法,這個方法將會載入我們上面自定義 spring.handlers
檔案。
看完 Spring 載入 NamespaceHandler
過程,下面我們檢視最重要 BeanDefinition
如何生成。
上面已經講到 Spring 會使用 NamespaceHandler.parse
解析,由於我們繼承了 NamespaceHandlerSupport
,檢視裡面具體實現。
獲取到 BeanDefinition 會將其註冊到容器中,然後會通過 BeanDefinition
生成 Bean。這個生成過程不屬於本章節內容,所以不再概述,感興趣同學可以自行搜尋。
Dubbo XML Schema 擴充套件實現
最後我們檢視 Dubbo XML Schema 擴充套件如何實現。
可以看到 Dubbo XML Schema 擴充套件剛好對應 Spring 四個標準的步驟。
總結
最後用一張圖片總結全文內容。