今天我們來談談 Dubbo XML 配置相關內容。關於這部分內容我打算分為以下幾個部分進行介紹: Dubbo XML Spring 自定義 XML 標簽解析 Dubbo 自定義 XML 標簽解析 DubboBeanDefinitionParser.parse() End 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 自定義標簽實現作詳細分析。
- 定義 xsd 文件
XSD(XML Schemas Definition) 即 XML 結構定義。我們通過 XSD 文件不僅可以定義新的元素和屬性,同時也使用它對我們的 XML 文件規範進行約束。
在 Dubbo 項目中可以找類似實現:dubbo.xsd - 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
- 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 標簽
終於進入到本文的重頭戲環節了。在介紹 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));
}
}
這個方法幹了三件事
- 獲取 element 元素的 namespaceURI,並據此獲取對應的 NamespaceHandler 對象。Dubbo 自定義標簽(比如 Dubbo:provider) namespaceUri 的值為
http://dubbo.apache.org/schema/dubbo
; - 根據 step1 獲取到的 namespaceUri ,獲取對應的 NamespaceHandler 對象。這裡會調用 DefaultNamespaceHandlerResolver 類的 resolve() 方法,我們下麵會分析;
- 調用 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"/>
- 當我們先解析了 ProtocolConfig 元素時,我們會遍歷所有已經註冊 spring 註冊表中 bean。如果 bean 對象存在 protocol 屬性且與 name 和當前 ProtolConfig id 匹配,則會新建 RuntimeBeanReference 對象覆蓋 protocol 屬性。對於上面這行配置,最後會新建一個擁有 name 和 port 的 beanDefinition 對象。
- 先解析了 protocol 元素,ProtocolConfig 未被解析。此時我們在 spring 註冊表中找不到對應的 ProtocolConfig bean。此時我們將需要新建一個 ProtocolConfig 並將其 name 屬性
設置為當前屬性值。最後將其設置為 beanDefinition 對象的 protocol 屬性。後面載入到了 ProtocolConfig 元素時,會替換 protocol 的值。
End
Dubbo 對於自定義 XML 標簽的定義和解析實際上藉助了 Spring 框架對自定義 XML 標簽的支持。本篇水文雖然又臭又長,但是對於理解 Dubbo 的初始化過程還是很重要的。後面我們會介紹關於 Dubbo 服務暴露相關內容。
本BLOG上原創文章未經本人許可,不得用於商業用途及傳統媒體。網路媒體轉載請註明出處,否則屬於侵權行為。
https://juejin.im/post/5c1753b65188250850604ebe