Dubbo 提供了 XML 和注解兩種方式來與 Spring 進行結合,目前最常用的還是 XML 方式,Dubbo 自定義了若干個 XML 標簽(eg.
<dubbo:service>
)。本文首先分析 “如何在 Spring 中自定義 XML 標簽”,然后分析 Dubbo 自定義標簽的設計與實現(基于 dubbo 2.6.7-SNAPSHOT)。
一、Spring 自定義標簽的使用姿勢
步驟如下:
三個類
- 定義 xml 標簽類,eg. Dubbo
ApplicationConfig
- 編寫 xml 標簽解析類,eg.
DubboBeanDefinitionParser
- 編寫命名空間處理類,eg.
DubboNamespaceHandler
,用于注冊標簽解析器
三個配置文件
- 編寫 xsd 文件,eg.
META-INF/dubbo.xsd
- 指定 xsd 文件的位置,eg.
META-INF/spring.schemas
,注意:該文件的文件名和文件路徑是固定的
- 指定命名空間(
key
)與命名空間處理器(value
)的對應關系,eg.META-INF/spring.handlers
,注意:該文件的文件名和文件路徑是固定的
示例
image.png
maven 依賴
<!-- 用于定義自定義標簽 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
<!-- 用于提供上下文(這里用于測試) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
xml 標簽類 RpcConfig
/**
* xml 標簽類
*/
public class RpcConfig {
private String id;
private int timeout;
// getter and setter
...
}
標簽解析器
/**
* <rpc:xxx></rpc:xxx> 標簽解析器
*/
public class RpcBeanDefinitionParser implements BeanDefinitionParser {
/**
* eg. RpcConfig
*/
private Class<?> beanClass;
public RpcBeanDefinitionParser(Class<?> aClass) {
this.beanClass = aClass;
}
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 1. 創建 BeanDefinition
RootBeanDefinition rootBD = new RootBeanDefinition();
rootBD.setBeanClass(beanClass);
rootBD.getPropertyValues().add("id", element.getAttribute("id"));
rootBD.getPropertyValues().add("timeout", element.getAttribute("timeout"));
// 2. 注冊 BeanDefinition 到 BeanDefinitionRegistry 中
BeanDefinitionRegistry registry = parserContext.getRegistry();
registry.registerBeanDefinition(beanClass.getName(), rootBD);
return rootBD;
}
}
命名空間處理器
/**
* 命名空間處理類
*/
public class RpcNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// 注冊 <rpc:provider> 標簽的解析器
// key:elementName - eg.provider,value:解析器
registerBeanDefinitionParser("provider", new RpcBeanDefinitionParser(RpcConfig.class));
}
}
META-INF/rpc.xsd
<xsd:schema
xmlns="http://io.study/schema"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://io.study/schema">
<!-- 歸類屬性 -->
<xsd:complexType name="providerType">
<xsd:attribute name="id" type="xsd:string">
<!-- 注釋 -->
<xsd:annotation>
<xsd:documentation><![CDATA[ The provider id. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="timeout" type="xsd:int">
<xsd:annotation>
<xsd:documentation><![CDATA[ The provider timeout. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<!-- 標簽元素 -->
<xsd:element name="provider" type="providerType">
<xsd:annotation>
<xsd:documentation><![CDATA[ provider 元素的的文檔說明 ]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:schema>
可去掉注釋簡化如下:
<xsd:schema
xmlns="http://io.study/schema"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://io.study/schema">
<!-- 歸類屬性 -->
<xsd:complexType name="providerType">
<xsd:attribute name="id" type="xsd:string"/>
<xsd:attribute name="timeout" type="xsd:int"/>
</xsd:complexType>
<!-- 標簽元素 -->
<xsd:element name="provider" type="providerType"/>
</xsd:schema>
META-INF/spring.schemas
http\://io.study/schema/rpc.xsd=META-INF/rpc.xsd
- key:在 spring 的 xml 配置文件中
schemaLocation
指定的 *.xsd 文件的位置標志;- value:*.xsd 文件的在類路徑下的位置。
META-INF/spring.handlers
http\://io.study/schema=io.study.xmltag.RpcNamespaceHandler
- key:在 *.xsd 和 spring 的 xml 配置文件中指定的命名空間;
- value:命令空間處理器。
測試
spring 配置文件 test/rpc.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:rpc="http://io.study/schema"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://io.study/schema http://io.study/schema/rpc.xsd">
<!-- 使用自定義標簽 -->
<rpc:provider id="helloworld" timeout="60"/>
</beans>
測試主類
public class MainTest {
public static void main(String[] args) {
// 1. 加載配置文件 rpc.xml
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test/rpc.xml");
// 2. 根據 type 獲取 bean;也可以根據 name 獲取 bean
RpcConfig rpc = context.getBean(RpcConfig.class);
// 3. 使用 bean
System.out.println(rpc.getId() + " - " + rpc.getTimeout());
}
}
二、Spring 自定義標簽大致原理
BeanDefinitionParserDelegate # BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd)
// 1. 獲取命名空間 namespaceUri=http://io.study/schema
-- String namespaceUri = getNamespaceURI(ele)
// 2. 根據命令空間獲取處理器,handler=RpcNamespaceHandler
-- NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri)
-- namespaceHandler.init() // 執行解析器的注冊操作
// 3. 使用命令空間處理器進行解析
-- handler.parse(ele, new ParserContext(...))
-- NamespaceHandlerSupport # BeanDefinition parse(Element element, ParserContext parserContext)
// 獲取對應 xml 元素的解析器
-- BeanDefinitionParser parser = findParserForElement(element, parserContext);
// 進行解析
-- parser.parse(element, parserContext)
spring 在加載過程中,遇到自定義標簽時會去 spring.schemas 中尋找 *.xsd,會去 spring.handlers 中尋找命名空間處理器 XxxNamespaceHandler 完成自定義標簽的解析器的注冊,然后獲取對應標簽的解析器,進行解析操作。
詳細的源碼分析,見 《spring 源碼深度解析》第四章。
三、Dubbo 自定義標簽的設計與實現
類比上述我們實現的 <rpc.xxx>
標簽,Dubbo 也是用相同的機制實現了一套 <dubbo:xxx>
標簽,eg. <dubbo:service>
,我們以 <dubbo:service>
為例,來看下 Dubbo 自定義標簽的實現機制。
image.png
xml 標簽類 ServiceBean
public class ServiceBean<T> extends ServiceConfig<T> ... {
...
}
public class ServiceConfig<T> extends AbstractServiceConfig {
// reference to interface impl
private T ref;
...
}
標簽解析器
/**
* <dubbo:xxx></dubbo:xxx> 標簽解析器
*/
public class DubboBeanDefinitionParser implements BeanDefinitionParser {
/**
* eg. ServiceBean
*/
private final Class<?> beanClass;
...
public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) {
this.beanClass = beanClass;
...
}
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return parse(element, parserContext, beanClass, required);
}
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
beanDefinition.setLazyInit(false);
/********* 處理 id 屬性 *********/
String id = element.getAttribute("id");
...
// 注冊 BeanDefinition 到 BeanDefinitionRegistry 中
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
// 添加 id 屬性
beanDefinition.getPropertyValues().addPropertyValue("id", id);
/********* 處理 ProtocolConfig,即 <dubbo:protocol> 標簽 *********/
if (ProtocolConfig.class.equals(beanClass)) {
...
/********* 處理 ServiceBean,即 <dubbo:service> 標簽 *********/
} else if (ServiceBean.class.equals(beanClass)) {
...
// 設置 ref 屬性
beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
/********* 處理 ProviderConfig,即 <dubbo:provider> 標簽 *********/
} else if (ProviderConfig.class.equals(beanClass)) {
...
}
...
}
}
命名空間處理器
/**
* 命名空間處理類
*/
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
...
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
...
}
}
META-INF/dubbo.xsd
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
...
xmlns="http://dubbo.apache.org/schema/dubbo"
targetNamespace="http://dubbo.apache.org/schema/dubbo">
<!-- 歸類屬性 abstractServiceType -->
<xsd:complexType name="abstractServiceType">
<xsd:complexContent>
<xsd:extension base="abstractInterfaceType">
...
<xsd:attribute name="group" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[ The service group. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
...
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<!-- 歸類屬性 serviceType -->
<xsd:complexType name="serviceType">
<xsd:complexContent>
<!-- 繼承 abstractServiceType -->
<xsd:extension base="abstractServiceType">
...
<!-- <dubbo:service ref='xxx'/> -->
<xsd:attribute name="ref" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation>
<![CDATA[ The service implementation instance bean id. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
...
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<!-- 標簽元素 -->
<xsd:element name="service" type="serviceType">
<xsd:annotation>
<xsd:documentation><![CDATA[ Export service config ]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:schema>
可去掉注釋簡化如下:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
...
xmlns="http://dubbo.apache.org/schema/dubbo"
targetNamespace="http://dubbo.apache.org/schema/dubbo">
<!-- 歸類屬性 abstractServiceType -->
<xsd:complexType name="abstractServiceType">
<xsd:complexContent>
<xsd:extension base="abstractInterfaceType">
...
<xsd:attribute name="group" type="xsd:string" use="optional"/>
...
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<!-- 歸類屬性 serviceType -->
<xsd:complexType name="serviceType">
<xsd:complexContent>
<!-- 繼承 abstractServiceType -->
<xsd:extension base="abstractServiceType">
...
<!-- <dubbo:service ref='xxx'/> -->
<xsd:attribute name="ref" type="xsd:string" use="optional"/>
...
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<!-- 標簽元素 -->
<xsd:element name="service" type="serviceType"/>
</xsd:schema>
META-INF/spring.schemas
http\://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
- key:在 spring 的 xml 配置文件中
schemaLocation
指定的 *.xsd 文件的位置標志;- value:*.xsd 文件的在類路徑下的位置。
META-INF/spring.handlers
http\://dubbo.apache.org/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
- key:在 *.xsd 和 spring 的 xml 配置文件中指定的命名空間;
- value:命令空間處理器。
測試使用
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
...
<!-- 聲明暴露的服務 -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
</beans>
完整示例:第1章 第一個 Dubbo 項目