【Dubbo源码阅读系列】之 Dubbo XML 配置加载

今天咱们来谈谈 Dubbo XML 配置相关内容。关于这部份内容我打算分为如下几个部分进行介绍:php

  • Dubbo XML
  • Spring 自定义 XML 标签解析
  • Dubbo 自定义 XML 标签解析
  • DubboBeanDefinitionParser.parse()
  • End

Dubbo XML

在本小节开始前咱们先来看下 Dubbo XML 配置文件示例:java

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 是如何定义并加载这些自定义标签的。node

Spring 自定义 XML 标签解析

Dubbo 中的自定义 XML 标签其实是依赖于 Spring 解析自定义标签的功能实现的。网上关于 Spring 解析自定义 XML 标签的文章也比较多,这里咱们仅介绍下实现相关功能须要的文件,给你们一个直观的印象,不去深刻的对 Spring 自定义标签实现做详细分析。spring

  1. 定义 xsd 文件
    XSD(XML Schemas Definition) 即 XML 结构定义。咱们经过 XSD 文件不只能够定义新的元素和属性,同时也使用它对咱们的 XML 文件规范进行约束。 在 Dubbo 项目中能够找相似实现:dubbo.xsd
  2. spring.schemas
    该配置文件约定了自定义命名空间和 xsd 文件之间的映射关系,用于 spring 容器感知咱们自定义的 xsd 文件位置。
http\://dubbo.apache.org/schema/dubbo/dubbo.xsd=META-INF/dubbo.xsd
http\://code.alibabatech.com/schema/dubbo/dubbo.xsd=META-INF/compat/dubbo.xsd
复制代码
  1. 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
复制代码
  1. 命名空间处理器
    命名空间处理器主要用来注册 BeanDefinitionParser 解析器。对应上面 spring.handlers 文件中的 DubboNamespaceHandler
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
    @Override
    public void init() {
        registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
        // 省略...
        registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
    }
}
复制代码
  1. BeanDefinitionParser 解析器
    实现 BeanDefinitionParser 接口中的 parse 方法,用于自定义标签的解析。Dubbo 中对应 DubboBeanDefinitionParser 类。

Dubbo 解析自定义 XML 标签

终于进入到本文的重头戏环节了。在介绍 Dubbo 自定义 XML 标签解析前,先放一张图帮助你们理解如下 Spring 是如何从 XML 文件中解析并加载 Bean 的。apache

上图言尽于 handler.parse() 方法,若是你仔细看了上文,对 parse() 应该是有印象的。
没错,在前一小结的第五点咱们介绍了 DubboBeanDefinitionParser 类。该类有个方法就叫 parse()。那么这个 parse() 方法有什么用? Spring 是如何感知到我就要调用 DubboBeanDefinitionParser 类中的 parse() 方法的呢?咱们带着这两个问题接着往下看。

BeanDefinitionParserDelegate

上面图的流程比较长,咱们先着重看下 BeanDefinitionParserDelegate 类中的几个关键方法。网络

BeanDefinitionParserDelegate.java public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    // 获取当前 element 的 namespaceURI
    // 好比 dubbo.xsd 中的为 http://dubbo.apache.org/schema/dubbo
    String namespaceUri = this.getNamespaceURI(ele);
    // 根据 URI 获取对应的 NamespaceHandler
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        this.error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    } else {
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }
}
复制代码

这个方法干了三件事app

  1. 获取 element 元素的 namespaceURI,并据此获取对应的 NamespaceHandler 对象。Dubbo 自定义标签(好比 Dubbo:provider) namespaceUri 的值为 http://dubbo.apache.org/schema/dubbo;
  2. 根据 step1 获取到的 namespaceUri ,获取对应的 NamespaceHandler 对象。这里会调用 DefaultNamespaceHandlerResolver 类的 resolve() 方法,咱们下面会分析;
  3. 调用 handler 的 parse 方法,咱们自定以的 handler 会继承 NamespaceHandlerSupport 类,因此这里调用的实际上是 NamespaceHandlerSupport 类的 parse() 方法,后文分析;

一图胜千言
在详细分析 step2 和 step3 中涉及的 resolver()parse() 方法前,先放一张时序图让你们有个基本概念:框架

DefaultNamespaceHandlerResolver.java public NamespaceHandler resolve(String namespaceUri) {
    Map<String, Object> handlerMappings = this.getHandlerMappings();
    // 以 namespaceUri 为 Key 获取对应的 handlerOrClassName
    Object handlerOrClassName = handlerMappings.get(namespaceUri);
    if (handlerOrClassName == null) {
        return null;
    } else if (handlerOrClassName instanceof NamespaceHandler) {
        return (NamespaceHandler)handlerOrClassName;
    } else {
        // 若是不为空且不为 NamespaceHandler 的实例,转换为 String 类型
        // DubboNamespaceHandler 执行的即是这段逻辑
        String className = (String)handlerOrClassName;

        try {
            Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
            // handlerClass 是否为 NamespaceHandler 的实现类,若不是则抛出异常
            if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
                throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri + "] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
            } else {
                // 初始化 handlerClass
                NamespaceHandler namespaceHandler = (NamespaceHandler)BeanUtils.instantiateClass(handlerClass);
                // 执行 handlerClass类的 init() 方法
                namespaceHandler.init();
                handlerMappings.put(namespaceUri, namespaceHandler);
                return namespaceHandler;
            }
        } catch (ClassNotFoundException var7) {
            throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" + namespaceUri + "] not found", var7);
        } catch (LinkageError var8) {
            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() 方法比较重要,咱们接着往下看。async

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() 方法。ide

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() 方法。

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"/>
复制代码
  1. 当咱们先解析了 ProtocolConfig 元素时,咱们会遍历全部已经注册 spring 注册表中 bean。若是 bean 对象存在 protocol 属性且与 name 和当前 ProtolConfig id 匹配,则会新建 RuntimeBeanReference 对象覆盖 protocol 属性。对于上面这行配置,最后会新建一个拥有 name 和 port 的 beanDefinition 对象。
  2. 先解析了 protocol 元素,ProtocolConfig 未被解析。此时咱们在 spring 注册表中找不到对应的 ProtocolConfig bean。此时咱们将须要新建一个 ProtocolConfig 并将其 name 属性 设置为当前属性值。最后将其设置为 beanDefinition 对象的 protocol 属性。后面加载到了 ProtocolConfig 元素时,会替换 protocol 的值。

End

Dubbo 对于自定义 XML 标签的定义和解析实际上借助了 Spring 框架对自定义 XML 标签的支持。本篇水文虽然又臭又长,可是对于理解 Dubbo 的初始化过程仍是很重要的。后面咱们会介绍关于 Dubbo 服务暴露相关内容。

本BLOG上原创文章未经本人许可,不得用于商业用途及传统媒体。网络媒体转载请注明出处,不然属于侵权行为。https://juejin.im/post/5c1753b65188250850604ebe