介紹
本篇文章,我們來研究一下 Dubbo 導出服務的過程。Dubbo 服務導出過程始于 Spring 容器發布刷新事件,Dubbo 在接收到事件后,會立即執行服務導出邏輯。整個邏輯大致可分為三個部分:
- 第一部分是前置工作,主要用于檢查參數,組裝 URL。
- 第二部分是導出服務,包含導出服務到本地 (JVM),和導出服務到遠程兩個過程。
- 第三部分是向注冊中心注冊服務,用于服務發現。
1. 第一部分- 導出服務前置工作
Dubbo 導出服務的入口class是com.alibaba.dubbo.config.spring.ServiceBean
,這個類是spring通過解析<dubbo:service>
節點創建的單例Bean,每一個<dubbo:service>
都會創建一個ServiceBean
,ServiceBean
的類圖如下:
ServiceBean
的聲明為:
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware
可以看出ServiceBean
實現了spring的InitializingBean
、DisposableBean
、ApplicationContextAware
、ApplicationListener
和BeanNameAware
接口,關于這幾個接口的使用在前面已經提到了,這里不再說明。
Dubbo 支持兩種服務導出方式,分別延遲導出和立即導出。延遲導出的入口是 ServiceBean
的 afterPropertiesSet
方法,立即導出的入口是 ServiceBean
的onApplicationEvent
方法。
我們先看一下afterPropertiesSet
方法,這個方法會在ServiceBean
創建前調用。
@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);
}
}
// 延遲導出,這里不做講解
if (!isDelay()) {
export();
}
}
afterPropertiesSet
檢查ServiceBean的某個屬性(這里的屬性包含如下6個)是否為空,如果為空,從applicationContext獲取相應類型的bean,如果獲取到了,則進行相應的設置。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
:服務名稱
填充完后,有一個重要的方法就是export()
。這個方法會判斷延遲的時間是否大于0,如果是,才會執行。這里如果我們沒有設置延遲,一個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"
}
實際上在創建ServiceBean實例的時候,也會初始化其父類ServiceConfig的靜態屬性:
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
這里使用了Adaptive,之前有過講解,會生成對應的Protocol$Adaptive
實例和ProxyFactory$Adaptive
實例。對應的源碼如下:
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的初始化過程已經講解完成了。接下來我們看看ServiceBean#onApplicationEvent
方法,改方法會在 Spring 上下文刷新事件時調用。代碼如下:
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 是否有延遲導出 && 是否已導出 && 是不是已被取消導出
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
這個方法首先會根據條件決定是否導出服務,比如有些服務設置了延時導出,那么此時就不應該在此處導出。還有一些服務已經被導出了,或者當前服務被取消導出了,此時也不能再次導出相關服務。注意這里的 isDelay 方法,這個方法字面意思是“是否延遲導出服務”,返回 true 表示延遲導出,false 表示不延遲導出。但是該方法真實意思卻并非如此,當方法返回 true 時,表示無需延遲導出。返回 false 時,表示需要延遲導出。
接下來對服務導出的前置邏輯進行分析。
配置檢查以及 URL 裝配
在導出服務之前,Dubbo 需要檢查用戶的配置是否合理,或者為用戶補充缺省配置。配置檢查完成后,接下來需要根據這些配置組裝 URL。
- 檢查配置,本節我們接著前面的源碼向下分析,前面說過 onApplicationEvent 方法在經過一些判斷后,會決定是否調用 export 方法導出服務。那么下面我們從 export 方法開始進行分析,如下:
public synchronized void export() {
if (provider != null) {
// 獲取 export 和 delay 配置
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
// 如果 export 為 false,則不導出服務
if (export != null && !export) {
return;
}
// delay > 0,延時導出服務
if (delay != null && delay > 0) {
delayExportExecutor.schedule(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
// 立即導出服務
} else {
doExport();
}
}
export
方法分別對export
和delay
配置進行檢查,首先是 export 配置,這個配置決定了是否導出服務。有時候我們只是想本地啟動服務進行一些調試工作,我們并不希望把本地啟動的服務暴露出去給別人調用。此時,我們可通過配置 export 禁止服務導出,比如:
<dubbo:provider export="false" />
delay 配置顧名思義,用于延遲導出服務,這個就不分析了。
- 下面,我們繼續分析源碼,這次要分析的是 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 是否為空,為空則新建一個,并通過系統變量為其初始化
checkDefault();
// 下面幾個 if 語句用于檢測 provider、application 等核心配置類對象是否為空,
// 若為空,則嘗試從其他配置類對象中獲取相應的實例。
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 是否為泛化服務類型
if (ref instanceof GenericService) {
// 設置 interfaceClass 為 GenericService.class
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
// 設置 generic = "true"
generic = Boolean.TRUE.toString();
}
// ref 非 GenericService 類型
} else {
try {
// 設置 interfaceClass
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
// 對 interfaceClass,以及 <dubbo:method> 標簽中的必要字段進行檢查
checkInterfaceAndMethods(interfaceClass, methods);
// 對 ref 合法性進行檢測
checkRef();
// 設置 generic = "false"
generic = Boolean.FALSE.toString();
}
// local 和 stub 在功能應該是一致的,用于配置本地存根
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;
}
// 導出服務
doExportUrls();
// ProviderModel 表示服務提供者模型,此對象中存儲了與服務提供者相關的信息。
// 比如服務的配置信息,服務實例等。每個被導出的服務對應一個 ProviderModel。
// ApplicationModel 持有所有的 ProviderModel。
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
以上就是配置檢查的相關分析,代碼比較多,需要大家耐心看一下。下面對配置檢查的邏輯進行簡單的總結,如下:
- 檢測 <dubbo:service> 標簽的 interface 屬性合法性,不合法則拋出異常
- 檢測 ProviderConfig、ApplicationConfig 等核心配置類對象是否為空,若為空,則嘗試從其他配置類對象中獲取相應的實例。
- 檢測并處理泛化服務和普通服務類
- 檢測本地存根配置,并進行相應的處理
- 對 ApplicationConfig、RegistryConfig 等配置類進行檢測,為空則嘗試創建,若無法創建則拋出異常
配置檢查并非本文重點,因此這里不打算對 doExport 方法所調用的方法進行分析(doExportUrls 方法除外)。在這些方法中,除了 appendProperties 方法稍微復雜一些,其他方法邏輯不是很復雜。因此,大家可自行分析。
-
doExportUrls
方法,Dubbo 允許我們使用不同的協議導出服務,也允許我們向多個注冊中心注冊服務。相關代碼如下:
private void doExportUrls() {
// 加載注冊中心鏈接
List<URL> registryURLs = loadRegistries(true);
// 遍歷 protocols,并在每個協議下導出服務
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
上面代碼首先是通過 loadRegistries
加載注冊中心鏈接,然后再遍歷 ProtocolConfig
集合導出每個服務。并在導出服務的過程中,將服務注冊到注冊中心。下面,我們先來看一下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 為空,則將其設為 0.0.0.0
address = Constants.ANYHOST_VALUE;
}
// 從系統屬性中加載注冊中心地址
String sysaddress = System.getProperty("dubbo.registry.address");
// 如果系統屬性中的配置中心地址不為空,將注冊地址設置為系統屬性配置中的地址
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()));
}
// 配置協議
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 協議頭設置為 registry
url = url.setProtocol(Constants.REGISTRY_PROTOCOL);
// 通過判斷條件,決定是否添加 url 到 registryList 中,條件如下:
// (服務提供者 && register = true 或 null)
// || (非服務提供者 && subscribe = true 或 null)
if ((provider && url.getParameter(Constants.REGISTER_KEY, true))
|| (!provider && url.getParameter(Constants.SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}
loadRegistries
方法主要包含如下的邏輯:
- 檢測是否存在注冊中心配置類,不存在則拋出異常
- 構建參數映射集合,也就是 map
- 構建注冊中心鏈接列表
- 遍歷鏈接列表,并根據條件決定是否將其添加到 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
后,就是繼續組裝 導出的URL。下面開始分析doExportUrlsFor1Protocol
方法,代碼如下:
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
String name = protocolConfig.getName();
// 如果協議名為空,或空串,則將協議名變量設置為 dubbo
if (name == null || name.length() == 0) {
name = "dubbo";
}
Map<String, String> map = new HashMap<String, String>();
// 添加 side、版本、時間戳以及進程號等信息到 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> 標簽的配置信息
if (methods != null && !methods.isEmpty()) {
// 這段代碼用于添加 Callback 配置到 map 中,代碼太長,待會單獨分析
}
// 檢測 generic 是否為 "true",并根據檢測結果向 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 中包含了接口的詳細信息,比如接口方法名數組,字段信息等
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)) {
// 隨機生成 token
map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
} else {
map.put(Constants.TOKEN_KEY, token);
}
}
// 判斷協議名是否為 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);
// 省略無關代碼
}
上面的代碼首先是將一些信息,比如版本、時間戳、方法名以及各種配置對象的字段信息放入到 map 中,map 中的內容將作為 URL 的查詢字符串。構建好 map 后,緊接著是獲取上下文路徑、主機名以及端口號等信息。最后將 map 和主機名等數據傳給 URL 構造方法創建 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> 標簽中的配置信息,并將相關配置添加到 map 中。代碼如下:
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// ...
// methods 為 MethodConfig 集合,MethodConfig 中存儲了 <dubbo:method> 標簽的配置信息
if (methods != null && !methods.isEmpty()) {
for (MethodConfig method : methods) {
// 添加 MethodConfig 對象的字段信息到 map 中,鍵 = 方法名.屬性名。
// 比如存儲 <dubbo:method name="sayHello" retries="2"> 對應的 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,若是,則設置重試次數為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();
// 比對方法名,查找目標方法
if (methodName.equals(method.getName())) {
Class<?>[] argtypes = methods[i].getParameterTypes();
if (argument.getIndex() != -1) {
// 檢測 ArgumentConfig 中的 type 屬性與方法參數列表
// 中的參數名稱是否一致,不一致則拋出異常(分支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];
// 從參數類型列表中查找類型名稱為 argument.type 的參數
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 循環和 if else 分支嵌套太多,導致層次太深,不利于閱讀,需要耐心看一下。大家在看這段代碼時,注意把幾個重要的條件分支找出來。只要理解了這幾個分支的意圖,就可以弄懂這段代碼。請注意上面代碼中??符號,這幾個符號標識出了4個重要的分支,下面用偽代碼解釋一下這幾個分支的含義。
// 獲取 ArgumentConfig 列表
for (遍歷 ArgumentConfig 列表) {
if (type 不為 null,也不為空串) { // 分支1
1. 通過反射獲取 interfaceClass 的方法列表
for (遍歷方法列表) {
1. 比對方法名,查找目標方法
2. 通過反射獲取目標方法的參數類型數組 argtypes
if (index != -1) { // 分支2
1. 從 argtypes 數組中獲取下標 index 處的元素 argType
2. 檢測 argType 的名稱與 ArgumentConfig 中的 type 屬性是否一致
3. 添加 ArgumentConfig 字段信息到 map 中,或拋出異常
} else { // 分支3
1. 遍歷參數類型數組 argtypes,查找 argument.type 類型的參數
2. 添加 ArgumentConfig 字段信息到 map 中
}
}
} else if (index != -1) { // 分支4
1. 添加 ArgumentConfig 字段信息到 map 中
}
}
在本節分析的源碼中,appendParameters 這個方法出現的次數比較多,該方法用于將對象字段信息添加到 map 中。實現上則是通過反射獲取目標對象的 getter 方法,并調用該方法獲取屬性值。然后再通過 getter 方法名解析出屬性名,比如從方法名 getName 中可解析出屬性 name。如果用戶傳入了屬性名前綴,此時需要將屬性名加入前綴內容。最后將 <屬性名,屬性值> 鍵值對存入到 map 中就行了。限于篇幅原因,這里就不分析 appendParameters 方法的源碼了,大家請自行分析。