前面花了三篇文章一起深入源碼,分析了 Dubbo 服務提供者是如何 暴露服務的:
Dubbo 是如何暴露一個服務呢?相信你讀完上面三篇就懂了。
示例
本篇主要講解 Consumer,即消費者是如何運作的,還是以最開始的那個 例子代碼作爲 初始程序分析。
第一個例子 HelloDubbo
ReferenceConfig<HelloService> reference = new ReferenceConfig<>();
reference.setApplication(new ApplicationConfig("dubbo-consumer"));
reference.setRegistry(new RegistryConfig("zookeeper://127.0.0.1:2181"));
reference.setInterface(HelloService.class);
HelloService service = reference.get();
String message = service.hello("dubbo I am anla7856");
System.out.println(message);
如上,開始使用一個 ReferenceConfig
,而後給它設置了 ApplicationConfig
、RegistryConfig
、interfaceClass
等,設置完之後,就調用
reference.get()
獲取一個動態實例,而後直接像普通Java程序一樣調用它的方法即可。
所以最重要的就是 ReferenceConfig的 get
方法,接下來透過源碼,看看裏面幹了啥事。
ReferenceConfig 的 get
同樣的,這也是一個 synchronized
修飾的方法,
public synchronized T get() {
// 檢查配置
checkAndUpdateSubConfigs();
if (destroyed) {
throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
}
if (ref == null) {
// 初始化
init();
}
return ref;
}
看看 checkAndUpdateSubConfigs
裏面代碼:
public void checkAndUpdateSubConfigs() {
// 檢查 interfaceName 是否爲空
if (StringUtils.isEmpty(interfaceName)) {
throw new IllegalStateException("<dubbo:reference interface=\"\" /> interface not allow null!");
}
// 在 completeCompoundConfigs 方法中設置 按照 配置優先級設置ReferenceConfig 的 application、module、registries、monitor 等參數
completeCompoundConfigs();
// 嘗試加載 配置中心(ConfigCenter) 中數據,然後執行 剩下所有配置的 refresh 方法
startConfigCenter();
// 如果ConsumerConfig 不爲空,那麼就會初始一個默認的,並且刷新
checkDefault();
// 嘗試刷新 ReferenceConfig
this.refresh();
// 判斷是否爲 泛化調用 generic ,如果是泛化調用,則會直接把 interfaceClass = GenericService.class,這樣纔不會發生類型轉化錯誤
// 如果不是 泛化調用,則會加載 當前傳進來的 interfaceClass以及檢查傳入的方法(MethodConfig)是否在遠程服務提供的接口裏面。
if (getGeneric() == null && getConsumer() != null) {
setGeneric(getConsumer().getGeneric());
}
if (ProtocolUtils.isGeneric(getGeneric())) {
interfaceClass = GenericService.class;
} else {
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
checkInterfaceAndMethods(interfaceClass, methods);
}
// 檢查 外部默認未見 地址,如果有配置則將配置加載進來,默認文件默認地址是可配置爲 系統變量 dubbo.resolve.file
resolveFile();
// 會去判斷 ApplicationConfig 是否存在,不存在則會創建一個默認的 ApplicationConfig,然後會嘗試設置 Dubbo 優雅關機的一些參數。
checkApplication();
// 檢查是否 有 MetadataReportConfig, 沒有則會默認創建一個。
checkMetadataReport();
}
在 checkAndUpdateSubConfigs
中是檢查和初始化配置,裏面包括 消費者涉及的包括ConfigCenter、ReferenceConfig
等多個配置。
- 檢查
interfaceName
是否爲空 - 在
completeCompoundConfigs
方法中設置 按照 配置優先級設置ReferenceConfig
的application、module、registries、monitor
等參數 - 在
startConfigCenter
中,嘗試加載 配置中心(ConfigCenter) 中數據,然後執行 剩下所有配置的refresh
方法 - 在
checkDefault
中,如果ConsumerConfig
不爲空,那麼就會初始一個默認的,並且刷新 - 調用
this.refresh
嘗試刷新ReferenceConfig
- 判斷是否爲 泛化調用
generic
,如果是泛化調用,則會直接把interfaceClass = GenericService.class
,這樣纔不會發生類型轉化錯誤 - 如果不是 泛化調用,則會加載 當前傳進來的
interfaceClass
以及檢查傳入的方法(MethodConfig)是否在遠程服務提供的接口裏面。 resolveFile
檢查 外部默認未見 地址,如果有配置則將配置加載進來,默認文件默認地址是可配置爲 系統變量dubbo.resolve.file
checkApplication
會去判斷ApplicationConfig
是否存在,不存在則會創建一個默認的ApplicationConfig
,然後會嘗試設置 Dubbo 優雅關機的一些參數。checkMetadataReport
則會檢查是否 有MetadataReportConfig
, 沒有則會默認創建一個。
其實具體這一段加載配置邏輯和 服務提供者 ServiceConfig
中基本一致,具體分析在服務提供者初始化博主已經給出了詳細的分析:
可以去文章頂部直接點擊查看即可。
init
過完了前期配置的初始化以及檢查之後,就會進入 init
方法,並且會把 對應的 ref
變量初始化。當然,ref
只是一個接口,多次執行get
僅僅會初始化一次 ref
變量。
private void init() {
// 初始化
if (initialized) {
return;
}
// 檢查是否有 local 或者 stub方法,如果使得化,就去尋找並加載 interface+Local(Stub),而後驗證 Stub/Local 類與interface 類
checkStubAndLocal(interfaceClass);
// 檢查 interface是否配置了 Mock功能,如果配置了 Mock ,則會根據相應的Mock 配置去初始化 MockInvoker,這是一個過濾器,
// Dubbo裏面第一個過濾器,在功能上直接決定了Dubbo 一些重要功能,Mock 就是一個例子。
checkMock(interfaceClass);
// 聲明一個Map存下所有配置
Map<String, String> map = new HashMap<String, String>();
// 說明是 consumer 這邊的
map.put(SIDE_KEY, CONSUMER_SIDE);
// 增加運行時信息,例如 dubbo 版本,以及 timestamp 等。
appendRuntimeParameters(map);
// 檢查 是否爲泛化調用
if (!isGeneric()) {
String revision = Version.getVersion(interfaceClass, version);
if (revision != null && revision.length() > 0) {
map.put(REVISION_KEY, revision);
}
String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
if (methods.length == 0) {
logger.warn("No method found in service interface " + interfaceClass.getName());
map.put(METHODS_KEY, ANY_VALUE);
} else {
map.put(METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), COMMA_SEPARATOR));
}
}
// 設置 interface
map.put(INTERFACE_KEY, interfaceName);
// 設置metrics中參數
appendParameters(map, metrics);
// 設置application中參數
appendParameters(map, application);
// 設置module 中參數
appendParameters(map, module);
// 設置 consumer 中參數
appendParameters(map, consumer);
// 設置當前config, 即ReferenceConfig
appendParameters(map, this);
Map<String, Object> attributes = null;
// 判斷有否 retry 參數
if (CollectionUtils.isNotEmpty(methods)) {
attributes = new HashMap<String, Object>();
for (MethodConfig methodConfig : methods) {
appendParameters(map, methodConfig, methodConfig.getName());
String retryKey = methodConfig.getName() + ".retry";
if (map.containsKey(retryKey)) {
String retryValue = map.remove(retryKey);
if ("false".equals(retryValue)) {
map.put(methodConfig.getName() + ".retries", "0");
}
}
attributes.put(methodConfig.getName(), convertMethodConfig2AyncInfo(methodConfig));
}
}
// 判斷是否有DUBBO_IP_TO_REGISTRY參數,如果有,則判斷該配置是否符合規範
String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY);
if (StringUtils.isEmpty(hostToRegistry)) {
hostToRegistry = NetUtils.getLocalHost();
} else if (isInvalidLocalHost(hostToRegistry)) {
throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
}
map.put(REGISTER_IP_KEY, hostToRegistry);
// 創建一個代理對象
ref = createProxy(map);
// 構造一個 serviceKey
String serviceKey = URL.buildKey(interfaceName, group, version);
// 初始化一個ApplicationModel 對象
ApplicationModel.initConsumerModel(serviceKey, buildConsumerModel(serviceKey, attributes));
initialized = true;
}
以上init方法邏輯如下:
- 檢查是否已經初始化過了。
- 執行
checkStubAndLocal
,檢查是否有local
或者stub
方法,如果使得化,就去尋找並加載interface
+Local(Stub),而後驗證Stub/Local
類與interface
類。 - 執行
checkMock
,檢查interface
是否配置了Mock
功能,如果配置了Mock
,則會根據相應的Mock
配置去初始化MockInvoker
,這是一個過濾器,Dubbo裏面第一個過濾器,在功能上直接決定了Dubbo
一些重要功能,Mock
就是一個例子。 - 判斷是否爲
generic
泛化調用,是泛化調用則不做操作,不是則需要填充 版本version
,如果是方法級別暴露,則需要將需要的方法記錄在map
裏面 - 填充其他一些信息,諸如
application、module、metrics
等信息。 - 解析並填充
retries
參數。 - 如果配置了
DUBBO_IP_TO_REGISTRY
,則需要檢查 這個所傳參數合法性。 - 執行
createProxy
,構造一個 反射對象ref
。
createProxy
前期檢查和設置完配置之後,就可以創建一個代理對象了。
private T createProxy(Map<String, String> map) {
// 判斷是否爲 jvm應用
if (shouldJvmRefer(map)) {
// 構造一個 127.0.0.1 的 url
URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
// 構造並初始乎一個 invoker
invoker = REF_PROTOCOL.refer(interfaceClass, url);
if (logger.isInfoEnabled()) {
logger.info("Using injvm service " + interfaceClass.getName());
}
} else {
// 清除當前urls內容,否則在 retry時候會再次加入到url中,導致OOM異常
urls.clear(); // reference retry init will add url to urls, lead to OOM
// 解析並分割 url中內容,如果有多個則需要將其拆分並放入到urls中
if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
String[] us = SEMICOLON_SPLIT_PATTERN.split(url);
if (us != null && us.length > 0) {
for (String u : us) {
URL url = URL.valueOf(u);
if (StringUtils.isEmpty(url.getPath())) {
url = url.setPath(interfaceName);
}
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
} else {
urls.add(ClusterUtils.mergeUrl(url, map));
}
}
}
} else {
// 嘗試去 register's center 加載配置
if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())){
// 檢查註冊中心,沒有並新增,如果沒有配置中心,則將配置中心初始化爲配置中心
checkRegistry();
List<URL> us = loadRegistries(false);
if (CollectionUtils.isNotEmpty(us)) {
for (URL u : us) {
URL monitorUrl = loadMonitor(u);
if (monitorUrl != null) {
map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
}
urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
}
}
if (urls.isEmpty()) {
throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
}
}
}
if (urls.size() == 1) {
// 如果url只有一個,則將 其初始化 invoker
invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
} else {
// 如果有多個invoker,則需要將所有invokers給加入到 urls
List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
URL registryURL = null;
for (URL url : urls) {
invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
registryURL = url; // use last registry url
}
}
if (registryURL != null) { // registry url is available
// 當 register's 的CLUSTER 可用,增加一個 RegistryAwareCluster
URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME);
// 此時包裝順序則爲: RegistryAwareClusterInvoker(StaticDirectory) -> FailoverClusterInvoker(RegistryDirectory, will execute route) -> Invoker
invoker = CLUSTER.join(new StaticDirectory(u, invokers));
} else {
// 直接的invoker
invoker = CLUSTER.join(new StaticDirectory(invokers));
}
}
}
// 如果需要檢查,則判斷invoker是否可用
if (shouldCheck() && !invoker.isAvailable()) {
throw new IllegalStateException("Failed to check the status of the service " + interfaceName + ". No provider available for the service " + (group == null ? "" : group + "/") + interfaceName + (version == null ? "" : ":" + version) + " from the url " + invoker.getUrl() + " to the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion());
}
if (logger.isInfoEnabled()) {
logger.info("Refer dubbo service " + interfaceClass.getName() + " from url " + invoker.getUrl());
}
/**
* @since 2.7.0
* ServiceData Store
*/
// 設置metadata report信息
MetadataReportService metadataReportService = null;
if ((metadataReportService = getMetadataReportService()) != null) {
URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
metadataReportService.publishConsumer(consumerURL);
}
// create service proxy
// 使用代理工廠創建一個代理,封裝invoker 創建一個代理對象
return (T) PROXY_FACTORY.getProxy(invoker);
}
上面代碼主要包括以下幾個邏輯:
- 檢查是否是
injvm
應用 - 將配置中的url直連的配置,全部解析並加入到 List 中備用
- 初始 url,並構造
Invoker
對象。並加入集羣容錯相關配置。 - 初始化
MetadataReportService
信息 - 調用代理創建一個代理 對象
總結
本文主要介紹了Consumer 初始化的一些邏輯,包括:
- 檢查並初始化
ReferenceConfig
中一些配置 - 使用代理工廠初始化
ref
對象
當然,Consumer 初始化過程和 Provider 初始化過程,在配置讀取方面,十分類似,可能本文對一些初始化配置相關方法沒有細究,因爲這些都是可以從 前幾篇 Provider初始化中讀取。
下一篇文章將主要圍繞調用來看,即當獲取到了一個代理對象後,執行方法,又是以一種怎樣的邏輯呢?
下一篇例子在本篇例子實例基礎上,將啓動多 Provider
實例進行研究。
覺得博主寫的有用,不妨關注博主公衆號: 六點A君。
哈哈哈,Dubbo小吃街不迷路: