【Dubbo源碼閱讀系列】之 Dubbo XML 配置加載

今天我們來談談 Dubbo XML 配置相關內容。關於這部分內容我打算分爲以下幾個部分進行介紹:

  • Dubbo XML
  • Spring 自定義 XML 標籤解析
  • Dubbo 自定義 XML 標籤解析
  • DubboBeanDefinitionParser.parse()
  • End

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 是如何定義並加載這些自定義標籤的。

Spring 自定義 XML 標籤解析

Dubbo 中的自定義 XML 標籤實際上是依賴於 Spring 解析自定義標籤的功能實現的。網上關於 Spring 解析自定義 XML 標籤的文章也比較多,這裏我們僅介紹下實現相關功能需要的文件,給大家一個直觀的印象,不去深入的對 Spring 自定義標籤實現作詳細分析。

  1. 定義 xsd 文件

XSD(XML Schemas Definition) 即 XML 結構定義。我們通過 XSD 文件不僅可以定義新的元素和屬性,同時也使用它對我們的 XML 文件規範進行約束。
在 Dubbo 項目中可以找類似實現:dubbo.xsd

  1. 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 的。


上圖言盡於 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));
    }
}

這個方法幹了三件事

  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() 方法比較重要,我們接着往下看。

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

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章