介紹
本篇文章,我們來研究一下 Dubbo 導(dǎo)出服務(wù)的過程。Dubbo 服務(wù)導(dǎo)出過程始于 Spring 容器發(fā)布刷新事件,Dubbo 在接收到事件后踏枣,會立即執(zhí)行服務(wù)導(dǎo)出邏輯。整個邏輯大致可分為三個部分:
- 第一部分是前置工作钙蒙,主要用于檢查參數(shù),組裝 URL间驮。
- 第二部分是導(dǎo)出服務(wù)躬厌,包含導(dǎo)出服務(wù)到本地 (JVM),和導(dǎo)出服務(wù)到遠(yuǎn)程兩個過程竞帽。
- 第三部分是向注冊中心注冊服務(wù)扛施,用于服務(wù)發(fā)現(xiàn)。
1. 第一部分- 導(dǎo)出服務(wù)前置工作
Dubbo 導(dǎo)出服務(wù)的入口class是com.alibaba.dubbo.config.spring.ServiceBean
屹篓,這個類是spring通過解析<dubbo:service>
節(jié)點創(chuàng)建的單例Bean疙渣,每一個<dubbo:service>
都會創(chuàng)建一個ServiceBean
,ServiceBean
的類圖如下:
ServiceBean
的聲明為:
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware
可以看出ServiceBean
實現(xiàn)了spring的InitializingBean
堆巧、DisposableBean
妄荔、ApplicationContextAware
泼菌、ApplicationListener
和BeanNameAware
接口,關(guān)于這幾個接口的使用在前面已經(jīng)提到了啦租,這里不再說明哗伯。
Dubbo 支持兩種服務(wù)導(dǎo)出方式,分別延遲導(dǎo)出和立即導(dǎo)出篷角。延遲導(dǎo)出的入口是 ServiceBean
的 afterPropertiesSet
方法焊刹,立即導(dǎo)出的入口是 ServiceBean
的onApplicationEvent
方法。
我們先看一下afterPropertiesSet
方法恳蹲,這個方法會在ServiceBean
創(chuàng)建前調(diào)用虐块。
@Override
@SuppressWarnings({"unchecked", "deprecation"})
public void afterPropertiesSet() throws Exception {
// 初始化 provider
if (getProvider() == null) {
// 讀取 spring applicationContext 的 ProviderConfig
Map<String, ProviderConfig> providerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class, false, false);
if (providerConfigMap != null && providerConfigMap.size() > 0) {
// 讀取 spring applicationContext 的 ProtocolConfig
Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
if ((protocolConfigMap == null || protocolConfigMap.size() == 0)
&& providerConfigMap.size() > 1) {
List<ProviderConfig> providerConfigs = new ArrayList<ProviderConfig>();
for (ProviderConfig config : providerConfigMap.values()) {
if (config.isDefault() != null && config.isDefault().booleanValue()) {
providerConfigs.add(config);
}
}
if (!providerConfigs.isEmpty()) {
setProviders(providerConfigs);
}
} else {
ProviderConfig providerConfig = null;
for (ProviderConfig config : providerConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (providerConfig != null) {
throw new IllegalStateException("Duplicate provider configs: " + providerConfig + " and " + config);
}
providerConfig = config;
}
}
if (providerConfig != null) {
setProvider(providerConfig);
}
}
}
}
// 初始化 application
if (getApplication() == null
&& (getProvider() == null || getProvider().getApplication() == null)) {
// 讀取 spring applicationContext 的 ApplicationConfig
Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
ApplicationConfig applicationConfig = null;
for (ApplicationConfig config : applicationConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (applicationConfig != null) {
throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
}
applicationConfig = config;
}
}
if (applicationConfig != null) {
setApplication(applicationConfig);
}
}
}
// 初始化 module
if (getModule() == null
&& (getProvider() == null || getProvider().getModule() == null)) {
// 讀取 spring applicationContext 的 ModuleConfig
Map<String, ModuleConfig> moduleConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ModuleConfig.class, false, false);
if (moduleConfigMap != null && moduleConfigMap.size() > 0) {
ModuleConfig moduleConfig = null;
for (ModuleConfig config : moduleConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (moduleConfig != null) {
throw new IllegalStateException("Duplicate module configs: " + moduleConfig + " and " + config);
}
moduleConfig = config;
}
}
if (moduleConfig != null) {
setModule(moduleConfig);
}
}
}
// 初始化 Registries
if ((getRegistries() == null || getRegistries().isEmpty())
&& (getProvider() == null || getProvider().getRegistries() == null || getProvider().getRegistries().isEmpty())
&& (getApplication() == null || getApplication().getRegistries() == null || getApplication().getRegistries().isEmpty())) {
// 讀取 spring applicationContext 的 RegistryConfig
Map<String, RegistryConfig> registryConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class, false, false);
if (registryConfigMap != null && registryConfigMap.size() > 0) {
List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
for (RegistryConfig config : registryConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
registryConfigs.add(config);
}
}
if (registryConfigs != null && !registryConfigs.isEmpty()) {
super.setRegistries(registryConfigs);
}
}
}
// 初始化 Monitor
if (getMonitor() == null
&& (getProvider() == null || getProvider().getMonitor() == null)
&& (getApplication() == null || getApplication().getMonitor() == null)) {
// 讀取 spring applicationContext 的 MonitorConfig
Map<String, MonitorConfig> monitorConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MonitorConfig.class, false, false);
if (monitorConfigMap != null && monitorConfigMap.size() > 0) {
MonitorConfig monitorConfig = null;
for (MonitorConfig config : monitorConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
if (monitorConfig != null) {
throw new IllegalStateException("Duplicate monitor configs: " + monitorConfig + " and " + config);
}
monitorConfig = config;
}
}
if (monitorConfig != null) {
setMonitor(monitorConfig);
}
}
}
// 初始化 Protocols
if ((getProtocols() == null || getProtocols().isEmpty())
&& (getProvider() == null || getProvider().getProtocols() == null || getProvider().getProtocols().isEmpty())) {
// 讀取 spring applicationContext 的 ProtocolConfig
Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
if (protocolConfigMap != null && protocolConfigMap.size() > 0) {
List<ProtocolConfig> protocolConfigs = new ArrayList<ProtocolConfig>();
for (ProtocolConfig config : protocolConfigMap.values()) {
if (config.isDefault() == null || config.isDefault().booleanValue()) {
protocolConfigs.add(config);
}
}
if (protocolConfigs != null && !protocolConfigs.isEmpty()) {
super.setProtocols(protocolConfigs);
}
}
}
// 初始化 Path
if (getPath() == null || getPath().length() == 0) {
if (beanName != null && beanName.length() > 0
&& getInterface() != null && getInterface().length() > 0
&& beanName.startsWith(getInterface())) {
setPath(beanName);
}
}
// 延遲導(dǎo)出,這里不做講解
if (!isDelay()) {
export();
}
}
afterPropertiesSet
檢查ServiceBean的某個屬性(這里的屬性包含如下6個)是否為空嘉蕾,如果為空贺奠,從applicationContext獲取相應(yīng)類型的bean,如果獲取到了荆针,則進(jìn)行相應(yīng)的設(shè)置敞嗡。6個屬性如下:
-
ProviderConfig provider
:其實就是看有沒有配置<dubbo:provider> -
ApplicationConfig application
:其實就是看有沒有配置<dubbo:application> -
ModuleConfig module
:其實就是看有沒有配置<dubbo:module> -
List<RegistryConfig> registries
:其實就是看有沒有配置<dubbo:registry> -
MonitorConfig monitor
:其實就是看有沒有配置<dubbo:monitor> -
List<ProtocolConfig> protocols
:其實就是看有沒有配置<dubbo:protocol> -
String path
:服務(wù)名稱
填充完后,有一個重要的方法就是export()
航背。這個方法會判斷延遲的時間是否大于0喉悴,如果是,才會執(zhí)行玖媚。這里如果我們沒有設(shè)置延遲箕肃,一個ServiceBean實例就完成了。
接下來我們看看ServiceBean實例完成后今魔,私有屬性的值都是啥:
ServiceBean={
"beanName":"com.hui.wang.dubbo.learn.api.MyService",
"supportedApplicationListener":"true",
"interfaceName":"com.hui.wang.dubbo.learn.api.MyService",
"ref":interfaceName的實例,
"path":"com.hui.wang.dubbo.learn.api.MyService",
"verision":"1.0",
"application":{
"name":"provider",
"owner":"hui.wang"
"organization":"dubbo-learn",
"id":"provider"
},
registries:[
"address":"127.0.0.1:2181",
"protocol":"zookeeper",
"group":"dubbo-learn",
"id":"dubbo-provider"
],
"id":"com.hui.wang.dubbo.learn.api.MyService"
}
實際上在創(chuàng)建ServiceBean實例的時候勺像,也會初始化其父類ServiceConfig的靜態(tài)屬性:
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
這里使用了Adaptive,之前有過講解错森,會生成對應(yīng)的Protocol$Adaptive
實例和ProxyFactory$Adaptive
實例吟宦。對應(yīng)的源碼如下:
Protocol$Adaptive
:
public class Protocol$Adaptive implements Protocol {
@Override
public void destroy() {
throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
@Override
public int getDefaultPort() {
throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public Invoker refer(Class class_, URL uRL) throws RpcException {
String string;
if (uRL == null) {
throw new IllegalArgumentException("url == null");
}
URL uRL2 = uRL;
String string2 = string = uRL2.getProtocol() == null ? "dubbo" : uRL2.getProtocol();
if (string == null) {
throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(").append(uRL2.toString()).append(") use keys([protocol])").toString());
}
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(string);
return protocol.refer(class_, uRL);
}
public Exporter export(Invoker invoker) throws RpcException {
String string;
if (invoker == null) {
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
}
if (invoker.getUrl() == null) {
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
}
URL uRL = invoker.getUrl();
String string2 = string = uRL.getProtocol() == null ? "dubbo" : uRL.getProtocol();
if (string == null) {
throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(").append(uRL.toString()).append(") use keys([protocol])").toString());
}
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(string);
return protocol.export(invoker);
}
}
ProxyFactory$Adaptive
:
public class ProxyFactory$Adaptive implements ProxyFactory {
public Invoker getInvoker(Object object, Class class_, URL uRL) throws RpcException {
if (uRL == null) {
throw new IllegalArgumentException("url == null");
}
URL uRL2 = uRL;
String string = uRL2.getParameter("proxy", "javassist");
if (string == null) {
throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(").append(uRL2.toString()).append(") use keys([proxy])").toString());
}
ProxyFactory proxyFactory = (ProxyFactory) ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(string);
return proxyFactory.getInvoker(object, class_, uRL);
}
public Object getProxy(Invoker invoker, boolean bl) throws RpcException {
if (invoker == null) {
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
}
if (invoker.getUrl() == null) {
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
}
URL uRL = invoker.getUrl();
String string = uRL.getParameter("proxy", "javassist");
if (string == null) {
throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(").append(uRL.toString()).append(") use keys([proxy])").toString());
}
ProxyFactory proxyFactory = (ProxyFactory) ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(string);
return proxyFactory.getProxy(invoker, bl);
}
public Object getProxy(Invoker invoker) throws RpcException {
if (invoker == null) {
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
}
if (invoker.getUrl() == null) {
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
}
URL uRL = invoker.getUrl();
String string = uRL.getParameter("proxy", "javassist");
if (string == null) {
throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(").append(uRL.toString()).append(") use keys([proxy])").toString());
}
ProxyFactory proxyFactory = (ProxyFactory) ExtensionLoader.getExtensionLoader(ProxyFactory.class).getExtension(string);
return proxyFactory.getProxy(invoker);
}
}
到這里,整個ServiceBean的初始化過程已經(jīng)講解完成了涩维。接下來我們看看ServiceBean#onApplicationEvent
方法殃姓,改方法會在 Spring 上下文刷新事件時調(diào)用。代碼如下:
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 是否有延遲導(dǎo)出 && 是否已導(dǎo)出 && 是不是已被取消導(dǎo)出
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
這個方法首先會根據(jù)條件決定是否導(dǎo)出服務(wù)瓦阐,比如有些服務(wù)設(shè)置了延時導(dǎo)出蜗侈,那么此時就不應(yīng)該在此處導(dǎo)出。還有一些服務(wù)已經(jīng)被導(dǎo)出了睡蟋,或者當(dāng)前服務(wù)被取消導(dǎo)出了踏幻,此時也不能再次導(dǎo)出相關(guān)服務(wù)。注意這里的 isDelay 方法戳杀,這個方法字面意思是“是否延遲導(dǎo)出服務(wù)”该面,返回 true 表示延遲導(dǎo)出夭苗,false 表示不延遲導(dǎo)出。但是該方法真實意思卻并非如此吆倦,當(dāng)方法返回 true 時听诸,表示無需延遲導(dǎo)出。返回 false 時蚕泽,表示需要延遲導(dǎo)出晌梨。
接下來對服務(wù)導(dǎo)出的前置邏輯進(jìn)行分析。
配置檢查以及 URL 裝配
在導(dǎo)出服務(wù)之前须妻,Dubbo 需要檢查用戶的配置是否合理仔蝌,或者為用戶補(bǔ)充缺省配置。配置檢查完成后荒吏,接下來需要根據(jù)這些配置組裝 URL敛惊。
- 檢查配置,本節(jié)我們接著前面的源碼向下分析绰更,前面說過 onApplicationEvent 方法在經(jīng)過一些判斷后瞧挤,會決定是否調(diào)用 export 方法導(dǎo)出服務(wù)。那么下面我們從 export 方法開始進(jìn)行分析儡湾,如下:
public synchronized void export() {
if (provider != null) {
// 獲取 export 和 delay 配置
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
// 如果 export 為 false特恬,則不導(dǎo)出服務(wù)
if (export != null && !export) {
return;
}
// delay > 0,延時導(dǎo)出服務(wù)
if (delay != null && delay > 0) {
delayExportExecutor.schedule(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
// 立即導(dǎo)出服務(wù)
} else {
doExport();
}
}
export
方法分別對export
和delay
配置進(jìn)行檢查徐钠,首先是 export 配置癌刽,這個配置決定了是否導(dǎo)出服務(wù)。有時候我們只是想本地啟動服務(wù)進(jìn)行一些調(diào)試工作尝丐,我們并不希望把本地啟動的服務(wù)暴露出去給別人調(diào)用显拜。此時,我們可通過配置 export 禁止服務(wù)導(dǎo)出爹袁,比如:
<dubbo:provider export="false" />
delay 配置顧名思義远荠,用于延遲導(dǎo)出服務(wù),這個就不分析了失息。
- 下面矮台,我們繼續(xù)分析源碼,這次要分析的是 doExport 方法根时。
protected synchronized void doExport() {
// unexported 和 exported 檢查
if (unexported) {
throw new IllegalStateException("Already unexported!");
}
if (exported) {
return;
}
exported = true;
// 檢測 interfaceName 是否合法
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
}
// 檢測 provider 是否為空,為空則新建一個辰晕,并通過系統(tǒng)變量為其初始化
checkDefault();
// 下面幾個 if 語句用于檢測 provider蛤迎、application 等核心配置類對象是否為空,
// 若為空含友,則嘗試從其他配置類對象中獲取相應(yīng)的實例替裆。
if (provider != null) {
if (application == null) {
application = provider.getApplication();
}
if (module == null) {
module = provider.getModule();
}
if (registries == null) {
registries = provider.getRegistries();
}
if (monitor == null) {
monitor = provider.getMonitor();
}
if (protocols == null) {
protocols = provider.getProtocols();
}
}
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor();
}
}
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {
monitor = application.getMonitor();
}
}
// 檢測 ref 是否為泛化服務(wù)類型
if (ref instanceof GenericService) {
// 設(shè)置 interfaceClass 為 GenericService.class
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
// 設(shè)置 generic = "true"
generic = Boolean.TRUE.toString();
}
// ref 非 GenericService 類型
} else {
try {
// 設(shè)置 interfaceClass
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
// 對 interfaceClass校辩,以及 <dubbo:method> 標(biāo)簽中的必要字段進(jìn)行檢查
checkInterfaceAndMethods(interfaceClass, methods);
// 對 ref 合法性進(jìn)行檢測
checkRef();
// 設(shè)置 generic = "false"
generic = Boolean.FALSE.toString();
}
// local 和 stub 在功能應(yīng)該是一致的,用于配置本地存根
if (local != null) {
if ("true".equals(local)) {
local = interfaceName + "Local";
}
Class<?> localClass;
try {
// 獲取本地存根類
localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
// 檢測本地存根類是否可賦值給接口類辆童,若不可賦值則會拋出異常宜咒,提醒使用者本地存根類類型不合法
if (!interfaceClass.isAssignableFrom(localClass)) {
throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
}
}
if (stub != null) {
if ("true".equals(stub)) {
stub = interfaceName + "Stub";
}
Class<?> stubClass;
try {
stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(stubClass)) {
throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
}
}
// 檢測各種對象是否為空,為空則新建把鉴,或者拋出異常
checkApplication();
checkRegistry();
checkProtocol();
appendProperties(this);
checkStub(interfaceClass);
checkMock(interfaceClass);
if (path == null || path.length() == 0) {
path = interfaceName;
}
// 導(dǎo)出服務(wù)
doExportUrls();
// ProviderModel 表示服務(wù)提供者模型故黑,此對象中存儲了與服務(wù)提供者相關(guān)的信息。
// 比如服務(wù)的配置信息庭砍,服務(wù)實例等场晶。每個被導(dǎo)出的服務(wù)對應(yīng)一個 ProviderModel。
// ApplicationModel 持有所有的 ProviderModel怠缸。
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
以上就是配置檢查的相關(guān)分析诗轻,代碼比較多,需要大家耐心看一下揭北。下面對配置檢查的邏輯進(jìn)行簡單的總結(jié)扳炬,如下:
- 檢測 <dubbo:service> 標(biāo)簽的 interface 屬性合法性,不合法則拋出異常
- 檢測 ProviderConfig搔体、ApplicationConfig 等核心配置類對象是否為空恨樟,若為空,則嘗試從其他配置類對象中獲取相應(yīng)的實例嫉柴。
- 檢測并處理泛化服務(wù)和普通服務(wù)類
- 檢測本地存根配置厌杜,并進(jìn)行相應(yīng)的處理
- 對 ApplicationConfig、RegistryConfig 等配置類進(jìn)行檢測计螺,為空則嘗試創(chuàng)建夯尽,若無法創(chuàng)建則拋出異常
配置檢查并非本文重點,因此這里不打算對 doExport 方法所調(diào)用的方法進(jìn)行分析(doExportUrls 方法除外)登馒。在這些方法中匙握,除了 appendProperties 方法稍微復(fù)雜一些,其他方法邏輯不是很復(fù)雜陈轿。因此圈纺,大家可自行分析。
-
doExportUrls
方法麦射,Dubbo 允許我們使用不同的協(xié)議導(dǎo)出服務(wù)蛾娶,也允許我們向多個注冊中心注冊服務(wù)。相關(guān)代碼如下:
private void doExportUrls() {
// 加載注冊中心鏈接
List<URL> registryURLs = loadRegistries(true);
// 遍歷 protocols潜秋,并在每個協(xié)議下導(dǎo)出服務(wù)
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
上面代碼首先是通過 loadRegistries
加載注冊中心鏈接蛔琅,然后再遍歷 ProtocolConfig
集合導(dǎo)出每個服務(wù)。并在導(dǎo)出服務(wù)的過程中峻呛,將服務(wù)注冊到注冊中心罗售。下面辜窑,我們先來看一下loadRegistries
方法的邏輯。
protected List<URL> loadRegistries(boolean provider) {
// 檢測是否存在注冊中心配置類寨躁,不存在則拋出異常
checkRegistry();
List<URL> registryList = new ArrayList<URL>();
if (registries != null && !registries.isEmpty()) {
for (RegistryConfig config : registries) {
// 獲取注冊中心address
String address = config.getAddress();
if (address == null || address.length() == 0) {
// 若 address 為空穆碎,則將其設(shè)為 0.0.0.0
address = Constants.ANYHOST_VALUE;
}
// 從系統(tǒng)屬性中加載注冊中心地址
String sysaddress = System.getProperty("dubbo.registry.address");
// 如果系統(tǒng)屬性中的配置中心地址不為空,將注冊地址設(shè)置為系統(tǒng)屬性配置中的地址
if (sysaddress != null && sysaddress.length() > 0) {
address = sysaddress;
}
if (address.length() > 0 && !RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
// 添加 ApplicationConfig 中的字段信息到 map 中
appendParameters(map, application);
// 添加 RegistryConfig 字段信息到 map 中
appendParameters(map, config);
// 添加 path职恳、pid所禀,protocol 等信息到 map 中
map.put("path", RegistryService.class.getName());
map.put("dubbo", Version.getProtocolVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
// 配置協(xié)議
if (!map.containsKey("protocol")) {
if (ExtensionLoader.getExtensionLoader(RegistryFactory.class).hasExtension("remote")) {
map.put("protocol", "remote");
} else {
map.put("protocol", "dubbo");
}
}
// 解析得到 URL 列表,address 可能包含多個注冊中心 ip话肖,
// 因此解析得到的是一個 URL 列表
List<URL> urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
url = url.addParameter(Constants.REGISTRY_KEY, url.getProtocol());
// 將 URL 協(xié)議頭設(shè)置為 registry
url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
// 通過判斷條件北秽,決定是否添加 url 到 registryList 中,條件如下:
// (服務(wù)提供者 && register = true 或 null)
// || (非服務(wù)提供者 && subscribe = true 或 null)
if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
|| (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}
loadRegistries
方法主要包含如下的邏輯:
- 檢測是否存在注冊中心配置類最筒,不存在則拋出異常
- 構(gòu)建參數(shù)映射集合贺氓,也就是 map
- 構(gòu)建注冊中心鏈接列表
- 遍歷鏈接列表,并根據(jù)條件決定是否將其添加到 registryList 中
我的dubbo配置如下:
<dubbo:application name="provider" owner="hui.wang" organization="dubbo-learn"/>
<dubbo:registry protocol="zookeeper"
address="192.168.33.10:2181"
id="dubbo-provider"
register="true"
check="false"
group="dubbo-learn"/>
<dubbo:service interface="com.hui.wang.dubbo.learn.api.MyService"
ref="myServiceImpl"
version="1.0"
registry="dubbo-provider"/>
生成的registryURLs
如下:
registry://192.168.33.10:2181/com.alibaba.dubbo.registry.RegistryService?application=provider&check=false&dubbo=2.0.1&group=dubbo-learn&organization=dubbo-learn&owner=hui.wang&pid=87270®ister=true®istry=zookeeper×tamp=1550484891114
配置完registryURLs
后床蜘,就是繼續(xù)組裝 導(dǎo)出的URL辙培。下面開始分析doExportUrlsFor1Protocol
方法,代碼如下:
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
String name = protocolConfig.getName();
// 如果協(xié)議名為空邢锯,或空串扬蕊,則將協(xié)議名變量設(shè)置為 dubbo
if (name == null || name.length() == 0) {
name = "dubbo";
}
Map<String, String> map = new HashMap<String, String>();
// 添加 side、版本丹擎、時間戳以及進(jìn)程號等信息到 map 中
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getProtocolVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
// 通過反射將對象的字段信息添加到 map 中
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, protocolConfig);
appendParameters(map, this);
// methods 為 MethodConfig 集合尾抑,MethodConfig 中存儲了 <dubbo:method> 標(biāo)簽的配置信息
if (methods != null && !methods.isEmpty()) {
// 這段代碼用于添加 Callback 配置到 map 中,代碼太長蒂培,待會單獨分析
}
// 檢測 generic 是否為 "true"再愈,并根據(jù)檢測結(jié)果向 map 中添加不同的信息
if (ProtocolUtils.isGeneric(generic)) {
map.put(Constants.GENERIC_KEY, generic);
map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
} else {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put("revision", revision);
}
// 為接口生成包裹類 Wrapper,Wrapper 中包含了接口的詳細(xì)信息护戳,比如接口方法名數(shù)組翎冲,字段信息等
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
// 添加方法名到 map 中,如果包含多個方法名媳荒,則用逗號隔開抗悍,比如 method = init,destroy
if (methods.length == 0) {
logger.warn("NO method found in service interface ...");
map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
} else {
// 將逗號作為分隔符連接方法名,并將連接后的字符串放入 map 中
map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
}
}
// 添加 token 到 map 中
if (!ConfigUtils.isEmpty(token)) {
if (ConfigUtils.isDefault(token)) {
// 隨機(jī)生成 token
map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(Constants.TOKEN_KEY, token);
}
}
// 判斷協(xié)議名是否為 injvm
if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
protocolConfig.setRegister(false);
map.put("notify", "false");
}
// 獲取上下文路徑
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
contextPath = provider.getContextpath();
}
// 獲取 host 和 port
String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = this.findConfigedPorts(protocolConfig, name, map);
// 組裝 URL
URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
// 省略無關(guān)代碼
}
上面的代碼首先是將一些信息钳枕,比如版本缴渊、時間戳、方法名以及各種配置對象的字段信息放入到 map 中鱼炒,map 中的內(nèi)容將作為 URL 的查詢字符串疟暖。構(gòu)建好 map 后,緊接著是獲取上下文路徑、主機(jī)名以及端口號等信息俐巴。最后將 map 和主機(jī)名等數(shù)據(jù)傳給 URL 構(gòu)造方法創(chuàng)建 URL 對象。
這里我生成的URL配置為:
dubbo://192.168.33.1:20880/com.hui.wang.dubbo.learn.api.MyService?anyhost=true&application=provider&bind.ip=192.168.33.1&bind.port=20880&dubbo=2.0.1&generic=false&interface=com.hui.wang.dubbo.learn.api.MyService&methods=say&organization=dubbo-learn&owner=hui.wang&pid=87636&revision=1.0.0&side=provider×tamp=1550485766884&version=1.0
上面省略了一段代碼硬爆,這里簡單分析一下欣舵。這段代碼用于檢測 <dubbo:argument> 標(biāo)簽中的配置信息,并將相關(guān)配置添加到 map 中缀磕。代碼如下:
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// ...
// methods 為 MethodConfig 集合缘圈,MethodConfig 中存儲了 <dubbo:method> 標(biāo)簽的配置信息
if (methods != null && !methods.isEmpty()) {
for (MethodConfig method : methods) {
// 添加 MethodConfig 對象的字段信息到 map 中,鍵 = 方法名.屬性名袜蚕。
// 比如存儲 <dubbo:method name="sayHello" retries="2"> 對應(yīng)的 MethodConfig糟把,
// 鍵 = sayHello.retries,map = {"sayHello.retries": 2, "xxx": "yyy"}
appendParameters(map, method, method.getName());
String retryKey = method.getName() + ".retry";
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
// 檢測 MethodConfig retry 是否為 false牲剃,若是遣疯,則設(shè)置重試次數(shù)為0
if ("false".equals(retryValue)) {
map.put(method.getName() + ".retries", "0");
}
}
// 獲取 ArgumentConfig 列表
List<ArgumentConfig> arguments = method.getArguments();
if (arguments != null && !arguments.isEmpty()) {
for (ArgumentConfig argument : arguments) {
// 檢測 type 屬性是否為空,或者空串(分支1 ??)
if (argument.getType() != null && argument.getType().length() > 0) {
Method[] methods = interfaceClass.getMethods();
if (methods != null && methods.length > 0) {
for (int i = 0; i < methods.length; i++) {
String methodName = methods[i].getName();
// 比對方法名凿傅,查找目標(biāo)方法
if (methodName.equals(method.getName())) {
Class<?>[] argtypes = methods[i].getParameterTypes();
if (argument.getIndex() != -1) {
// 檢測 ArgumentConfig 中的 type 屬性與方法參數(shù)列表
// 中的參數(shù)名稱是否一致缠犀,不一致則拋出異常(分支2 ??)
if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
// 添加 ArgumentConfig 字段信息到 map 中,
// 鍵前綴 = 方法名.index聪舒,比如:
// map = {"sayHello.3": true}
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("argument config error: ...");
}
} else { // 分支3 ??
for (int j = 0; j < argtypes.length; j++) {
Class<?> argclazz = argtypes[j];
// 從參數(shù)類型列表中查找類型名稱為 argument.type 的參數(shù)
if (argclazz.getName().equals(argument.getType())) {
appendParameters(map, argument, method.getName() + "." + j);
if (argument.getIndex() != -1 && argument.getIndex() != j) {
throw new IllegalArgumentException("argument config error: ...");
}
}
}
}
}
}
}
// 用戶未配置 type 屬性辨液,但配置了 index 屬性,且 index != -1
} else if (argument.getIndex() != -1) { // 分支4 ??
// 添加 ArgumentConfig 字段信息到 map 中
appendParameters(map, argument, method.getName() + "." + argument.getIndex());
} else {
throw new IllegalArgumentException("argument config must set index or type");
}
}
}
}
}
// ...
}
上面這段代碼 for 循環(huán)和 if else 分支嵌套太多箱残,導(dǎo)致層次太深滔迈,不利于閱讀,需要耐心看一下被辑。大家在看這段代碼時燎悍,注意把幾個重要的條件分支找出來。只要理解了這幾個分支的意圖敷待,就可以弄懂這段代碼间涵。請注意上面代碼中??符號,這幾個符號標(biāo)識出了4個重要的分支榜揖,下面用偽代碼解釋一下這幾個分支的含義勾哩。
// 獲取 ArgumentConfig 列表
for (遍歷 ArgumentConfig 列表) {
if (type 不為 null,也不為空串) { // 分支1
1. 通過反射獲取 interfaceClass 的方法列表
for (遍歷方法列表) {
1. 比對方法名举哟,查找目標(biāo)方法
2. 通過反射獲取目標(biāo)方法的參數(shù)類型數(shù)組 argtypes
if (index != -1) { // 分支2
1. 從 argtypes 數(shù)組中獲取下標(biāo) index 處的元素 argType
2. 檢測 argType 的名稱與 ArgumentConfig 中的 type 屬性是否一致
3. 添加 ArgumentConfig 字段信息到 map 中思劳,或拋出異常
} else { // 分支3
1. 遍歷參數(shù)類型數(shù)組 argtypes,查找 argument.type 類型的參數(shù)
2. 添加 ArgumentConfig 字段信息到 map 中
}
}
} else if (index != -1) { // 分支4
1. 添加 ArgumentConfig 字段信息到 map 中
}
}
在本節(jié)分析的源碼中妨猩,appendParameters 這個方法出現(xiàn)的次數(shù)比較多潜叛,該方法用于將對象字段信息添加到 map 中。實現(xiàn)上則是通過反射獲取目標(biāo)對象的 getter 方法,并調(diào)用該方法獲取屬性值威兜。然后再通過 getter 方法名解析出屬性名销斟,比如從方法名 getName 中可解析出屬性 name。如果用戶傳入了屬性名前綴椒舵,此時需要將屬性名加入前綴內(nèi)容蚂踊。最后將 <屬性名,屬性值> 鍵值對存入到 map 中就行了笔宿。限于篇幅原因犁钟,這里就不分析 appendParameters 方法的源碼了,大家請自行分析泼橘。