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
,注意:该文件的文件名和文件路径是固定的
示例
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 自定义标签的实现机制。
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 项目