Dubbo服務導出過程開始於Spring容器的刷新事件,Dubbo在接收到事件後,會立即執行服務導出邏輯。
整個導出邏輯大體分爲三部分:
- 前置工作:主要是配置檢查和組裝URL
- 導出服務:包含導出本地服務和遠程服務
- 向註冊中心註冊服務,用於服務的發現
具體實現:
main()方法啓動後:根據20880端口(默認端口號),發佈一個遠程調用的地址:dubbo//:192.168.1.1:20880/interface?...(發佈到註冊中心)
上述的這個過程主要做了一下的事情:
- 解析配置文件–>
dubbo
是基於spring
標籤進行擴展的 - 暴露服務–>本地服務(以
injvm
開頭的URL)和遠程服務 - 啓動
netty
–>提供netty
服務,暴露端口 - 連接
zk
- 向
zk
註冊地址 - 監聽
zk
1、前置工作
1.1 解析配置並檢查
spring
裏面提供了很多擴展
dubbo-config-spring
模塊裏面提供了配置文件的擴展
spring
提供了兩個可擴展的:NamespaceHandlerSupport 和 BeanDefinitionParser
根據SPI
擴展機制進入DubboNamespaceHandler
解析配置文件,進入到serviceBean
和ServiceConfig
中,開始進行導出服務的前置工作;通過onApplicationEvent
監聽事件,檢查服務是否發佈,調用export()
導出服務,以下是對export()
的分析:
- 檢查
<dubbo service: interface>
的屬性是否合法 - 檢查
providerConfig 和 ApplicationConfig
等核心配置類對象是否爲空,爲空則出其他配置類獲取對應的實例 - 檢查並處理泛化服務和普通服務
- 檢查本地存根配置
- 對
ApplicationConfig 和 RegisterConfig
等配置類進行檢查,爲空則嘗試創建,無法創建則拋出異常
多協議多註冊中心的導出服務
private void doExportUrls(){
//加載註冊中連接
List<URL> registryURLs = loadRegistries(true);
// 遍歷 protocols,並在每個協議下導出服務
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
1.3 組裝URL
走到這一步url大概是這個樣子: registry://192.168.22.130:2181/org.apache.dubbo.registry.RegistryService/……
這一步是根據配置信息組裝url, 而且url驅動流程的執行,會再各個模塊之間一直往下傳,後續會不斷修改URL頭或協議地址,並根據url地址中的信息做相應的處理;
在doExportUrlsFor1Protocol方法中做了url的組裝處理,通過反射的方式獲取到版本、時間戳、方法名以及各種配置對象的字段信息,然後放入map, 貼一下僞代碼幫助理解:
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// 獲取 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 中
}
}
}
2. 導出Dubbo服務
準備工作做完了,接下來進行服務導出。服務導出分爲導出到本地JVM和導出到遠程。
代碼根據 url 中的 scope 參數決定服務導出方式,分別如下:
scope = none,不導出服務
scope != remote,導出到本地
scope != local,導出到遠程
服務發佈的本質就是把export的每個服務轉爲一個對應的Invoker可執行體,然後把轉換後的Invoker都放到一個exporterMap(key,invoker)集合中:
private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
// 省略無關代碼
if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.hasExtension(url.getProtocol())) {
// 加載 ConfiguratorFactory,並生成 Configurator 實例,然後通過實例配置 url
url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
.getExtension(url.getProtocol()).getConfigurator(url).configure(url);
}
String scope = url.getParameter(Constants.SCOPE_KEY);
// 如果 scope = none,則什麼都不做
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
// scope != remote,導出到本地
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
exportLocal(url);
}
// scope != local,導出到遠程
if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {
if (registryURLs != null && !registryURLs.isEmpty()) {
for (URL registryURL : registryURLs) {
url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));
// 加載監視器鏈接
URL monitorUrl = loadMonitor(registryURL);
if (monitorUrl != null) {
// 將監視器鏈接作爲參數添加到 url 中
url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
}
String proxy = url.getParameter(Constants.PROXY_KEY);
if (StringUtils.isNotEmpty(proxy)) {
registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
}
// 爲服務提供類(ref)生成 Invoker
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
// DelegateProviderMetaDataInvoker 用於持有 Invoker 和 ServiceConfig
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
// 導出服務,並生成 Exporter
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
// 不存在註冊中心,僅導出服務
} else {
Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);
exporters.add(exporter);
}
}
}
this.urls.add(url);
}
不管是導出到本地,還是遠程。進行服務導出之前,均需要先創建 Invoker,這是一個很重要的步驟。因此下面先來分析 Invoker 的創建過程。
2.1 invoker創建過程:
在 Dubbo 中,Invoker 是一個非常重要的模型。在服務提供端,以及服務引用端均會出現 Invoker。Dubbo 官方文檔中對 Invoker 進行了說明,這裏引用一下。
Invoker 是實體域,它是 Dubbo 的核心模型,其它模型都向它靠擾,或轉換成它,它代表一個可執行體,可向它發起 invoke 調用,它有可能是一個本地的實現,也可能是一個遠程的實現,也可能一個集羣實現。
Invoker由ProxyFactory創建而來,Dubbo默認的ProxyFactory實現類是javassistProFactory。來看一下javassistProFactory類創建Invoker的過程:
public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
// 爲目標類創建 Wrapper
final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
// 創建匿名 Invoker 類對象,並實現 doInvoke 方法。
//
return new AbstractProxyInvoker<T>(proxy, type, url) {
@Override
protected Object doInvoke(T proxy, String methodName,
Class<?>[] parameterTypes,
Object[] arguments) throws Throwable {
// 調用 Wrapper 的 invokeMethod 方法,invokeMethod 最終會調用目標方法
return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
}
};
}
重寫後的doInvoke邏輯比較簡單,僅僅是將調用請求轉發給了Wrapper類的invokeMethod,Wrapper用於“包裹”目標類,Wrapper是一個抽象類,僅可通過getWrapper方法傳入的Class對象進行解析,拿到類的信息,生成invokeMethod方法和其他方法代碼,代碼生成完畢之後,通過javassist生成Class對象,最後再通過反射創建Wrapper實例。
public static Wrapper getWrapper(Class<?> c) {
while (ClassGenerator.isDynamicClass(c))
c = c.getSuperclass();
if (c == Object.class)
return OBJECT_WRAPPER;
// 從緩存中獲取 Wrapper 實例
Wrapper ret = WRAPPER_MAP.get(c);
if (ret == null) {
// 緩存未命中,創建 Wrapper
ret = makeWrapper(c);
// 寫入緩存
WRAPPER_MAP.put(c, ret);
}
return ret;
}
未完待續。。。