Dubbo源码分析 - XML配置

前言

Dubbo源码分析 - API和属性配置 中介绍了Dubbo的配置承载对象,分析了核心的配置类及方法。了解了API配置后XML配置就容易多了,XML配置相比较API配置的区别在配置对象创建及其属性的设置是由Spring管理的,Dubbo和Spring XML融合是关键。

Dubbo和Spring融合

Dubbo框架直接集成了Spring的能力,利用Spring配置文件扩展出自定义的解析方式,即使用Spring的自定标签。关于Spring自定标签的示例,在Spring自定义标签 中有详细介绍,Dubbo基于schema的设计也是如此,下面我们就来分析下Dubbo是怎么和Spring融合的。

Dubbo的配置对象模型

Dubbo的配置对象模型已经在 [Dubbo源码分析 - API和属性配置] 中详细介绍过了,在Dubbo的命名空间处理器中也可以具体看到哪些配置类和Spring进行交互,这里就不再介绍。

Dubbo的xsd文件

dubbo.xsd文件是用来约束使用XML配置时的标签和对应的属性,如Dubbo中的<dubbo:service>标签等。由于当前分析的dubbo版本是2.6.5,Dubbo已经捐给了Apache组织,为了遵循Apache标准和兼容Dubbo原来的版本,会出现两个xsd文件,这篇文章还是按照Dubbo原来的版本进行相关描述。

  • dubbo.xsd总览

Dubbo设计的粒度很多都是针对方法级别的,如方法级别的timeout、retries等特性。具体的每个复杂类型的详细使用可以参考:官方文档

  • dubbo.xsd中的类型关系

上图的类型继承关系和Dubbo的配置类之间的关系几乎保持一致,因为这里定义的复杂类型就是要映射到配置类的属性上,即schema中的字段对应Config类中的属性和get/set方法。

Dubbo的spring.schemas文件

1
2
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.schemas文件用来指明约束文件的具体路径。

Dubbo的spring.handlers

1
2
http\://dubbo.apache.org/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
http\://code.alibabatech.com/schema/dubbo=com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler

spring.handlers文件用来指明Dubbo的XML命名空间处理器,即使用DubboNamespaceHandler来解析Dubbo自定义的标签。

Dubbo的DubboNamespaceHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class DubboNamespaceHandler extends NamespaceHandlerSupport {

static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}

/**
* 方法中定义了每个<xsd:element/>对应的BeanDefinitionParser 【Dubbo Bean定义解析器】
*/
@Override
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
// 注解已经重写,AnnotationBeanDefinitionParser 已经废弃,即@DubboComponentScan 作为 Dubbo 2.5.7 新增的 Annotation,是XML 元素 <dubbo:annotation> 的替代方案。
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
}

Dubbo解析配置的入口是在 DubboNamespaceHandler类中完成的,该类主要把不同的标签关联到解析实现类中,registerBeanDefinitionParser方法约定在遇到Dubbo自定的标签如application、registry、protocol等都会委托给Dubbo的命名空间处理器DubboNamespaceHandler处理,该处理器又会把解析任务交给DubboBeanDefinitionParser来处理。

Dubbo的DubboBeanDefinitionParser

实现了Spring的BeanDefinitionParser接口,是真正用来解析自定的Dubbo标签,将标签解析成对应的Bean定义并注册到Spring上下文中。

使用Dubbo标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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" owner="gentryhuang"/>

<!-- use multicast registry center to export service -->
<!--<dubbo:registry address="multicast://224.5.6.7:1234" protocol="test"/> -->
<dubbo:registry address="zookeeper://127.0.0.1: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="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>

<!-- declare the service interface to be exported -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>

</beans>

小结

以上就是Dubbo和Spring的XML配置进行融合的过程,与 Spring自定义标签 文章中的流程是一样的。总的来说,Dubbo框架先以流的形式装载Spring的XML配置文件,在将流解析成DOM的过程中会加载spring.schemas文件,然后读取该文件中指定的的xsd约束文件,接着使用xsd中的约束规则对每个标签及其属性进行校验,不合法则抛出异常,整个配置文件符合约束规则则生成DOM对象。spring.schema文件指定了配置约束文件的位置,加载spring.schemas文件的目的就是用来校验Spring的XML配置文件内容是否合法。加载spring.handlers文件的目的是,当解析Spring的XML配置文件中的标签时,会查找该文件中指定的DubboNamespaceHandler类来进行自定义标签的初始化和解析。

解析准备

加载 spring.schemas 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
AbstractXmlApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory){
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
// 设置 'META-INF/spring.schemas' 到 ResourceEntityResolver
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}

上面代码就设置 spring.schemas 文件路径,为接下来加载 spring.schemas 文件做准备。

1
2
3
4
5
6
7
8
9
XmlBeanDefinitionReader#doLoadBeanDefinitions(InputSource inputSource, Resource resource){
try {
// 加载 META-INF/spring.schemas 中xsd文件,在构建Dom时进行校验XML配置内容是否正确
Document doc = doLoadDocument(inputSource, resource);
// 加载 META-INF/spring.handlers 中的命名空间处理器,初始化并放入缓存
return registerBeanDefinitions(doc, resource);
}
// 省略无关代码
}

上面的代码是注册XML中的Bean的大流程入口,分别是加载 META-INF/spring.schemas 中xsd文件,用于构建DOM时校验XML配置内容是否正确,加载 META-INF/spring.handlers 中的命名空间处理器,用于处理标签和BeanDefinitionParser的映射关系以及解析标签。下面我们来看Spring是如何加载spring.schemas文件内容的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
PluggableSchemaResolver#getSchemaMappings(){
if (this.schemaMappings == null) {
synchronized (this) {
if (this.schemaMappings == null) {
if (logger.isDebugEnabled()) {
logger.debug("Loading schema mappings from [" + this.schemaMappingsLocation + "]");
}
try {
// 从 META-INF/spring.schemas 中读取xsd文件路径
Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.schemaMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded schema mappings: " + mappings);
}
Map<String, String> schemaMappings = new ConcurrentHashMap<String, String>(mappings.size());
// 放入缓存中
CollectionUtils.mergePropertiesIntoMap(mappings, schemaMappings);
this.schemaMappings = schemaMappings;
}catch (IOException ex) {
throw new IllegalStateException("Unable to load schema mappings from location [" + this.schemaMappingsLocation + "]", ex);
}
}
}
}
return this.schemaMappings;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* @param publicId
* @param systemId
*/
PluggableSchemaResolver#resolveEntity(String publicId, String systemId){
if (systemId != null) {
// 根据 spring.schemas中配置的xxx.xsd找到对应的xsd文件
String resourceLocation = getSchemaMappings().get(systemId);
if (resourceLocation != null) {
// 加载xsd文件
Resource resource = new ClassPathResource(resourceLocation, this.classLoader);
try {
InputSource source = new InputSource(resource.getInputStream());
source.setPublicId(publicId);
source.setSystemId(systemId);
if (logger.isDebugEnabled()) {
logger.debug("Found XML schema [" + systemId + "] in classpath: " + resourceLocation);
}
return source;
}catch (FileNotFoundException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Couldn't find XML schema [" + systemId + "]: " + resource, ex);
}
}
}
}
return null;
}

以上就是Spring在启动时加载spring.schemas中配置的xsd文件的几个代码片段,将XML配置文件解析成DOM的过程中,对每个标签及其属性进行校验,依据就是xsd中的约束条件。由于是Spring的源码部分,这里不进行深入分析,感兴趣的胖友可以自行调试。

加载 spring.handlers 文件

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @param doc 配置文件对应的DOM对象
* @param resource 配置文件资源对象
*/
XmlBeanDefinitionReader#registerBeanDefinitions(Document doc, Resource resource){
// 创建Bean定义的DOMReader,用来读取、解析DOM,接着创建对应的Bean
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 读取、解析DOM、创建对应的Bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}

registerBeanDefinitions方法是将Spring的XML配置文件中定义的有关标签进行创建并注册到Spring的注册表中。注意,这里所说的能够创建Bean的有关标签必须有对应的BeanDefinitionParser,否则不会对该标签进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @param resource 配置文件资源对象
*/
XmlBeanDefinitionReader#createReaderContext(Resource resource){
return new XmlReaderContext(
resource,
this.problemReporter,
this.eventListener,
this.sourceExtractor,
this,
// 读取 META-INF/spring.handlers 文件
getNamespaceHandlerResolver()
);
}

createReaderContext 方法用来创建 XmlReaderContext,该对象中包含的核心属性如下:

由XmlReaderContext对象中的属性可知,在创建该对象的过程中对 META-INF/spring.handlers 文件进行了读取。现在有了配置文件的DOM对象、Bean定义工厂以及spring.handlers文件中各种NamespaceHandler,接下来就可以解析DOM树,创建并注册相应的Bean。

1
2
3
4
5
6
7
8
DefaultBeanDefinitionDocumentReader#registerBeanDefinitions(Document doc, XmlReaderContext readerContext){
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
// 获取DOM的根元素,一般是 beans
Element root = doc.getDocumentElement();
// 解析入口
doRegisterBeanDefinitions(root);
}

上面的代码主要是获取DOM对象的根元素,然后以这个根元素作为起点进行解析,下面我们接着解析代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* Parse the elements at the root level in the document:
* "import", "alias", "bean".
* @param root DOM的根元素
* @param delegate Bean定义解析器代理
*/
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 判读根元素是不是默认的命名空间 'http://www.springframework.org/schema/beans'
if (delegate.isDefaultNamespace(root)) {
// 获取根元素下的子元素列表
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
// 判断是否是元素
if (node instanceof Element) {
Element ele = (Element) node;
// 当前元素的命名空间如果是默认的命名空间即Spring自身的命名空间,则通过Spring自身逻辑进行解析
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
// 当前元素的命名空间非默认的命名空间即自定义的标签,则通过自定义逻辑进行解析
else {
delegate.parseCustomElement(ele);
}
}
}
}else {
delegate.parseCustomElement(root);
}
}

上面代码的主要逻辑是判断要解析的DOM元素即标签,是否是Spring内置的,如果是Spring内置则整个解析逻辑使用Spring自身的那一套,如果是自定义的,则解析逻辑交给开发者。Spring自身的解析逻辑忽略,下面我们来分析下自定义的标签的处理流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element ele){
return parseCustomElement(ele, null);
}

/**
* @param ele DOM的根元素
* @param containingBd
*/
BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element ele, org.springframework.beans.factory.config.BeanDefinition containingBd){
// 获取元素即标签的命名空间
String namespaceUri = getNamespaceURI(ele);
// 使用 XmlReaderContext中的 DefaultNamespaceHandlerResolver获取命名空间对应的 NamespaceHandler对象
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 使用 NamespaceHandler 对象解析标签
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

上面代码先是获取当前元素的命名空间,然后通过该命名空间获取对应 NamespaceHandler对象,最后通过该对象解析当前元素。下面我们依次分析这两个步骤的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
DefaultNamespaceHandlerResolver#resolve(String namespaceUri){
// 获取 DefaultNamespaceHandlerResolver#handlerMappings属性,即命名空间到NamespaceHandler的映射,注意这里的NamespaceHandler可能是还没有进行实例化的字符串
Map<String, Object> handlerMappings = getHandlerMappings();
// 从缓存中获取 NamespaceHandler
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
// 如果当前命名空间对应的 NamespaceHandler 就是 NamespaceHandler对象,则需要进行实例化,直接返回即可
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
// 当前命名空间对应的 NamespaceHandler 还是字符串,需要反射创建对象
else {
String className = (String) handlerOrClassName;
try {
// 获取当前 当前命名空间对应的 NamespaceHandler 串 的 Class
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
// 创建对象
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 执行 init 方法,进行标签和BeanDefinitionParser 的关联
namespaceHandler.init();
// 加入缓存
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}catch (ClassNotFoundException ex) {
throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "] not found", ex);
}catch (LinkageError err) {
throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "]: problem with handler class file or dependent class", err);
}
}
}

上面代码核心是获取当前命名空间对应的 NamespaceHandler ,如果 NamespaceHandler 还是个字符串,那么就通过反射创建对象,接着调用该对象的 init(),进行标签和 BeanDefinitionParser 的关联 ,方法如果已经创建过了对象则直接返回该 NamespaceHandler 对象。由于Dubbo自定义标签的命名空间对应的NamespaceHandler是 DubboNamespaceHandler,我们在前面已经分析过了它的源码,这里再详细说明下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class DubboNamespaceHandler extends NamespaceHandlerSupport {

static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}

/**
* 方法中定义了每个<xsd:element/>对应的BeanDefinitionParser 【Dubbo Bean定义解析器】
*/
@Override
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
}

上面的代码比较直观,一个标签对应一个 DubboBeanDefinitionParser 对象,同时也对应这一个Dubbo的配置承载类。我们接下主要看registerBeanDefinitionParser方法是怎么把标签和DubboBeanDefinitionParser关联到一起的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class NamespaceHandlerSupport implements NamespaceHandler {

/**
* 标签名 到 BeanDefinitionParser 映射集合
*/
private final Map<String, BeanDefinitionParser> parsers = new HashMap<String, BeanDefinitionParser>();

/**
* 关联 标签名 到 BeanDefinitionParser
*/
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
}

原来如此简单,就是调用父类 NamespaceHandlerSupport 的registerBeanDefinitionParser方法,将标签名到BeanDefinitionParser的映射保存到缓存中。到了这里所有解析前的工作已经准备就绪,终于可以进入到这篇文章的核心部分了。之所以用了那么多的铺垫,就是想把整个过程串起来,如果一下子进入到Dubbo自定义标签的解析感觉还是挺奇怪的,毕竟笔者对Spring的源码也不熟悉,就按部就班吧。

解析标签

解析准备是特意为解析标签做的铺垫,有了这个铺垫下面的解析逻辑就容易很多了。我们接着解析准备中的 parseCustomElement 方法继续分析。

1
2
3
4
5
6
7
8
9
10
11
12
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
// 1. 获取DOM元素即标签对应的命名空间
String namespaceUri = getNamespaceURI(ele);
// 2. 获取命名空间映射的 NamespaceHandler对象
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 3. 调用 NamespaceHandler对象 的parse方法进行解析
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

上面代码中的第3步才正式进入到标签的解析,这里的 NamespaceHandler 就 DubboNamespaceHandler对象,parse 方法是其父类 NamespaceHandlerSupport 中的方法,我们来看看逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  @Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
/*
* 1. 获取标签的名称关联的 BeanDefinitionParser
* 2. 使用 BeanDefinitionParser解析标签
*/
return findParserForElement(element, parserContext).parse(element, parserContext);
}


private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
// 获取标签名
String localName = parserContext.getDelegate().getLocalName(element);
// 从缓存中获取标签名对应的 BeanDefinitionParser对象,即 DubboBeanDefinitionParser对象
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
return parser;
}

上面代码就是从 标签名 到 BeanDefinitionParser 映射集合parsers中获取标签名对应的BeanDefinitionParser对象,该映射集合是在 DubboNamespaceHandler#init 方法执行时维护的。下面我们接着分析DubboBeanDefinitionParser类。

Dubbo Bean定义解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class DubboBeanDefinitionParser implements BeanDefinitionParser {

/**
* 标签元素对应的对象类
*/
private final Class<?> beanClass;
/**
* 是否需要Bean的 id 属性
*/
private final boolean required;

/**
* @param beanClass Bean 对象的类
* @param required 是否需要在Bean对象的编号(id)不存在时自动生成编号。无需被其他应用引用的配置对象,无需自动生成编号。 eg:<dubbo:reference/>
*/
public DubboBeanDefinitionParser(Class<?> beanClass, boolean required) {
this.beanClass = beanClass;
this.required = required;
}

/**
* Spring解析标签的入口方法
*
* @param element 标签元素对象
* @param parserContext 解析上下文
* @return
*/
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
return parse(element, parserContext, beanClass, required);
}

// ${省略的代码}

}

DubboBeanDefinitionParser实现了Spring的BeanDefinitionParser接口,即Spring的Bean定义解析器。该类中有两个重要属性,beanClassrequired,这两个属性的值是在创建Dubbo的Bean定义解析器时通过构造方法传入的,分别是标签元素对应的配置类和在创建配置Bean的时候可能需要i的d属性。parse方法是解析XML元素的主流程的入口,其中 parserContext 参数是XML解析的上下文,它包含了 XmlReaderContext 这个重要对象,而该对象中又包含了BeanFactory等信息,具体如下图:

有了BeanFactory就可以实现Bean的定义了,接下来我们继续分析Dubbo是如何处理自定义标签与对应的配置类之间的关系,以及怎样创建标签对应的Bean定义的。

创建Bean定义并注册到Spring上下文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* @param element 标签对应的DOM
* @param parserContext spring 解析上下文
* @param beanClass 标签对应的配置类
* @param required 在创建Bean定义的时候是否需要id
* @return 标签对应的配置类的Bean定义
*/
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {
// 生成Spring的Bean定义,指定beanClass交给Spring反射创建实例
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(beanClass);
/**
* 设置Bean初始化方式,默认设置为延迟加载。
* 需要说明的是,引用缺省是延迟初始化的,只有引用被注入到其它Bean或者getBean() 获取才会初始化。如果需要立即初始化可以配置: <dubbo:reference init="true"/>
*/
beanDefinition.setLazyInit(false);

//--------------------------- 确保Spring 容器中没有重复的Bean定义 开始 ------------------------/

// 解析标签对象的id属性
String id = element.getAttribute("id");
// 标签没有设置id属性,并且创建的Bean定义需要id时,就执行生成id的逻辑。需要注意的是,Dubbo的reference标签对应Bean定义不需要id
if ((id == null || id.length() == 0) && required) {
// 1. 取name属性值
String generatedBeanName = element.getAttribute("name");
if (generatedBeanName == null || generatedBeanName.length() == 0) {
// 2. 也没有设置name属性,此时如果当前标签是Protocol,那么id的值就直接设置为 'dubbo',非Protocol协议则尝试取标签的interface属性值
if (ProtocolConfig.class.equals(beanClass)) {
generatedBeanName = "dubbo";
} else {
generatedBeanName = element.getAttribute("interface");
}
}
// 3. 以上过程都没有生成id,则最后使用标签对应的配置类的类名
if (generatedBeanName == null || generatedBeanName.length() == 0) {
generatedBeanName = beanClass.getName();
}
id = generatedBeanName;
int counter = 2;
// 检查Spring注册表中是否存在标识id,存在就通过自增序列继续处理id,使其唯一
while (parserContext.getRegistry().containsBeanDefinition(id)) {
id = generatedBeanName + (counter++);
}
}

if (id != null && id.length() > 0) {
if (parserContext.getRegistry().containsBeanDefinition(id)) {
throw new IllegalStateException("Duplicate spring bean id " + id);
}
// 把标签对应的配置类的Bean定义注册到Spring,Bean 名称为id
parserContext.getRegistry().registerBeanDefinition(id, beanDefinition);
// 为Bean追加id属性
beanDefinition.getPropertyValues().addPropertyValue("id", id);
}

// ${省略的代码}

}

特殊处理protocol标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* @param element 标签对应的DOM
* @param parserContext spring 解析上下文
* @param beanClass 标签对应的配置类
* @param required 在创建Bean定义的时候是否需要id
* @return 标签对应的配置类的Bean定义
*/
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {

// ${省略的代码}

if (ProtocolConfig.class.equals(beanClass)) {
/**
* 以下代码逻辑需要满足:
* 顺序需要这样:
* 1 <dubbo:service interface="com.xxx.xxxService protocol="dubbo" ref="xxxServiceImpl"/>
* 2 <dubbo:protocol id ="dubbo" name="dubbo" port="20880"/>
*/
// 获取Bean注册表中所有的Bean id
for (String name : parserContext.getRegistry().getBeanDefinitionNames()) {
// 根据id获取Bean定义
BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name);
// 获取当前Bean定义的属性对象集合,并尝试获取属性名为 'protocol' 的属性对象
PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol");
if (property != null) {
// 获取属性值
Object value = property.getValue();
// 如果当前遍历的Bean定义中的属性满足条件,就更新该Bean的 protocol 属性值,即名称为id的RuntimeBeanReference对象
if (value instanceof ProtocolConfig && id.equals(((ProtocolConfig) value).getName())) {
definition.getPropertyValues().addPropertyValue("protocol", new RuntimeBeanReference(id));
}
}
}
}

// ${省略的代码}

}

上面的代码用来处理框架中那些属性名为’protocol’且属性类型为为ProtocolConfig的Bean,如果该Bean符合条件就更新该Bean的protocol属性值。

特殊处理service标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* @param element 标签对应的DOM
* @param parserContext spring 解析上下文
* @param beanClass 标签对应的配置类
* @param required 在创建Bean定义的时候是否需要id
* @return 标签对应的配置类的Bean定义
*/
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {

// ${省略的代码}

else if (ServiceBean.class.equals(beanClass)) {
// 如果<dubbo:service>配置了class属性,那么为具体class配置的类创建Bean定义,并且把该定义注入到Service的 ref属性。一般不这么使用。
// eg: <dubbo:service interface="com.alibaba.dubbo.demo.DemoService class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>
String className = element.getAttribute("class");
if (className != null && className.length() > 0) {
RootBeanDefinition classDefinition = new RootBeanDefinition();
classDefinition.setBeanClass(ReflectUtils.forName(className));
classDefinition.setLazyInit(false);
// 解析 <dubbo:service class="xxx"/> 情况下内嵌的<property/>标签,然后设置到classDefinition的属性中
parseProperties(element.getChildNodes(), classDefinition);
// 设置ref属性,相当于设置 <dubbo:service ref=""/>属性
beanDefinition.getPropertyValues().addPropertyValue("ref", new BeanDefinitionHolder(classDefinition, id + "Impl"));
}
}

// ${省略的代码}
}

上面的代码用来处理 service标签 中有 class 属性的情况,处理逻辑就是创建class对应的Bean定义,然后设置到 service标签 对应的Bean的ref属性中。我们再来看看对service的子标签 property 的解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 解析 <dubbo:service class="xxx"/> 情况下内嵌的<property/>
*
* @param nodeList 子元素数组
* @param beanDefinition Bean定义对象
*/
private static void parseProperties(NodeList nodeList, RootBeanDefinition beanDefinition) {
if (nodeList != null && nodeList.getLength() > 0) {
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
// 只解析<property/>标签
if (node instanceof Element) {
if ("property".equals(node.getNodeName())
|| "property".equals(node.getLocalName())) {
String name = ((Element) node).getAttribute("name");
// 优先使用value属性,其次使用ref属性
if (name != null && name.length() > 0) {
String value = ((Element) node).getAttribute("value");
String ref = ((Element) node).getAttribute("ref");
if (value != null && value.length() > 0) {
beanDefinition.getPropertyValues().addPropertyValue(name, value);
} else if (ref != null && ref.length() > 0) {
beanDefinition.getPropertyValues().addPropertyValue(name, new RuntimeBeanReference(ref));
} else {
// 属性不全,抛出异常
throw new UnsupportedOperationException("Unsupported <property name=\"" + name + "\"> sub tag, Only supported <property name=\"" + name + "\" ref=\"...\" /> or <property name=\"" + name + "\" value=\"...\" />");
}
}
}
}
}
}
}

上面的代码用来解析service的property标签,目的是为service标签的class属性对应的Bean定义设置属性,比较简单。

特殊处理provider/consumer标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @param element 标签对应的DOM
* @param parserContext spring 解析上下文
* @param beanClass 标签对应的配置类
* @param required 在创建Bean定义的时候是否需要id
* @return 标签对应的配置类的Bean定义
*/
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {

// ${省略的代码}

else if (ProviderConfig.class.equals(beanClass)) {
// 解析 <dubbo:provider/> 的内嵌子元素<dubbo:service/>
parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition);
}else if (ConsumerConfig.class.equals(beanClass)) {
// 解析 <dubbo:consumer/> 的内嵌子元素<dubbo:reference/>
parseNested(element, parserContext, ReferenceBean.class, false, "reference", "consumer", id, beanDefinition);
}

// ${省略的代码}

}

从上面的代码可以看出,特殊处理provider/consumer标签就是处理它有service/reference子标签的情况,代码过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 解析内嵌的标签
*
* @param element 父标签对象 - provider/consumer标签对象
* @param parserContext Spring解析上下文
* @param beanClass 内嵌子元素的Bean类 - ServiceBean/ReferenceBean
* @param required 是否需要Bean的id属性
* @param tag 子元素标签名 service/reference
* @param property 父Bean对象在子元素中的属性名 provider/consumer
* @param ref 父Bean的id
* @param beanDefinition 父Bean定义对象
*/
private static void parseNested(Element element, ParserContext parserContext, Class<?> beanClass, boolean required, String tag, String property, String ref, BeanDefinition beanDefinition) {
// 获取子节点列表
NodeList nodeList = element.getChildNodes();
if (nodeList != null && nodeList.getLength() > 0) {
boolean first = true;
for (int i = 0; i < nodeList.getLength(); i++) {
// 获取子节点
Node node = nodeList.item(i);
if (node instanceof Element) {
// 当前节点是否是指定的子节点,这里可能是service/reference节点
if (tag.equals(node.getNodeName()) || tag.equals(node.getLocalName())) {
if (first) {
first = false;
// 获取父节点的default的属性值 [暂时不知道有什么用]
String isDefault = element.getAttribute("default");
if (isDefault == null || isDefault.length() == 0) {
beanDefinition.getPropertyValues().addPropertyValue("default", "false");
}
}
// 解析子元素,创建BeanDefinition 对象 (递归)
BeanDefinition subDefinition = parse((Element) node, parserContext, beanClass, required);

// 设置子BeanDefinition的指向,指向父BeanDefinition
if (subDefinition != null && ref != null && ref.length() > 0) {
subDefinition.getPropertyValues().addPropertyValue(property, new RuntimeBeanReference(ref));
}
}
}
}
}
}

上面的代码主要处理provider/consumer标签内部嵌套的标签,内部嵌套的标签对象会自动持有外层标签的对象。

设置标签的属性到 BeanDefinition

前面处理的逻辑属于特殊的情况,接下来我们分析标签的属性是如何设置到配置对象中的。本质上是通过遍历配置对象的get、set和is前缀方法,通过反射将标签属性设置到配置对象中。总体上分为两种情况:

  • 如果标签属性和方法名相同,则通过反射调用设置标签的值到配置对象中。
  • 如果标签属性不能匹配到配置对象中的方法名称,则将标签属性当作parameter参数设置到配置对象中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/**
* @param element 标签对应的DOM
* @param parserContext spring 解析上下文
* @param beanClass 标签对应的配置类
* @param required 在创建Bean定义的时候是否需要id
* @return 标签对应的配置类的Bean定义
*/
@SuppressWarnings("unchecked")
private static BeanDefinition parse(Element element, ParserContext parserContext, Class<?> beanClass, boolean required) {

// ${省略的代码}

// 用来保存已遍历的配置对象的属性集合,用来判断标签中哪些属性没有匹配上
Set<String> props = new HashSet<String>();
// 专门存放<dubbo:parameters/> 标签下子标签属性信息。最后都设置到Bean定义中
ManagedMap parameters = null;

// 1. 获取配置对象所有方法
for (Method setter : beanClass.getMethods()) {
String name = setter.getName();

// 2. 选择所有set前缀方法,并且只有一个参数的 public 方法
if (name.length() > 3 && name.startsWith("set") && Modifier.isPublic(setter.getModifiers()) && setter.getParameterTypes().length == 1) {

// 获取方法的参数类型
Class<?> type = setter.getParameterTypes()[0];
// 3. 提取set对应的属性名字,eg: setTimeout->timeout,setBeanName->bean-name
String property = StringUtils.camelToSplitName(name.substring(3, 4).toLowerCase() + name.substring(4), "-");

// 保存到属性 props 集合中
props.add(property);

// 4. 尝试获取属性对应的getter方法
Method getter = null;
try {
getter = beanClass.getMethod("get" + name.substring(3), new Class<?>[0]);
} catch (NoSuchMethodException e) {
try { // 没有setter对应的getter方法,尝试获取is方法,is方法在功能上是同getter
getter = beanClass.getMethod("is" + name.substring(3), new Class<?>[0]);
} catch (NoSuchMethodException e2) {
}
}

// 5. 校验属性是否有对应的getter/is前缀方法,没有就跳过
if (getter == null
|| !Modifier.isPublic(getter.getModifiers())
|| !type.equals(getter.getReturnType())) {
continue;
}

// 6. 解析 <dubbo:parameter/> 标签,将当前标签element的子标签 <dubbo:parameter/> 的属性键值对保存到parameters中
if ("parameters".equals(property)) {
parameters = parseParameters(element.getChildNodes(), beanDefinition);
// 7. 解析 <dubbo:method/> 标签,将当前标签element的子标签 <dubbo:method/> 进行解析,将解析得到的对应BeanDefiniton放入到ManagedList集合中,最后作为 beanDefiniton的methods属性值。
} else if ("methods".equals(property)) {
parseMethods(id, element.getChildNodes(), beanDefinition, parserContext);
// 8. 解析 <dubbo:argument/>标签,将当前标签element的子标签 <dubbo:argument/> 进行解析,将解析得到的对应的BeanDefinition放入到ManagedList集合中,最后作为 beanDefinition的arguments属性值。
} else if ("arguments".equals(property)) {
parseArguments(id, element.getChildNodes(), beanDefinition, parserContext);
} else {
// 9. 获取标签属性的值 【前面的步骤之所以单独处理,是因为当前配置配置对象对应的属性不是一个标签属性,而是一个子标签】
String value = element.getAttribute(property);
if (value != null) {
value = value.trim();
if (value.length() > 0) {
// 9.1 标签中配置了 registry=N/A, 不想注册到的情况
if ("registry".equals(property) && RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(value)) {
RegistryConfig registryConfig = new RegistryConfig();
// RegistryConfig的地址设置 N/A
registryConfig.setAddress(RegistryConfig.NO_AVAILABLE);
beanDefinition.getPropertyValues().addPropertyValue(property, registryConfig);
// 9.2 多注册中心情况,将多个注册中心处理成一个集合,然后设置到 beanDefiniton 中,属性名为 'registries'
} else if ("registry".equals(property) && value.indexOf(',') != -1) {
parseMultiRef("registries", value, beanDefinition, parserContext);
// 9.3 多服务提供者情况,将多个服务提供者处理成一个集合,然后设置到 beanDefinition 中,属性为 'providers'
} else if ("provider".equals(property) && value.indexOf(',') != -1) {
parseMultiRef("providers", value, beanDefinition, parserContext);
// 9.4 多协议情况,将多个协议处理成一个集合,然后设置到 beanDefinition 中,属性为 'protocols'
} else if ("protocol".equals(property) && value.indexOf(',') != -1) {
parseMultiRef("protocols", value, beanDefinition, parserContext);
} else {
Object reference;

// 10. 属性类型为基本类型的情况
if (isPrimitive(type)) {
// 兼容性处理【一些设置了但是意义不大的属性就把值设置为null】
if ("async".equals(property) && "false".equals(value)
|| "timeout".equals(property) && "0".equals(value)
|| "delay".equals(property) && "0".equals(value)
|| "version".equals(property) && "0.0.0".equals(value)
|| "stat".equals(property) && "-1".equals(value)
|| "reliable".equals(property) && "false".equals(value)) {
// backward compatibility for the default value in old version's xsd
value = null;
}
reference = value;

//11. 处理在<dubbo:provider/> 或者 <dubbo:service/> 上定义了 protocol 属性的兼容性,目前已经不推荐这样使用了,应该单独配置 <dubbo:protocol/>
} else if ("protocol".equals(property)
&& ExtensionLoader.getExtensionLoader(Protocol.class).hasExtension(value)
&& (!parserContext.getRegistry().containsBeanDefinition(value)
|| !ProtocolConfig.class.getName().equals(parserContext.getRegistry().getBeanDefinition(value).getBeanClassName()))) {

if ("dubbo:provider".equals(element.getTagName())) {
logger.warn("Recommended replace <dubbo:provider protocol=\"" + value + "\" ... /> to <dubbo:protocol name=\"" + value + "\" ... />");
}
// backward compatibility
ProtocolConfig protocol = new ProtocolConfig();
protocol.setName(value);
reference = protocol;

//------- 12. 事件通知: 在调用前,调用后,出现异常,会触发oninvoke,onreturn,onthrow三个事件,可以配置当事件发生时,通知哪个类的哪个方法 ------//
/*
// 格式:实现Bean.方法
<bean id="demoCallBack" class = "com.alibaba.dubbo.callback.implicit.NofifyImpl"/>
<dubbo:reference id = "demoService" interface="com.alibaba.dubbo.IDemoService">
<dubbo:method name="get" onreturn="demoCallBack.xxxMethod" onthrow="demoCallBack.xMethod"/>
</dubbo:reference>
*/

// 12.1 处理 onreturn 属性
} else if ("onreturn".equals(property)) {
// 按照 . 拆分
int index = value.lastIndexOf(".");
// 获取实例名
String returnRef = value.substring(0, index);
// 获取实例的方法
String returnMethod = value.substring(index + 1);
// 创建 RuntimeBeanReference,指向回调的对象
reference = new RuntimeBeanReference(returnRef);
// 设置 onreturnMethod 到 BeanDefinition 的属性值
beanDefinition.getPropertyValues().addPropertyValue("onreturnMethod", returnMethod);
// 12.2 处理 onthrow 属性
} else if ("onthrow".equals(property)) {
int index = value.lastIndexOf(".");
String throwRef = value.substring(0, index);
String throwMethod = value.substring(index + 1);
// 创建 RuntimeBeanReference,指向回调的对象
reference = new RuntimeBeanReference(throwRef);
// 设置 onthrowMethod 到 BeanDefinition 的属性值
beanDefinition.getPropertyValues().addPropertyValue("onthrowMethod", throwMethod);
//12.3 处理oninvoke 属性
} else if ("oninvoke".equals(property)) {
int index = value.lastIndexOf(".");
String invokeRef = value.substring(0, index);
String invokeRefMethod = value.substring(index + 1);
reference = new RuntimeBeanReference(invokeRef);
beanDefinition.getPropertyValues().addPropertyValue("oninvokeMethod", invokeRefMethod);

//----------------------------- 事件通知结束 ------------------------------//

} else {
// 13. 属性名没有匹配到对应的标签名,都会到这里

//13.1 如果属性名是ref, ref 对应的Bean 必须是单例的
if ("ref".equals(property) && parserContext.getRegistry().containsBeanDefinition(value)) {
BeanDefinition refBean = parserContext.getRegistry().getBeanDefinition(value);
if (!refBean.isSingleton()) {
throw new IllegalStateException("The exported service ref " + value + " must be singleton! Please set the " + value + " bean scope to singleton, eg: <bean id=\"" + value + "\" scope=\"singleton\" ...>");
}
}
// 创建RuntimeBeanReference
reference = new RuntimeBeanReference(value);
}
// 设置Bean定义的属性
beanDefinition.getPropertyValues().addPropertyValue(property, reference);
}
}
}
}
}
}

// 将标签中自定义的属性(不是Dubbo Schema 约定好的)也加入到 parameters 集合中
NamedNodeMap attributes = element.getAttributes();
int len = attributes.getLength();
for (int i = 0; i < len; i++) {
Node node = attributes.item(i);
String name = node.getLocalName();
if (!props.contains(name)) {
if (parameters == null) {
parameters = new ManagedMap();
}
String value = node.getNodeValue();
parameters.put(name, new TypedStringValue(value, String.class));
}
}

if (parameters != null) {
beanDefinition.getPropertyValues().addPropertyValue("parameters", parameters);
}

return beanDefinition;
}

上面的代码是把属性注入到标签对应的BeanDefinition,如果属性是引用对象,Dubbo默认会创建 RuntimeBeanReference 类型注入,运行时由Spring注入引用对象。

总结

Dubbo框架解析配置文件生成BeanDefinition其实是生成标签对应的配置类的Bean定义,Bean定义中的属性值主要来源于标签的属性值,Dubbo对标签属性只是进行了提取,标签的内嵌标签处理也是如此,运行时属性注入和转换都还是Spring来完成的,Dubbo框架生成的BeanDefinition最终会委托Spring创建对应的对象,这个属于Spring的流程就不多说了。dubbo.xsd文件中定义的类型都会有与之对应的配置承载类中的属性,我们已经在API配置中介绍过了。XML配置解析还是挺复杂的,分支流比较多,下一章要分析的注解配置稍微比这个复杂一些。随着后面深入的分析就会发现这些东西都是基础,结合Dubbo的整个过程就很容易理解了。