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文件
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
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
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 已经废弃
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }
}

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

Dubbo的DubboBeanDefinitionParser

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

使用Dubbo标签
<?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.handlers文件。spring.schema文件指定了配置约束文件的位置,加载spring.schemas文件的目的就是用来校验Spring的XML配置文件内容是否合法。加载spring.handlers文件的目的是,当解析Spring的XML配置文件中的标签时,会查找该文件中指定的DubboNamespaceHandler类来进行自定义标签的初始化和解析。

解析准备

加载 spring.schemas 文件
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 文件做准备。

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文件内容的。

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;
}
/**
 * @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 文件
/**
 * @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,否则不会对该标签进行处理。

/**
 * @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。

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

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

/**
* 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自身的解析逻辑忽略,下面我们来分析下自定义的标签的处理流程。

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对象,最后通过该对象解析当前元素。下面我们依次分析这两个步骤的代码。

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,我们在前面已经分析过了它的源码,这里再详细说明下。

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关联到一起的。

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 方法继续分析。

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 中的方法,我们来看看逻辑。

   @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定义解析器
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上下文
  /**
     * @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标签
/**
     * @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标签
/**
     * @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 的解析。

    /**
     * 解析 <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标签
/**
     * @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子标签的情况,代码过程如下:

 /**
     * 解析内嵌的标签
     *
     * @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参数设置到配置对象中。
/**
     * @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的整个过程就很容易理解了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值