叨叨
今天考慮了很久要不要寫這篇文章莫其。
距離《Dubbo源碼》系列的開篇到現(xiàn)在已經(jīng)快兩個月時間了。當時是想著工作上的RPC框架使用存在一些讓人頭疼的問題少辣,就來看看Dubbo給出了一套什么樣的解決方案讲婚。
結(jié)果另锋,寫完第一篇沒幾天总处,工作上因為要趕一個項目的進度,關(guān)小黑屋了睛蛛,前段時間剛放出來-_-!
琢磨著鹦马,做事不能半途而廢。今天就又打開了Dubbo項目忆肾,pull下代碼荸频,在十多個子模塊之間來回滾動,感覺都不是好惹的客冈,一時不知道從哪下手了旭从。再一想,Dubbo源碼系列不能就這么唐突的出一篇就結(jié)束了啊。
行和悦,思來想去退疫,還是接著從上篇提到的dubbo-demo模塊繼續(xù)往下說……
正文
下面是dubbo-demo-provider模塊下的dubbo-demo-provider.xml配置文件內(nèi)容
<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在上篇已經(jīng)說明,有過改動鸽素,這里使用的是zookeeper的配置
dubbo-demo-provider與常見的xml文件有何不同
1褒繁、除了<bean id="demoService" ...
其他的標簽我們?nèi)粘6紱]有使用過
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的代碼谎替,就會發(fā)現(xiàn),Dubbo也是基于Spring開發(fā)的蹋辅,使用了Spring的很多特性钱贯,但是鑒于自己的業(yè)務(wù)框架需求,需要做相應的拓展和定制化侦另,實現(xiàn)一套自己的自定義XML標簽秩命。那么這些標簽又是如何生效和被使用的呢
基于Spring的Schema提供自定義配置支持
在dubbo-demo-provider.xml中見到的那些標簽也是基于Spring的Schema實現(xiàn)的一套自定義標簽。
這一套流程主要包括以下幾個步驟:
編寫配置類和屬性
編寫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同級目錄下殿托,還包括其他出現(xiàn)在dubbo-demo-provider.xml中出現(xiàn)自定義標簽類霹菊,如RegistryConfig、ProtocolConfig
注意:<dubbo:application name="demo-provider"/>
標簽中的dubbo對應的聲明在dubbo-demo-provider.xml中的xmlns:dubbo="http://dubbo.apache.org/schema/dubbo
支竹,這里的xmlns其實就是一個命名空間的概念旋廷。
編寫XSD文件
XSD文件已經(jīng)在dubbo-demo-provider文件中定義好了,dubbo.xsd在dubbo-config-spring模塊下礼搁,內(nèi)容較長饶碘,舉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類的屬性是一一對應的關(guān)系馒吴。這里表示有一個屬性名為name扎运,且是String類型瑟曲,必填字段。
編寫spring.handlers和spring.schemas
僅僅有上面的配置類和XSD文件還是無法讓自定義標簽工作豪治,因為Spring還無法發(fā)現(xiàn)這些自定義標簽洞拨,更別提讓其發(fā)揮該有的作用了。
這時候鬼吵,需要添加兩個配置文件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類芭梯,該類實現(xiàn)了Spring的BeanDefinitionParser接口险耀,該類的一個核心方法就是parse()方法,其抽絲剝繭解析標簽玖喘,加載bean的思路其實和之前在《Spring讀書筆記》系列中介紹的一樣甩牺。最終都是解析并轉(zhuǎn)化為BeanDefinition對象并塞到Spring的上下文中,完成Bean的加載累奈。
我們可以以debug模式啟動dubbo-demo-provider模塊中的Provider類贬派,通過打斷點,會發(fā)現(xiàn)首先會執(zhí)行DubboNamespaceHandler類中的init方法澎媒,然后進入DubboBeanDefinitionParser類中的parse方法搞乏。
配合dubbo-demo-provider.xml配置文件中的<dubbo:registry address="zookeeper://127.0.0.1:2181"/>
,我們在調(diào)試的時候發(fā)現(xiàn)解析后對應的BeanDefinition如下
通過這樣一個過程戒努,就實現(xiàn)了將XML自定義的標簽加載到Spring容器中请敦,而不需要使用Spring自己的bean去定義。
明白了這個流程储玫,后面看Dubbo的其他配置文件里面那些陌生的標簽就不會蒙圈了侍筛。