前一篇 聊一聊 Spring 中的擴充套件機制(一) 中聊到了ApplicationListener
、ApplicationContextAware
、BeanFactoryAware
三種機制。本篇將介紹 NamespaceHandler
的擴充套件使用。
相信很多小夥伴對於這幾個類都不陌生,基本基於java
實現的RPC
框架都會使用,比如 Dubbo , SOFARpc 等。本文先從幾個小demo
入手,瞭解下基本的概念和程式設計流程,然後分析下 SOFARpc
中是如何使用的。
NamespaceHandler
NamespaceHandler
是 Spring
提供的 名稱空間處理器。下面這張圖中,除了亂入的本篇 demo
中涉及到的 BridgeNameSpaceHandler
之外,其他均為 Spring
自身提供的。
bean
和 context
依賴,所以這也僅僅是一部分。圖中我們常用的應該算是 AopNamespaceHandler
。
我們使用基於xml
的spring
配置時,可能需要配置如<aop:config />
這樣的標籤,在配置這個標籤之前,通常我們需要引入這個aop
所在的名稱空間:
<?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
,最終效果如下:
<bridge:application id="bridgeTestApplication"
name="bridgeTestApplication"
version="1.0"
organization="bridge.glmapper.com"
owner="leishu@glmapper"/>
複製程式碼
1、定義 xsd 檔案
關於 xsd
檔案的語法規則不在本篇範圍之內,有興趣的同學可以自行google
。
下面這個檔案很簡單,定義的element
name 為application
,對應於 bridge:application
中的application
。attribute
就是上面效果展示中對應的幾個屬性名。
<?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>
複製程式碼
2、編寫 NamespaceHandler
In addition to the schema, we need a NamespaceHandler that will parse all elements of this specific namespace Spring encounters while parsing configuration files.
用編寫的這個 NamespaceHandler
來解析配置檔案。
具體說來NamespaceHandler
會根據schema
和節點名找到某個BeanDefinitionParser
,然後由BeanDefinitionParser
完成具體的解析工作。
Spring
提供了預設實現類NamespaceHandlerSupport
和AbstractSingleBeanDefinitionParser
,最簡單的方式就是去繼承這兩個類。
這裡通過繼承 NamespaceHandlerSupport
這個抽象類來完成。
public class BridgeNamespaceHandler extends NamespaceHandlerSupport {
public void init() {
registerBeanDefinitionParser("application",
new ApplicationBeanDefinitionParser());
}
}
複製程式碼
這裡實際上只是註冊了一個解析器,具體的 BeanDefinitionParser
才是將 XML
元素對映到特定bean
的。
3、編寫 BeanDefinitionParser
這裡直接通過實現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
解析。
4、配置 spring.handlers 和 spring.schmas
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
標籤,僅需完成以下幾步即可:
- 1、定義 xsd 檔案
- 2、編寫 NamespaceHandler
- 3、編寫 BeanDefinitionParser
- 4、配置 spring.handlers 和 spring.schmas
SOFARpc 中使用分析
SOFARpc
中的 rpc.xsd
檔案是整合在 sofaboot.xsd
檔案中的,詳細可見:sofa-boot
xsd
檔案這裡不貼了,有點長
spring.handlers 和 spring.schmas
先看下 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
。
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
的擴充套件使用。