前言
很久沒有寫關于 spring 的文章了,最近在系統梳理 dubbo 代碼的過程中發現了 xml schema 這個被遺漏的知識點。由于工作中使用 springboot 比較多的原因,幾乎很少接觸 xml,此文可以算做是亡羊補牢,另一方面,也為后續的 dubbo 源碼解析做個鋪墊。
xml schema 擴展機制是啥?從spring2.0開始,spring提供了xml schema可擴展機制,用戶可以自定義xml schema文件,并自定義xml bean解析器,并集成到spring ioc 容器中。這并不是一塊很大的知識點,翻閱一下 spring 的文檔,我甚至沒找到一個貫穿上下文的詞來描述這個功能,xml schema authoring 是文檔中對應的標題,簡單來說:
spring 為基于 xml 構建的應用提供了一種擴展機制,用于定義和配置 bean。 它允許使用者編寫自定義的 xml bean 解析器,并將解析器本身以及最終定義的 bean 集成到 spring ioc 容器中。
dubbo 依賴了 spring,并提供了一套自定義的 xml 標簽,<dubbo:application> ,<dubbo:registry> ,<dubbo:protocol>,<dubbo:service>。作為使用者,大多數人只需要關心這些參數如何配置,但不知道有沒有人好奇過,它們是如何加載進入 spring 的 ioc 容器中被其他組件使用的呢?這便牽扯出了今天的主題:spring 對 xml schema 的擴展支持。
自定義 xml 擴展
為了搞懂 spring 的 xml 擴展機制,最直接的方式便是實現一個自定義的擴展。實現的步驟也非常簡單,分為四步:
- 編寫一個 xml schema 文件描述的你節點元素。
- 編寫一個 namespacehandler 的實現類
- 編寫一個或者多個 beandefinitionparser 的實現 (關鍵步驟).
- 注冊上述的 schema 和 handler。
我們的目的便是想要實現一個 kirito xml schema,我們的項目中可以自定義 kirito.xml,在其中會以 kirito 為標簽來定義不同的類,并在最終的測試代碼中驗證這些聲明在 kirito.xml 的類是否被 spring 成功加載。大概像這樣,是不是和 dubbo.xml 的格式很像呢?
動手實現
有了明確的目標,我們逐步開展自己的工作。
1 編寫kirito.xsd
resources/meta-inf/kirito.xsd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
<?xml version= "1.0" encoding= "utf-8" ?> <xsd:schema xmlns= "http://www.cnkirito.moe/schema/kirito" xmlns:xsd= "http://www.w3.org/2001/xmlschema" xmlns:beans= "http://www.springframework.org/schema/beans" targetnamespace= "http://www.cnkirito.moe/schema/kirito" > ① <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:element name= "service" > ② <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> |
① 注意這里的 targetnamespace="http://www.cnkirito.moe/schema/kirito"
這便是之后 kirito 標簽的關鍵點。
② kirito.xsd 定義了兩個元素: application 和 service,出于簡單考慮,都只有一個 name 字段。
schema 的意義在于它可以和 eclipse/idea 這樣智能化的集成開發環境形成很好的搭配,在編輯 xml 的過程中,用戶可以獲得告警和提示。 如果配置得當,可以使用自動完成功能讓用戶在事先定義好的枚舉類型中進行選擇。
2 編寫kiritonamespacehandler
1
2
3
4
5
6
7
8
|
public class kiritonamespacehandler extends namespacehandlersupport { @override public void init() { super .registerbeandefinitionparser( "application" , new kiritobeandefinitionparser(applicationconfig. class )); super .registerbeandefinitionparser( "service" , new kiritobeandefinitionparser(servicebean. class )); } } |
完成 schema 之后,還需要一個 namespacehandler 來幫助 spring 解析 xml 中不同命名空間的各類元素。
1
2
3
|
<kirito:application name= "kirito" /> <dubbo:application name= "dubbo" /> <motan:application name= "motan" /> |
不同的命名空間需要不同的 namespacehandler 來處理,在今天的示例中,我們使用 kiritonamespacehandler 來解析 kirito 命名空間。kiritonamespacehandler 繼承自 namespacehandlersupport 類,并在其 init() 方法中注冊了兩個 beandefinitionparser ,用于解析 kirito 命名空間/kirito.xsd 約束中定義的兩個元素:application,service。beandefinitionparser 是下一步的主角,我們暫且跳過,將重心放在父類 namespacehandlersupport 之上。
1
2
3
4
5
|
public interface namespacehandler { void init(); beandefinition parse(element element, parsercontext parsercontext); beandefinitionholder decorate(node source, beandefinitionholder definition, parsercontext parsercontext); } |
namespacehandlersupport 是 namespacehandler 命名空間處理器的抽象實現,我粗略看了namespacehandler 的幾個實現類,parse 和 decorate 方法可以完成元素節點的組裝并通過 parsercontext 注冊到 ioc 容器中,但實際我們并沒有調用這兩個方法,而是通過 init() 方法注冊 beandefinitionparser 來完成解析節點以及注冊 bean 的工作,所以對于 namespacehandler,我們主要關心 init 中注冊的兩個 beandefinitionparser 即可。
3 編寫kiritobeandefinitionparser
在文章開始我們便標記到 beandefinitionparser 是最為關鍵的一環,每一個 beandefinitionparser 實現類都負責一個映射,將一個 xml 節點解析成 ioc 容器中的一個實體類。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class kiritobeandefinitionparser implements beandefinitionparser { private final class <?> beanclass; public kiritobeandefinitionparser( class <?> beanclass) { this .beanclass = beanclass; } private static beandefinition parse(element element, parsercontext parsercontext, class <?> beanclass) { rootbeandefinition beandefinition = new rootbeandefinition(); beandefinition.setbeanclass(beanclass); beandefinition.setlazyinit( false ); string name = element.getattribute( "name" ); beandefinition.getpropertyvalues().addpropertyvalue( "name" , name); parsercontext.getregistry().registerbeandefinition(name, beandefinition); return beandefinition; } @override public beandefinition parse(element element, parsercontext parsercontext) { return parse(element, parsercontext, beanclass); } } |
由于我們的實體類是非常簡單的,所以不存在很復雜的解析代碼,而實際項目中,往往需要大量的解析步驟。parse 方法會解析一個個 xml 中的元素,使用 rootbeandefinition 組裝成對象,并最終通過 parsercontext 注冊到 ioc 容器中。
至此,我們便完成了 xml 文件中定義的對象到 ioc 容器的映射。
4 注冊schema和handler
最后一步還需要通知 spring,告知其自定義 schema 的所在之處以及對應的處理器。
resources/meta-inf/spring.handlers
http\://www.cnkirito.moe/schema/kirito=moe.cnkirito.sample.xsd.kiritonamespacehandler
resources/meta-inf/spring.schemas
http\://www.cnkirito.moe/schema/kirito/kirito.xsd=meta-inf/kirito.xsd
沒有太多可以說的,需要遵守 spring 的約定。
至此一個自定義的 xml schema 便擴展完成了,隨后來驗證一下。
驗證擴展
我們首先定義好 kirito.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<?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:kirito= "http://www.cnkirito.moe/schema/kirito" xsi:schemalocation=" http: //www.springframework.org/schema/beans http: //www.springframework.org/schema/beans/spring-beans.xsd http: //www.cnkirito.moe/schema/kirito http: //www.cnkirito.moe/schema/kirito/kirito.xsd"> <kirito:application name= "kirito-demo-application" /> <kirito:service name= "kirito-demo-service" /> </beans> |
使用 spring 去加載它,并驗證 ioc 容器中是否存在注冊成功的 bean。
1
2
3
4
5
6
7
8
9
10
11
12
|
@springbootapplication @importresource (locations = { "classpath:kirito.xml" }) public class xmlschemaauthoringsampleapplication { public static void main(string[] args) { configurableapplicationcontext applicationcontext = springapplication.run(xmlschemaauthoringsampleapplication. class , args); servicebean servicebean = applicationcontext.getbean(servicebean. class ); system.out.println(servicebean.getname()); applicationconfig applicationconfig = applicationcontext.getbean(applicationconfig. class ); system.out.println(applicationconfig.getname()); } } |
觀察控制臺的輸出:
kirito-demo-service
kirito-demo-application
一個基礎的基于 xml schema 的擴展便完成了。
dubbo中的xml schema擴展
最后我們以 dubbo 為例,看看一個成熟的 xml schema 擴展是如何被應用的。
剛好對應了四個標準的擴展步驟,是不是對 xml 配置下的 dubbo 應用有了更好的理解了呢?
順帶一提,僅僅完成 bean 的注冊還是不夠的,在“注冊”的同時,dubbo 還進行了一系列其他操作如:暴露端口,開啟服務器,完成注冊中心的注冊,生成代理對象等等行為,由于不在本文的范圍內,后續的 dubbo 專題會專門介紹這些細節,本文便是了解 dubbo 加載流程的前置文章了。
總結:
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://www.cnkirito.moe/spring-xsd/