今天我们来谈谈 Dubbo XML 配置相关内容。关于这部分内容我打算分为以下几个部分进行介绍:
- Dubbo XML
- Spring 自定义 XML 标签解析
- Dubbo 自定义 XML 标签解析
- DubboBeanDefinitionParser.parse()
- End
Dubbo XML
在本小节开始前我们先来看下 Dubbo XML 配置文件示例:
dubbo-demo-provider.xml
<?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">
<!-- provider's application name, used for tracing dependency relationship -->
<dubbo:application name="demo-provider"/>
<!-- use multicast registry center to export service -->
<!--<dubbo:registry address="multicast://224.5.6.7:1234"/>-->
<dubbo:registry address="zookeeper://10.14.22.68:2181"/>
<!-- use dubbo protocol to export service on port 20880 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- service implementation, as same as regular local bean -->
<bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/>
<!-- declare the service interface to be exported -->
<dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>
</beans>
在这段配置文件中有一些以 dubbo 开头的 xml 标签,直觉告诉我们这种标签和 dubbo 密切相关。那么这些标签的用途是什么?又是如何被识别的呢?
我们结合 Spring 自定义 xml 标签实现相关内容来聊聊 Dubbo 是如何定义并加载这些自定义标签的。
Spring 自定义 XML 标签解析
Dubbo 中的自定义 XML 标签实际上是依赖于 Spring 解析自定义标签的功能实现的。网上关于 Spring 解析自定义 XML 标签的文章也比较多,这里我们仅介绍下实现相关功能需要的文件,给大家一个直观的印象,不去深入的对 Spring 自定义标签实现作详细分析。
- 定义 xsd 文件
XSD(XML Schemas Definition) 即 XML 结构定义。我们通过 XSD 文件不仅可以定义新的元素和属性,同时也使用它对我们的 XML 文件规范进行约束。
在 Dubbo 项目中可以找类似实现:dubbo.xsd - spring.schemas
该配置文件约定了自定义命名空间和 xsd 文件之间的映射关系,用于 spring 容器感知我们自定义的 xsd 文件位置。
http\://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
http\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/compat/dubbo.xsd
- spring.handlers
该配置文件约定了自定义命名空间和 NamespaceHandler 类之间的映射关系。 NamespaceHandler 类用于注册自定义标签解析器。
http\://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler
- 命名空间处理器
命名空间处理器主要用来注册 BeanDefinitionParser 解析器。对应上面 spring.handlers 文件中的 DubboNamespaceHandler
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
// 省略...
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
}
- BeanDefinitionParser 解析器
实现 BeanDefinitionParser 接口中的 parse 方法,用于自定义标签的解析。Dubbo 中对应 DubboBeanDefinitionParser 类。
Dubbo 解析自定义 XML 标签
终于进入到本文的重头戏环节了。在介绍 Dubbo 自定义 XML 标签解析前,先放一张图帮助大家理解以下 Spring 是如何从 XML 文件中解析并加载 Bean 的。
上图言尽于 handler.parse() 方法,如果你仔细看了上文,对 parse() 应该是有印象的。
没错,在前一小结的第五点我们介绍了 DubboBeanDefinitionParser 类。该类有个方法就叫 parse()。那么这个 parse() 方法有什么用? Spring 是如何感知到我就要调用 DubboBeanDefinitionParser 类中的 parse() 方法的呢?我们带着这两个问题接着往下看。
BeanDefinitionParserDelegate
上面图的流程比较长,我们先着重看下 BeanDefinitionParserDelegate 类中的几个关键方法。
BeanDefinitionParserDelegate.java
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
// 获取当前 element 的 namespaceURI
// 比如 dubbo.xsd 中的为 http://dubbo.apache.org/schema/dubbo
String namespaceUri = this.getNamespaceURI(ele);
// 根据 URI 获取对应的 NamespaceHandler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
} else {
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
}
这个方法干了三件事
- 获取 element 元素的 namespaceURI,并据此获取对应的 NamespaceHandler 对象。Dubbo 自定义标签(比如 Dubbo:provider) namespaceUri 的值为
http://dubbo.apache.org/schema/dubbo
; - 根据 step1 获取到的 namespaceUri ,获取对应的 NamespaceHandler 对象。这里会调用 DefaultNamespaceHandlerResolver 类的 resolve() 方法,我们下面会分析;
- 调用 handler 的 parse 方法,我们自定以的 handler 会继承 NamespaceHandlerSupport 类,所以这里调用的其实是 NamespaceHandlerSupport 类的 parse() 方法,后文分析;
一图胜千言
在详细分析 step2 和 step3 中涉及的 resolver() 和 parse() 方法前,先放一张时序图让大家有个基本概念:
DefaultNamespaceHandlerResolver.java
public NamespaceHandler resolve(String namespaceUri) {
Map<String, Object> handlerMappings = this.getHandlerMappings();
// 以 namespaceUri 为 Key 获取对应的 handlerOrClassName
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
} else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler)handlerOrClassName;
} else {
// 如果不为空且不为 NamespaceHandler 的实例,转换为 String 类型
// DubboNamespaceHandler 执行的便是这段逻辑
String className = (String)handlerOrClassName;
try {
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
// handlerClass 是否为 NamespaceHandler 的实现类,若不是则抛出异常
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
} else {
// 初始化 handlerClass
NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass);
// 执行 handlerClass类的 init() 方法
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
} catch (ClassNotFoundException var7) {
throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "] not found", var7);
} catch (LinkageError var8) {