前言 在 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设计的粒度很多都是针对方法级别的,如方法级别的timeout、retries等特性。具体的每个复杂类型的详细使用可以参考:官方文档
上图的类型继承关系和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 ) ; } @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()); } }
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" > <dubbo:application name ="demo-provider" owner ="gentryhuang" /> <dubbo:registry address ="zookeeper://127.0.0.1:2181" /> <dubbo:protocol name ="dubbo" port ="20880" /> <bean id ="demoService" class ="com.alibaba.dubbo.demo.provider.DemoServiceImpl" /> <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){ XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); beanDefinitionReader.setEnvironment(this .getEnvironment()); beanDefinitionReader.setResourceLoader(this ); beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this )); initBeanDefinitionReader(beanDefinitionReader); loadBeanDefinitions(beanDefinitionReader); }
上面代码就设置 spring.schemas 文件路径,为接下来加载 spring.schemas 文件做准备。
1 2 3 4 5 6 7 8 9 XmlBeanDefinitionReader#doLoadBeanDefinitions(InputSource inputSource, Resource resource){ try { Document doc = doLoadDocument(inputSource, resource); 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 { 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 PluggableSchemaResolver#resolveEntity(String publicId, String systemId){ if (systemId != null ) { String resourceLocation = getSchemaMappings().get(systemId); if (resourceLocation != null ) { 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 XmlBeanDefinitionReader#registerBeanDefinitions(Document doc, Resource resource){ BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); 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 XmlBeanDefinitionReader#createReaderContext(Resource resource){ return new XmlReaderContext( resource, this .problemReporter, this .eventListener, this .sourceExtractor, this , 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" ); 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 DefaultBeanDefinitionDocumentReader#parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { 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; 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 ); } BeanDefinitionParserDelegate#parseCustomElement(org.w3c.dom.Element ele, org.springframework.beans.factory.config.BeanDefinition containingBd){ String namespaceUri = getNamespaceURI(ele); NamespaceHandler handler = this .readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null ) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]" , ele); return null ; } 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){ Map<String, Object> handlerMappings = getHandlerMappings(); Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null ) { return null ; } else if (handlerOrClassName instanceof NamespaceHandler) { return (NamespaceHandler) handlerOrClassName; } else { String className = (String) handlerOrClassName; try { 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); 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 ) ; } @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 { private final Map<String, BeanDefinitionParser> parsers = new HashMap<String, 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) { String namespaceUri = getNamespaceURI(ele); NamespaceHandler handler = this .readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null ) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]" , ele); return null ; } 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) { return findParserForElement(element, parserContext).parse(element, parserContext); } private BeanDefinitionParser findParserForElement (Element element, ParserContext parserContext) { String localName = parserContext.getDelegate().getLocalName(element); 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; private final boolean required; public DubboBeanDefinitionParser (Class<?> beanClass, boolean required) { this .beanClass = beanClass; this .required = required; } @Override public BeanDefinition parse (Element element, ParserContext parserContext) { return parse(element, parserContext, beanClass, required); } }
DubboBeanDefinitionParser实现了Spring的BeanDefinitionParser接口,即Spring的Bean定义解析器。该类中有两个重要属性,beanClass
和 required
,这两个属性的值是在创建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 @SuppressWarnings ("unchecked" ) private static BeanDefinition parse (Element element, ParserContext parserContext, Class<?> beanClass, boolean required) { RootBeanDefinition beanDefinition = new RootBeanDefinition(); beanDefinition.setBeanClass(beanClass); beanDefinition.setLazyInit(false ); String id = element.getAttribute("id" ); if ((id == null || id.length() == 0 ) && required) { String generatedBeanName = element.getAttribute("name" ); if (generatedBeanName == null || generatedBeanName.length() == 0 ) { if (ProtocolConfig.class .equals (beanClass )) { generatedBeanName = "dubbo" ; } else { generatedBeanName = element.getAttribute("interface" ); } } if (generatedBeanName == null || generatedBeanName.length() == 0 ) { generatedBeanName = beanClass.getName(); } id = generatedBeanName; int counter = 2 ; 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); } parserContext.getRegistry().registerBeanDefinition(id, beanDefinition); 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 @SuppressWarnings ("unchecked" ) private static BeanDefinition parse (Element element, ParserContext parserContext, Class<?> beanClass, boolean required) { if (ProtocolConfig.class .equals (beanClass )) { for (String name : parserContext.getRegistry().getBeanDefinitionNames()) { BeanDefinition definition = parserContext.getRegistry().getBeanDefinition(name); PropertyValue property = definition.getPropertyValues().getPropertyValue("protocol" ); if (property != null ) { Object value = property.getValue(); 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 @SuppressWarnings ("unchecked" ) private static BeanDefinition parse (Element element, ParserContext parserContext, Class<?> beanClass, boolean required) { else if (ServiceBean.class .equals (beanClass )) { String className = element.getAttribute("class" ); if (className != null && className.length() > 0 ) { RootBeanDefinition classDefinition = new RootBeanDefinition(); classDefinition.setBeanClass(ReflectUtils.forName(className)); classDefinition.setLazyInit(false ); parseProperties(element.getChildNodes(), classDefinition); 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 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); if (node instanceof Element) { if ("property" .equals(node.getNodeName()) || "property" .equals(node.getLocalName())) { String name = ((Element) node).getAttribute("name" ); 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 @SuppressWarnings ("unchecked" ) private static BeanDefinition parse (Element element, ParserContext parserContext, Class<?> beanClass, boolean required) { else if (ProviderConfig.class .equals (beanClass )) { parseNested(element, parserContext, ServiceBean.class, true, "service", "provider", id, beanDefinition); }else if (ConsumerConfig.class .equals (beanClass )) { 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 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) { if (tag.equals(node.getNodeName()) || tag.equals(node.getLocalName())) { if (first) { first = false ; String isDefault = element.getAttribute("default" ); if (isDefault == null || isDefault.length() == 0 ) { beanDefinition.getPropertyValues().addPropertyValue("default" , "false" ); } } BeanDefinition subDefinition = parse((Element) node, parserContext, beanClass, required); 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 @SuppressWarnings ("unchecked" ) private static BeanDefinition parse (Element element, ParserContext parserContext, Class<?> beanClass, boolean required) { Set<String> props = new HashSet<String>(); ManagedMap parameters = null ; for (Method setter : beanClass.getMethods()) { String name = setter.getName(); if (name.length() > 3 && name.startsWith("set" ) && Modifier.isPublic(setter.getModifiers()) && setter.getParameterTypes().length == 1 ) { Class<?> type = setter.getParameterTypes()[0 ]; String property = StringUtils.camelToSplitName(name.substring(3 , 4 ).toLowerCase() + name.substring(4 ), "-" ); props.add(property); Method getter = null ; try { getter = beanClass.getMethod("get" + name.substring(3 ), new Class<?>[0 ]); } catch (NoSuchMethodException e) { try { getter = beanClass.getMethod("is" + name.substring(3 ), new Class<?>[0 ]); } catch (NoSuchMethodException e2) { } } if (getter == null || !Modifier.isPublic(getter.getModifiers()) || !type.equals(getter.getReturnType())) { continue ; } if ("parameters" .equals(property)) { parameters = parseParameters(element.getChildNodes(), beanDefinition); } else if ("methods" .equals(property)) { parseMethods(id, element.getChildNodes(), beanDefinition, parserContext); } else if ("arguments" .equals(property)) { parseArguments(id, element.getChildNodes(), beanDefinition, parserContext); } else { String value = element.getAttribute(property); if (value != null ) { value = value.trim(); if (value.length() > 0 ) { if ("registry" .equals(property) && RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(value)) { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress(RegistryConfig.NO_AVAILABLE); beanDefinition.getPropertyValues().addPropertyValue(property, registryConfig); } else if ("registry" .equals(property) && value.indexOf(',' ) != -1 ) { parseMultiRef("registries" , value, beanDefinition, parserContext); } else if ("provider" .equals(property) && value.indexOf(',' ) != -1 ) { parseMultiRef("providers" , value, beanDefinition, parserContext); } else if ("protocol" .equals(property) && value.indexOf(',' ) != -1 ) { parseMultiRef("protocols" , value, beanDefinition, parserContext); } else { Object reference; if (isPrimitive(type)) { 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)) { value = null ; } reference = value; } 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 + "\" ... />" ); } ProtocolConfig protocol = new ProtocolConfig(); protocol.setName(value); reference = protocol; } else if ("onreturn" .equals(property)) { int index = value.lastIndexOf("." ); String returnRef = value.substring(0 , index); String returnMethod = value.substring(index + 1 ); reference = new RuntimeBeanReference(returnRef); beanDefinition.getPropertyValues().addPropertyValue("onreturnMethod" , returnMethod); } else if ("onthrow" .equals(property)) { int index = value.lastIndexOf("." ); String throwRef = value.substring(0 , index); String throwMethod = value.substring(index + 1 ); reference = new RuntimeBeanReference(throwRef); beanDefinition.getPropertyValues().addPropertyValue("onthrowMethod" , throwMethod); } 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 { 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\" ...>" ); } } reference = new RuntimeBeanReference(value); } beanDefinition.getPropertyValues().addPropertyValue(property, reference); } } } } } } 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的整个过程就很容易理解了。