今天我们来谈谈 Dubbo XML 配置相关内容。关于这部分内容我打算分为以下几个部分进行介绍:
在本小节开始前我们先来看下 Dubbo XML 配置文件示例:
dubbo-demo-provider.xml <?xml version="1.0" encoding="UTF-8"?> <!-- <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://dubbo.apache.org/schema/dubbo" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd"> <!-- provider's application name, used for tracing dependency relationship --> <dubbo:application name="demo-provider"/> <!-- use multicast registry center to export service --> <!--<dubbo:registry address="multicast://224.5.6.7:1234"/>--> <dubbo:registry address="zookeeper://10.14.22.68:2181"/> <!-- use dubbo protocol to export service on port 20880 --> <dubbo:protocol name="dubbo" port="20880"/> <!-- service implementation, as same as regular local bean --> <bean id="demoService" class="org.apache.dubbo.demo.provider.DemoServiceImpl"/> <!-- declare the service interface to be exported --> <dubbo:service interface="org.apache.dubbo.demo.DemoService" ref="demoService"/> </beans> 复制代码
在这段配置文件中有一些以 dubbo 开头的 xml 标签,直觉告诉我们这种标签和 dubbo 密切相关。那么这些标签的用途是什么?又是如何被识别的呢? 我们结合 Spring 自定义 xml 标签实现相关内容来聊聊 Dubbo 是如何定义并加载这些自定义标签的。
Dubbo 中的自定义 XML 标签实际上是依赖于 Spring 解析自定义标签的功能实现的。网上关于 Spring 解析自定义 XML 标签的文章也比较多,这里我们仅介绍下实现相关功能需要的文件,给大家一个直观的印象,不去深入的对 Spring 自定义标签实现作详细分析。
http/://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd http/://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/compat/dubbo.xsd 复制代码
spring.handlers
该配置文件约定了自定义命名空间和 NamespaceHandler 类之间的映射关系。 NamespaceHandler 类用于注册自定义标签解析器。
http/://dubbo.apache.org/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler http/://code.alibabatech.com/schema/dubbo=org.apache.dubbo.config.spring.schema.DubboNamespaceHandler 复制代码
命名空间处理器
命名空间处理器主要用来注册 BeanDefinitionParser 解析器。对应上面 spring.handlers 文件中的 DubboNamespaceHandler
public class DubboNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true)); // 省略... registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser()); } } 复制代码
BeanDefinitionParser 解析器
实现 BeanDefinitionParser 接口中的 parse 方法,用于自定义标签的解析。Dubbo 中对应 DubboBeanDefinitionParser 类。
终于进入到本文的重头戏环节了。在介绍 Dubbo 自定义 XML 标签解析前,先放一张图帮助大家理解以下 Spring 是如何从 XML 文件中解析并加载 Bean 的。
上图言尽于 handler.parse() 方法,如果你仔细看了上文,对 parse() 应该是有印象的。
没错,在前一小结的第五点我们介绍了 DubboBeanDefinitionParser 类。该类有个方法就叫 parse()。那么这个 parse() 方法有什么用? Spring 是如何感知到我就要调用 DubboBeanDefinitionParser 类中的 parse() 方法的呢?我们带着这两个问题接着往下看。
上面图的流程比较长,我们先着重看下 BeanDefinitionParserDelegate 类中的几个关键方法。
BeanDefinitionParserDelegate.java public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { // 获取当前 element 的 namespaceURI // 比如 dubbo.xsd 中的为 http://dubbo.apache.org/schema/dubbo String namespaceUri = this.getNamespaceURI(ele); // 根据 URI 获取对应的 NamespaceHandler NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } else { return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); } } 复制代码
这个方法干了三件事
http://dubbo.apache.org/schema/dubbo
; 一图胜千言
在详细分析 step2 和 step3 中涉及的 resolver() 和 parse() 方法前,先放一张时序图让大家有个基本概念:
DefaultNamespaceHandlerResolver.java public NamespaceHandler resolve(String namespaceUri) { Map<String, Object> handlerMappings = this.getHandlerMappings(); // 以 namespaceUri 为 Key 获取对应的 handlerOrClassName Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } else if (handlerOrClassName instanceof NamespaceHandler) { return (NamespaceHandler)handlerOrClassName; } else { // 如果不为空且不为 NamespaceHandler 的实例,转换为 String 类型 // DubboNamespaceHandler 执行的便是这段逻辑 String className = (String)handlerOrClassName; try { Class<?> handlerClass = ClassUtils.forName(className, this.classLoader); // handlerClass 是否为 NamespaceHandler 的实现类,若不是则抛出异常 if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) { throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface"); } else { // 初始化 handlerClass NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass); // 执行 handlerClass类的 init() 方法 namespaceHandler.init(); handlerMappings.put(namespaceUri, namespaceHandler); return namespaceHandler; } } catch (ClassNotFoundException var7) { throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "] not found", var7); } catch (LinkageError var8) { throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "]: problem with handler class file or dependent class", var8); } } } 复制代码
resolve() 方法用途是根据方法参数中的 namespaceUri 获取对应的 NamespaceHandler 对象。这里会先尝试以 namespaceUri 为 key 去 handlerMappings 集合中取对象。 如果 handlerOrClassName 不为 null 且不为 NamespaceHandler 的实例。那么尝试将 handlerOrClassName 作为 className 并调用 BeanUtils.instantiateClass() 方法初始化一个 NamespaceHandler 实例。初始化后,调用其 init() 方法。这个 init() 方法比较重要,我们接着往下看。
DubboNamespaceHandler 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()); } NamespaceHandlerSupport private final Map<String, BeanDefinitionParser> parsers = new HashMap(); protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) { this.parsers.put(elementName, parser); } 复制代码
DubboNamespaceHandler 类中的 init() 方法干的事情特别简单,就是新建 DubboBeanDefinitionParser 对象并将其放入 NamespaceHandlerSupport 类的 parsers 集合中。我们再回顾一下 parseCustomElement() 方法。
BeanDefinitionParserDelegate.java public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { // 省略... return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); // 省略... } 复制代码
这里会调用 NamespaceHandlerSupport 类的 parse() 方法。我们继续跟踪一下。
public BeanDefinition parse(Element element, ParserContext parserContext) { return this.findParserForElement(element, parserContext).parse(element, parserContext); } private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { String localName = parserContext.getDelegate().getLocalName(element); BeanDefinitionParser parser = (BeanDefinitionParser)this.parsers.get(localName); if (parser == null) { parserContext.getReaderContext().fatal("Cannot locate BeanDefinitionParser for element [" + localName + "]", element); } return parser; } 复制代码
看到这里大家有没有一丝豁然开朗的感觉?之前的 resolve() 方法实际上就是根据当前 element 的 namespaceURI 获取对应的 NamespaceHandler 对象(对于 Dubbo 来说是 DubboNamespaceHandler), 然后调用 DubboNamespaceHandler 中的 init() 方法新建 DubboBeanDefinitionParser 对象并注册到 NamespaceHandlerSupport 类的 parsers 集合中。 然后 parser 方法会根据当前 element 对象从 parsers 集合中获取合适的 BeanDefinitionParser 对象。对于 Dubbo 元素来说,实际上最后执行的是 DubboBeanDefinitionParser 的 parse() 方法。
最后我们再来看看 Dubbo 解析 XML 文件的详细实现吧。如果对具体实现没有兴趣可直接直接跳过。
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"); // DubboBeanDefinitionParser 构造方法中有对 required 值进行初始化; // DubboNamespaceHandler 类中的 init 方法会创建并注册 DubboBeanDefinitionParser 类 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 { // name 属性为空且不为 ProtocolConfig 类型,取 interface 值 generatedBeanName = element.getAttribute("interface"); } } if (generatedBeanName == null || generatedBeanName.length() == 0) { // 获取 beanClass 的全限定类名 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); } // 注册 beanDefinition parserContext.getRegistry().registerBeanDefinition(id, beanDefinition); // 为 beanDefinition 添加 id 属性 beanDefinition.getPropertyValues().addPropertyValue("id", id); } // 如果当前 beanClass 类型为 ProtocolConfig // 遍历已经注册过的 bean 对象,如果 bean 对象含有 protocol 属性 // protocol 属性值为 ProtocolConfig 实例且 name 和当前 id 值一致,为当前 beanClass 对象添加 protocl 属性 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)); } } } } else if (ServiceBean.class.equals(beanClass)) { // 如果当前元素包含 class 属性,调用 ReflectUtils.forName() 方法加载类对象 // 调用 parseProperties 解析其他属性设置到 classDefinition 对象中 // 最后设置 beanDefinition 的 ref 属性为 BeanDefinitionHolder 包装类 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")); } } 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); } 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 propertyName = name.substring(3, 4).toLowerCase() + name.substring(4); String property = StringUtils.camelToSplitName(propertyName, "-"); 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) { // 如果属性为 registry,且 registry 属性的值为"N/A",标识不会注册到任何注册中心 // 新建 RegistryConfig 并将其设置为 beanDefinition 的 registry 属性 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)) { // type 为方法参数,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)) { // 新老版本 xsd 兼容性处理 // backward compatibility for the default value in old version's xsd 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()))) { // 如果 protocol 属性值有对应的扩展实现,而且没有被注册到 spring 注册表中 // 或者 spring 注册表中对应的 bean 的类型不为 ProtocolConfig.class 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; } 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 { // 如果 ref 属性值已经被注册到 spring 注册表中 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(propertyName, 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; } 复制代码
上面这一大段关于配置的解析的代码需要大家自己结合实际的代码进行调试才能更好的理解。我在理解 Dubbo XML 解析的时候,也是耐着性子一遍一遍的来。
关于 ProtocolConfig 和 protocol 加载先后顺序的问题最后再集合一个小例子总结下吧:
dubbo-demo-provider.xml <dubbo:protocol name="dubbo" port="20880"/> 复制代码