叨叨
今天考慮了很久要不要寫這篇文章。
距離《Dubbo源碼》系列的開篇到現在已經快兩個月時間了。當時是想著工作上的RPC框架使用存在一些讓人頭疼的問題,就來看看Dubbo給出了一套什么樣的解決方案。
結果,寫完第一篇沒幾天,工作上因為要趕一個項目的進度,關小黑屋了,前段時間剛放出來-_-!
琢磨著,做事不能半途而廢。今天就又打開了Dubbo項目,pull下代碼,在十多個子模塊之間來回滾動,感覺都不是好惹的,一時不知道從哪下手了。再一想,Dubbo源碼系列不能就這么唐突的出一篇就結束了啊。
行,思來想去,還是接著從上篇提到的dubbo-demo模塊繼續往下說……
正文
下面是dubbo-demo-provider模塊下的dubbo-demo-provider.xml配置文件內容
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(255, 255, 255); color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo; font-size: 9pt;"><?xml version="1.0" encoding="UTF-8"?> <!--
Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <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 zookeeper registry center to export service -->
<dubbo:registry address="zookeeper://127.0.0.1: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="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>
<!-- declare the service interface to be exported -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
</beans></pre>
注意:這里的dubbo:registry在上篇已經說明,有過改動,這里使用的是zookeeper的配置
dubbo-demo-provider與常見的xml文件有何不同
1、除了<bean id="demoService" ...
其他的標簽我們日常都沒有使用過
2、<beans>標簽中除了常見的“http://www.springframework.org/schema/beans/spring-beans-4.3.xsd”還多了“http://dubbo.apache.org/schema/dubbo/dubbo.xsd”
我們平常使用的xml都是在Spring框架下,所以可以看到熟悉的<beans>、<bean> 、<import>等。那有沒有想過,為什么定義一個<bean>標簽就是生命一個bean,就能夠在Spring上下文注冊一個類的實例呢?其實,這些工作Spring在幕后都幫我們做好了,這個我在之前的《Spring讀書筆記》系列有著重寫過。
稍稍掃一眼Dubbo的代碼,就會發現,Dubbo也是基于Spring開發的,使用了Spring的很多特性,但是鑒于自己的業務框架需求,需要做相應的拓展和定制化,實現一套自己的自定義XML標簽。那么這些標簽又是如何生效和被使用的呢
基于Spring的Schema提供自定義配置支持
在dubbo-demo-provider.xml中見到的那些標簽也是基于Spring的Schema實現的一套自定義標簽。
這一套流程主要包括以下幾個步驟:
編寫配置類和屬性
編寫XSD文件
編寫spring.handlers和spring.schemas
編寫DubboNamespaceHandler和DubboBeanDefinitionParser,主要負責標簽解析
編寫配置類和屬性
針對dubbo-demo-provider中的<dubbo:application name="demo-provider"/>
來說,該標簽對應的配置類在dubbo-config-api模塊下的ApplicationConfig。
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(255, 255, 255); color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo; font-size: 9pt;">package com.alibaba.dubbo.config;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.alibaba.dubbo.config.support.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* ApplicationConfig * * @export
*/ public class ApplicationConfig extends AbstractConfig {
private static final long serialVersionUID = 5508512956753757169L;
// application name
private String name;
// module version
private String version;
// application owner
private String owner;
// application's organization (BU)
private String organization;
// architecture layer
private String architecture;
// environment, e.g. dev, test or production
private String environment;
// Java compiler
private String compiler;
// logger
private String logger;
// registry centers
private List<RegistryConfig> registries;
// monitor center
private MonitorConfig monitor;
// is default or not
private Boolean isDefault;
// directory for saving thread dump
private String dumpDirectory;
private Boolean qosEnable;
private Integer qosPort;
private Boolean qosAcceptForeignIp;
// customized parameters
private Map<String, String> parameters;
public ApplicationConfig() {
}
public ApplicationConfig(String name) {
setName(name);
}
@Parameter(key = Constants.APPLICATION_KEY, required = true)
public String getName() {
return name;
}
public void setName(String name) {
checkName("name", name);
this.name = name;
if (id == null || id.length() == 0) {
id = name;
}
}
@Parameter(key = "application.version")
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
checkMultiName("owner", owner);
this.owner = owner;
}
public String getOrganization() {
return organization;
}
public void setOrganization(String organization) {
checkName("organization", organization);
this.organization = organization;
}
public String getArchitecture() {
return architecture;
}
public void setArchitecture(String architecture) {
checkName("architecture", architecture);
this.architecture = architecture;
}
public String getEnvironment() {
return environment;
}
public void setEnvironment(String environment) {
checkName("environment", environment);
if (environment != null) {
if (!("develop".equals(environment) || "test".equals(environment) || "product".equals(environment))) {
throw new IllegalStateException("Unsupported environment: " + environment + ", only support develop/test/product, default is product.");
}
}
this.environment = environment;
}
public RegistryConfig getRegistry() {
return registries == null || registries.isEmpty() ? null : registries.get(0);
}
public void setRegistry(RegistryConfig registry) {
List<RegistryConfig> registries = new ArrayList<RegistryConfig>(1);
registries.add(registry);
this.registries = registries;
}
public List<RegistryConfig> getRegistries() {
return registries;
}
@SuppressWarnings({"unchecked"})
public void setRegistries(List<? extends RegistryConfig> registries) {
this.registries = (List<RegistryConfig>) registries;
}
public MonitorConfig getMonitor() {
return monitor;
}
public void setMonitor(MonitorConfig monitor) {
this.monitor = monitor;
}
public void setMonitor(String monitor) {
this.monitor = new MonitorConfig(monitor);
}
public String getCompiler() {
return compiler;
}
public void setCompiler(String compiler) {
this.compiler = compiler;
AdaptiveCompiler.setDefaultCompiler(compiler);
}
public String getLogger() {
return logger;
}
public void setLogger(String logger) {
this.logger = logger;
LoggerFactory.setLoggerAdapter(logger);
}
public Boolean isDefault() {
return isDefault;
}
public void setDefault(Boolean isDefault) {
this.isDefault = isDefault;
}
@Parameter(key = Constants.DUMP_DIRECTORY)
public String getDumpDirectory() {
return dumpDirectory;
}
public void setDumpDirectory(String dumpDirectory) {
this.dumpDirectory = dumpDirectory;
}
@Parameter(key = Constants.QOS_ENABLE)
public Boolean getQosEnable() {
return qosEnable;
}
public void setQosEnable(Boolean qosEnable) {
this.qosEnable = qosEnable;
}
@Parameter(key = Constants.QOS_PORT)
public Integer getQosPort() {
return qosPort;
}
public void setQosPort(Integer qosPort) {
this.qosPort = qosPort;
}
@Parameter(key = Constants.ACCEPT_FOREIGN_IP)
public Boolean getQosAcceptForeignIp() {
return qosAcceptForeignIp;
}
public void setQosAcceptForeignIp(Boolean qosAcceptForeignIp) {
this.qosAcceptForeignIp = qosAcceptForeignIp;
}
public Map<String, String> getParameters() {
return parameters;
}
public void setParameters(Map<String, String> parameters) {
checkParameterName(parameters);
this.parameters = parameters;
}
}</pre>
在ApplicationConfig同級目錄下,還包括其他出現在dubbo-demo-provider.xml中出現自定義標簽類,如RegistryConfig、ProtocolConfig
注意:<dubbo:application name="demo-provider"/>
標簽中的dubbo對應的聲明在dubbo-demo-provider.xml中的xmlns:dubbo="http://dubbo.apache.org/schema/dubbo
,這里的xmlns其實就是一個命名空間的概念。
編寫XSD文件
XSD文件已經在dubbo-demo-provider文件中定義好了,dubbo.xsd在dubbo-config-spring模塊下,內容較長,舉Application為例
...
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(255, 255, 255); color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo; font-size: 9pt;"><xsd:element name="application" type="applicationType">
<xsd:annotation>
<xsd:documentation><![CDATA[ The application config ]]></xsd:documentation>
</xsd:annotation>
</xsd:element></pre>
...
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(255, 255, 255); color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo; font-size: 9pt;"><xsd:complexType name="applicationType">
<xsd:sequence minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="parameter" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID">
<xsd:annotation>
<xsd:documentation><![CDATA[ The unique identifier for a bean. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="name" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[ The application name. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="version" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ The application version. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="owner" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ The application owner name (email prefix). ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="organization" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ The organization name. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="architecture" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ The architecture. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="environment" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ The application environment, eg: dev/test/run ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="compiler" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ The java code compiler. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="logger" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[ The application logger. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="registry" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[ The application registry. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="monitor" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[ The application monitor. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="default" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[ Is default. ]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType></pre>
...
代碼中上面一部分代碼<xsd:element name="application" type="applicationType">
表示標簽的名稱
<xsd:attribute name="name" type="xsd:string" use="required">
表示的是application標簽的屬性,與ApplicationConfig類的屬性是一一對應的關系。這里表示有一個屬性名為name,且是String類型,必填字段。
編寫spring.handlers和spring.schemas
僅僅有上面的配置類和XSD文件還是無法讓自定義標簽工作,因為Spring還無法發現這些自定義標簽,更別提讓其發揮該有的作用了。
這時候,需要添加兩個配置文件spring.handlers和spring.schemas,從文件字面意思就可以知道,這兩個配置文件起到了貫通的作用。spring.handlers用于配置具體的解析類,下面會提到,spring.schemas用于指明schemas的文件路徑。
我們可以在dubbo-config-spring模塊中看到這兩個配置文件
Spring會在啟動容器的時候加載META-INF目錄下的這兩個配置文件并加載對應的解析類。
編寫DubboNamespaceHandler和DubboBeanDefinitionParser,主要負責標簽解析
DubboNamespaceHandler類位于dubbo-config-spring模塊下
<pre style="margin: 0.5em 0px; padding: 0.4em 0.6em; border-radius: 8px; background: rgb(255, 255, 255); color: rgb(0, 0, 0); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; font-family: Menlo; font-size: 9pt;">/*
* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.dubbo.config.spring.schema;
import com.alibaba.dubbo.common.Version;
import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ConsumerConfig;
import com.alibaba.dubbo.config.ModuleConfig;
import com.alibaba.dubbo.config.MonitorConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.ProviderConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.alibaba.dubbo.config.spring.ServiceBean;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
/**
* DubboNamespaceHandler * * @export
*/ public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
@Override
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());
}
}
</pre>
是不是又看到了之前在dubbo-demo-provider.xml配置文件中看到那些標簽。沒錯,真正給那些標簽賦能的功能代碼就在這里。
具體的解析工作交給了dubbo-config-spring模塊下的DubboBeanDefinitionParser類,該類實現了Spring的BeanDefinitionParser接口,該類的一個核心方法就是parse()方法,其抽絲剝繭解析標簽,加載bean的思路其實和之前在《Spring讀書筆記》系列中介紹的一樣。最終都是解析并轉化為BeanDefinition對象并塞到Spring的上下文中,完成Bean的加載。
我們可以以debug模式啟動dubbo-demo-provider模塊中的Provider類,通過打斷點,會發現首先會執行DubboNamespaceHandler類中的init方法,然后進入DubboBeanDefinitionParser類中的parse方法。
配合dubbo-demo-provider.xml配置文件中的<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
,我們在調試的時候發現解析后對應的BeanDefinition如下
通過這樣一個過程,就實現了將XML自定義的標簽加載到Spring容器中,而不需要使用Spring自己的bean去定義。
明白了這個流程,后面看Dubbo的其他配置文件里面那些陌生的標簽就不會蒙圈了。