今天咱們來談談 Dubbo XML 配置相關內容。關於這部份內容我打算分爲如下幾個部分進行介紹:java
在本小節開始前咱們先來看下 Dubbo XML 配置文件示例:node
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
Dubbo 中的自定義 XML 標籤其實是依賴於 Spring 解析自定義標籤的功能實現的。網上關於 Spring 解析自定義 XML 標籤的文章也比較多,這裏咱們僅介紹下實現相關功能須要的文件,給你們一個直觀的印象,不去深刻的對 Spring 自定義標籤實現做詳細分析。apache
XSD(XML Schemas Definition) 即 XML 結構定義。咱們經過 XSD 文件不只能夠定義新的元素和屬性,同時也使用它對咱們的 XML 文件規範進行約束。
在 Dubbo 項目中能夠找相似實現:dubbo.xsd網絡
該配置文件約定了自定義命名空間和 xsd 文件之間的映射關係,用於 spring 容器感知咱們自定義的 xsd 文件位置。app
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
該配置文件約定了自定義命名空間和 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 文件中的 DubboNamespaceHandlerasync
public class DubboNamespaceHandler extends NamespaceHandlerSupport { @Override public void init() { registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true)); // 省略... registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser()); } }
實現 BeanDefinitionParser 接口中的 parse 方法,用於自定義標籤的解析。Dubbo 中對應 DubboBeanDefinitionParser 類。ide
終於進入到本文的重頭戲環節了。在介紹 Dubbo 自定義 XML 標籤解析前,先放一張圖幫助你們理解如下 Spring 是如何從 XML 文件中解析並加載 Bean 的。post
上圖言盡於 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"/>
Dubbo 對於自定義 XML 標籤的定義和解析實際上藉助了 Spring 框架對自定義 XML 標籤的支持。本篇水文雖然又臭又長,可是對於理解 Dubbo 的初始化過程仍是很重要的。後面咱們會介紹關於 Dubbo 服務暴露相關內容。
本BLOG上原創文章未經本人許可,不得用於商業用途及傳統媒體。網絡媒體轉載請註明出處,不然屬於侵權行爲。
https://juejin.im/post/5c1753b65188250850604ebe