前一篇 聊一聊 Spring 中的擴展機制(一) 中聊到了ApplicationListener
、ApplicationContextAware
、BeanFactoryAware
三種機制。本篇將介紹 NamespaceHandler
的擴展使用。php
相信不少小夥伴對於這幾個類都不陌生,基本基於java
實現的RPC
框架都會使用,好比 Dubbo , SOFARpc 等。本文先從幾個小demo
入手,瞭解下基本的概念和編程流程,而後分析下 SOFARpc
中是如何使用的。html
NamespaceHandler
是 Spring
提供的 命名空間處理器。下面這張圖中,除了亂入的本篇 demo
中涉及到的 BridgeNameSpaceHandler
以外,其餘均爲 Spring
自身提供的。 java
bean
和
context
依賴,因此這也僅僅是一部分。圖中咱們經常使用的應該算是
AopNamespaceHandler
。
咱們使用基於xml
的spring
配置時,可能須要配置如<aop:config />
這樣的標籤,在配置這個標籤以前,一般咱們須要引入這個aop
所在的命名空間:git
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" />
複製代碼
關於AOP 能夠了解下 聊一聊 AOP :表現形式與基礎概念,這裏不過多解釋,下面就按照 官方文檔的流程 來寫一個自定義xml
,最終效果以下:github
<bridge:application id="bridgeTestApplication" name="bridgeTestApplication" version="1.0" organization="bridge.glmapper.com" owner="leishu@glmapper"/>
複製代碼
關於 xsd
文件的語法規則不在本篇範圍以內,有興趣的同窗能夠自行google
。 下面這個文件很簡單,定義的element
name 爲application
,對應於 bridge:application
中的application
。attribute
就是上面效果展現中對應的幾個屬性名。spring
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:tool="http://www.springframework.org/schema/tool" xmlns="http://bridge.glmapper.com/schema/bridge" targetNamespace="http://bridge.glmapper.com/schema/bridge">
<xsd:import namespace="http://www.springframework.org/schema/beans"/>
<xsd:complexType name="applicationType">
<xsd:attribute name="id" type="xsd:ID"/>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="version" type="xsd:string"/>
<xsd:attribute name="owner" type="xsd:string"/>
<xsd:attribute name="organization" type="xsd:string"/>
</xsd:complexType>
<xsd:element name="application" type="applicationType"/>
</xsd:schema>
複製代碼
In addition to the schema, we need a NamespaceHandler that will parse all elements of this specific namespace Spring encounters while parsing configuration files.apache
用編寫的這個 NamespaceHandler
來解析配置文件。編程
具體說來NamespaceHandler
會根據schema
和節點名找到某個BeanDefinitionParser
,而後由BeanDefinitionParser
完成具體的解析工做。bash
Spring
提供了默認實現類NamespaceHandlerSupport
和AbstractSingleBeanDefinitionParser
,最簡單的方式就是去繼承這兩個類。app
這裏經過繼承 NamespaceHandlerSupport
這個抽象類來完成。
public class BridgeNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("application",
new ApplicationBeanDefinitionParser());
}
}
複製代碼
這裏實際上只是註冊了一個解析器,具體的 BeanDefinitionParser
纔是將 XML
元素映射到特定bean
的。
這裏直接經過實現BeanDefinitionParser
接口的方式定義咱們的BeanDefinitionParser
實現類。關於AbstractSingleBeanDefinitionParser
的使用在 SPFARpc
中會涉及到。
public class ApplicationBeanDefinitionParser implements BeanDefinitionParser {
public BeanDefinition parse(Element element, ParserContext parserContext) {
//beanDefinition
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(ApplicationConfig.class);
beanDefinition.setLazyInit(false);
//解析id
String id = element.getAttribute("id");
beanDefinition.getPropertyValues().add("id", id);
//解析name
beanDefinition.getPropertyValues().add("name",
element.getAttribute("name"));
//解析version
beanDefinition.getPropertyValues().add("version",
element.getAttribute("version"));
//owner
beanDefinition.getPropertyValues().add("owner",
element.getAttribute("owner"));
//organization
beanDefinition.getPropertyValues().add("organization",
element.getAttribute("organization"));
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
return beanDefinition;
}
}
複製代碼
這裏咱們須要瞭解的是開始解析自定義標籤的時候,是經過BeanDefinitionParserDelegate->parseCustomElement
方法來處理的,以下圖所示:
經過ele
元素拿到當前namespaceUri
,也就是在xsd
中定義的命名空間,接着委託給 DefaultNamespaceResolver
獲得具體的handler
(BridgenamspaceHandler
) , 而後執行parse
解析。
http\://bridge.glmapper.com/schema/bridge=
com.glmapper.extention.namespacehandler.BridgeNamespaceHandler
http\://bridge.glmapper.com/schema/bridge.xsd=META-INF/bridge.xsd
複製代碼
配置這個實際上是爲了讓Spring
在解析xml
的時候可以感知到咱們的自定義元素,咱們須要把NamespaceHandler
和xsd
文件放到位於META-INF目錄下的spring.handlers
和 spring.schmas
文件中。這樣就能夠在spring
配置文件中使用咱們自定義的標籤了。以下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:bridge="http://bridge.glmapper.com/schema/bridge" 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 http://bridge.glmapper.com/schema/bridge http://bridge.glmapper.com/schema/bridge.xsd">
<bridge:application id="bridgeTestApplication" name="bridgeTestApplication" version="1.0" organization="bridge.glmapper.com" owner="leishu@glmapper"/>
</beans>
複製代碼
驗證下從容器中獲取咱們的bean
:
public static void main(String[] args) {
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("classpath:bean.xml");
ApplicationConfig applicationConfig = (ApplicationConfig)
applicationContext.getBean("bridgeTestApplication");
System.out.println("applicationConfig = "+applicationConfig);
}
複製代碼
輸出示例:
applicationConfig = ApplicationConfig {
id=bridgeTestApplication,
name='bridgeTestApplication',
version='1.0',
owner='leishu@glmapper',
organization='bridge.glmapper.com'
}
複製代碼
總體來看,若是咱們要實現本身的 xml
標籤,僅需完成如下幾步便可:
SOFARpc
中的 rpc.xsd
文件是集成在 sofaboot.xsd
文件中的,詳細可見:sofa-boot
xsd
文件這裏不貼了,有點長
先看下 spring.handlers
和 spring.schmas
配置:
http\://sofastack.io/schema/sofaboot=
com.alipay.sofa.infra.config.spring.namespace.handler.SofaBootNamespaceHandler
http\://sofastack.io/schema/sofaboot.xsd=
META-INF/com/alipay/sofa/infra/config/spring/namespace/schema/sofaboot.xsd
http\://sofastack.io/schema/rpc.xsd=
META-INF/com/alipay/sofa/infra/config/spring/namespace/schema/rpc.xsd
複製代碼
從 spring.handlers
找到 NamespaceHandler
: SofaBootNamespaceHandler
。
源碼以下,這裏看出來,並非像上面咱們本身寫的那種方式那樣,會有一個 BeanDefinitionParser
。這裏其實設計的很巧妙,經過spi
的方式來載入具體的BeanDefinitionParser
。
public class SofaBootNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
ServiceLoader<SofaBootTagNameSupport> serviceLoaderSofaBoot =
ServiceLoader.load(SofaBootTagNameSupport.class);
//SOFABoot
for (SofaBootTagNameSupport tagNameSupport : serviceLoaderSofaBoot) {
this.registerTagParser(tagNameSupport);
}
}
private void registerTagParser(SofaBootTagNameSupport tagNameSupport) {
if (!(tagNameSupport instanceof BeanDefinitionParser)) {
// log
return;
}
String tagName = tagNameSupport.supportTagName();
registerBeanDefinitionParser(tagName, (BeanDefinitionParser)
tagNameSupport);
}
}
複製代碼
這裏能夠看出有 ReferenceDefinitionParser
和 ServiceDefinitionParser
兩個解析類,分別對應服務引用和服務暴露。
下面以ReferenceDefinitionParser
爲例,先看下它的類圖:
解析工做都是在 AbstractContractDefinitionParser
類中完成, ReferenceDefinitionParser
本身只是作了一些特殊處理【jvm-first,jvm服務】。
本篇經過 NamespaceHandler
瞭解瞭如何去編寫咱們自定義的xml標籤,從NamespaceHandler
的角度能夠很好的理解一些 RPC
框架中最基礎的基於xml
方式的服務引用和暴露的實現思路。另外經過分析 SOFARpc
,也瞭解了在實際的工程組件中對於NamespaceHandler
的擴展使用。