1. Dubbo服務(wù)啟動(dòng)過(guò)程
啟動(dòng)一個(gè)Dubbo服務(wù),通過(guò)啟動(dòng)日志,查看Dubbo服務(wù)啟動(dòng)的過(guò)程中都做了哪些事情:

通過(guò)啟動(dòng)日志可以看到,在Dubbo服務(wù)發(fā)布過(guò)程中做了以下的一系列動(dòng)作:
1.暴露本地服務(wù)
2.暴露遠(yuǎn)程服務(wù)
3.啟動(dòng)Netty服務(wù)
4.連接Zookeeper并到Zookeeper進(jìn)行注冊(cè)
5.監(jiān)聽(tīng)Zookeeper
通過(guò)Dubbo發(fā)布過(guò)程的詳細(xì)圖解看下服務(wù)提供者暴露服務(wù)的一個(gè)詳細(xì)過(guò)程:

首先ServiceConfig拿到對(duì)外提供服務(wù)的實(shí)際類(lèi)ref(如Dubbo源碼中 dubbo-demo-xml-provider下的DemoServiceImpl類(lèi)),然后通過(guò)ProxyFactory的getInvoker方法使用ref生成一個(gè)AbstractProxyInvoker的實(shí)例,到這一步就完成具體服務(wù)到Invoker的轉(zhuǎn)換,接下來(lái)就是Invoker到Exporter的轉(zhuǎn)換。
2.本地服務(wù)暴露的實(shí)現(xiàn)過(guò)程
從Dubbo的啟動(dòng)過(guò)程我們可以得知,Dubbo服務(wù)提供者,先進(jìn)行本地暴露再進(jìn)行遠(yuǎn)程暴露,在我們?nèi)粘5氖褂脠?chǎng)景中用的最多的是遠(yuǎn)程暴露。那么為什么還要進(jìn)行本地暴露呢?
很多使用Dubbo框架的應(yīng)用,可能存在在同一個(gè)JVM暴露了遠(yuǎn)程服務(wù),同時(shí)同一個(gè)JVM內(nèi)部又引用了自身服務(wù)的情況,Dubbo默認(rèn)會(huì)把遠(yuǎn)程服務(wù)用injvm協(xié)議再暴露一份,這樣消費(fèi)方直接消費(fèi)用一個(gè)JVM內(nèi)部的服務(wù),避免了跨網(wǎng)絡(luò)進(jìn)行遠(yuǎn)程通信。
我們先來(lái)看一下啟動(dòng)Dubbo服務(wù)提供者時(shí)最開(kāi)始輸出的日志:
NFO support.ClassPathXmlApplicationContext: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@3b192d32: startup date [Sun Aug 18 22:28:44 CST 2019]; root of context hierarchy
[18/08/19 22:28:44:714 CST] main INFO xml.XmlBeanDefinitionReader: Loading XML bean definitions from class path resource [spring/dubbo-provider.xml]
[18/08/19 22:28:45:257 CST] main INFO logger.LoggerFactory: using logger: org.apache.dubbo.common.logger.log4j.Log4jLoggerAdapter
[18/08/19 22:28:47:550 CST] main INFO config.AbstractConfig: [DUBBO] The service ready on spring started. service: org.apache.dubbo.demo.DemoService, dubbo version: , current host: 192.168.0.102
[18/08/19 22:28:48:151 CST] main INFO utils.Compatibility: Running in ZooKeeper 3.4.x compatibility mode
可以根據(jù)日志看出啟動(dòng)的流程一下關(guān)鍵步驟:
1.Loading XML bean definitions from class path resource [spring/dubbo-provider.xml] 加載配置文件
2.The service ready on spring started. service: org.apache.dubbo.demo.DemoService, dubbo version: , current host: 192.168.0.102 Service可以啟動(dòng)
那么我們根據(jù)這句日志輸出做為一個(gè)切入點(diǎn),來(lái)搜索下是哪里輸出了 The service ready on spring started. 這句日志發(fā)現(xiàn)在ServiceBean類(lèi)中出現(xiàn)
ServiceBean--onApplicationEvent()方法)
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
我們來(lái)具體看一下ServiceBean這個(gè)類(lèi),到底是怎么執(zhí)行的。
通過(guò)配置文件的加載過(guò)程我們可以知道,在服務(wù)啟動(dòng)時(shí),Spring會(huì)解析dubbo服務(wù)的配置文件,并將配置文件中的參數(shù)轉(zhuǎn)換成相應(yīng)的Bean,當(dāng)遇到 <dubbo:service> 時(shí)就會(huì)轉(zhuǎn)換成ServiceBean。
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean,
ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, BeanNameAware,
ApplicationEventPublisherAware
以上為ServiceBean的定義,集成了ServiceConfig同時(shí)實(shí)現(xiàn)了InitializingBean、ApplicationListener等多個(gè)接口。在InitializingBean接口中有一個(gè)有個(gè)afterPropertiesSet方法,ServiceBean重寫(xiě)了該方法,在Bean的屬性初始化時(shí),Spring會(huì)默認(rèn)調(diào)用該方法。
同時(shí)實(shí)現(xiàn)了ApplicationListener接口,并且重寫(xiě)了onApplicationEvent()方法。這兩個(gè)接口都是Spring提供的接口,那么這兩個(gè)接口會(huì)起到什么作用呢?
ServiceBean在創(chuàng)建完對(duì)象之后,會(huì)調(diào)用afterPropertiesSet()方法,該方法完成beanClass屬性值的設(shè)置;在IOC容器啟動(dòng)完成之后,Spring會(huì)自動(dòng)回調(diào)onApplicationEvent()方法,該方法完成服務(wù)的暴露,也就是在該方法中,我們看到了日志中的
The service ready on spring started.
3.服務(wù)暴露方法實(shí)現(xiàn)的解析
ServiceBean的 onApplicationEvent
ServiceBean--onApplicationEvent()方法源碼如下:
public void onApplicationEvent(ContextRefreshedEvent
event) {
/**如果是暴露的、并且沒(méi)有暴露的則調(diào)用export方法,暴露服務(wù)*/
if (!isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) { logger.info("The service ready on spring
started. service: " + getInterface());
}
export();
}
}
該方法為服務(wù)暴露的入口,那么暴露邏輯的真正實(shí)現(xiàn)則是在export方法中,接下來(lái)對(duì)export方法進(jìn)行解析。
ServicebBean--export()方法:
@Override
public void export() {
super.export();
// Publish ServiceBeanExportedEvent
publishExportEvent();
}
通過(guò)export方法的代碼可知,在這里調(diào)用父類(lèi)的export()方法,之后調(diào)用調(diào)用publishExportEvent()方法。
下面看下 ServiceConfig的export()方法都實(shí)現(xiàn)了哪些邏輯。
public synchronized void export() {
//檢測(cè)一些必要的屬性和設(shè)置一些默認(rèn)值
checkAndUpdateSubConfigs();
//判斷是否已經(jīng)導(dǎo)出
if (!shouldExport()) {
return;
}
//判斷是否已經(jīng)設(shè)置了延遲
if (shouldDelay()) {
DELAY_EXPORT_EXECUTOR.schedule(this::doExport, getDelay(), TimeUnit.MILLISECONDS);
} else {
//執(zhí)行導(dǎo)出動(dòng)作
doExport();
}
}
該方法除了檢測(cè)基本的配置,以及在沒(méi)有配置的情況下為配置設(shè)置默認(rèn)值之外,最關(guān)鍵的是執(zhí)行 doExport()方法,進(jìn)一步查看doExzport方法都實(shí)現(xiàn)了什么邏輯。
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("The service " + interfaceClass.getName() + " has already unexported!");
}
if (exported) {
return;
}
exported = true;
if (StringUtils.isEmpty(path)) {
path = interfaceName;
}
doExportUrls();
}
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true);
for (ProtocolConfig protocolConfig : protocols) {
//拼接pathKey:group/contextpath/interfacename:version
String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" + path).orElse(path), group, version);
// ProviderModel 表示服務(wù)提供者模型,此對(duì)象中存儲(chǔ)了與服務(wù)提供者相關(guān)的信息。
// 比如服務(wù)的配置信息,服務(wù)實(shí)例等。每個(gè)被導(dǎo)出的服務(wù)對(duì)應(yīng)一個(gè) ProviderModel。
// ApplicationModel 持有所有的 ProviderModel。
ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
ApplicationModel.initProviderModel(pathKey, providerModel);
doExportUrlsFor1Protocol(protocolConfig, registryURLs);
}
}
以上代碼通過(guò)loadRegistries 加載注冊(cè)中心鏈接,然后再遍歷 ProtocolConfig 集合導(dǎo)出每個(gè)服務(wù)。并在暴露服務(wù)的過(guò)程中,將服務(wù)注冊(cè)到注冊(cè)中心。
loadRegistries()方法:
protected List<URL> loadRegistries(boolean provider) {
// check && override if necessary
List<URL> registryList = new ArrayList<URL>();
//判斷注冊(cè)配置是否為空,及配置文件中有沒(méi)有<dubbo:registry>標(biāo)簽(配置注冊(cè)中心的一些信息)
if (CollectionUtils.isNotEmpty(registries)) {
//由于可能配置多個(gè)注冊(cè)中心地址,在這里遍歷注冊(cè)列表
for (RegistryConfig config : registries) {
String address = config.getAddress();
//判斷配置的address是否為空,如果為空則配置為 0.0.0.0(由于在此步驟前已經(jīng)對(duì)registry的地址做了非空校驗(yàn),一般走不到這一步)
if (StringUtils.isEmpty(address)) {
address = ANYHOST_VALUE;
}
if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) {
Map<String, String> map = new HashMap<String, String>();
appendParameters(map, application);
appendParameters(map, config);
map.put(PATH_KEY, RegistryService.class.getName());
appendRuntimeParameters(map);
if (!map.containsKey(PROTOCOL_KEY)) {
map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
}
List<URL> urls = UrlUtils.parseURLs(address, map);
for (URL url : urls) {
// 將 URL 協(xié)議頭設(shè)置為 registry
url = URLBuilder.from(url)
.addParameter(REGISTRY_KEY, url.getProtocol())
.setProtocol(REGISTRY_PROTOCOL)
.build();
//判斷是否將地址增加到注冊(cè)中心地址列表中
if ((provider && url.getParameter(REGISTER_KEY, true))
|| (!provider && url.getParameter(SUBSCRIBE_KEY, true))) {
registryList.add(url);
}
}
}
}
}
return registryList;
}
經(jīng)過(guò)這一步驟之后,已經(jīng)獲取到了注冊(cè)中心的地址,接下來(lái)就是調(diào)用doExportUrlsFor1Protocol()方法組裝參數(shù)并且暴露Dubbo服務(wù)。
服務(wù)導(dǎo)出(暴露)過(guò)程概述
1. Spring容器啟動(dòng)時(shí)通過(guò)NameSpaceHandler來(lái)解析Dubbo服務(wù)的配置文件,并對(duì)配置文件中的參數(shù)進(jìn)行初始化。
2.加載完配置文件之后,在ServiceBean的onApplicationEvent()方法受到Spring上下文刷新事件后會(huì)執(zhí)行導(dǎo)出(export())方法。該方法為Dubbo服務(wù)導(dǎo)出的起點(diǎn)。
Export()方法邏輯分析:
1、進(jìn)行參數(shù)配置的檢查,檢查該方法是否允許導(dǎo)出,另外檢查該方法是否延遲導(dǎo)出。滿足導(dǎo)出的條件之后,調(diào)用doExport()方法
備注:
* 檢測(cè) <dubbo:service> 標(biāo)簽的 interface 屬性合法性,不合法則拋出異常
* 檢測(cè) ProviderConfig、ApplicationConfig 等核心配置類(lèi)對(duì)象是否為空,若為空,則嘗試從其他配置類(lèi)對(duì)象中獲取相應(yīng)的實(shí)例。
*檢測(cè)并處理泛化服務(wù)和普通服務(wù)類(lèi)
*檢測(cè)本地存根配置,并進(jìn)行相應(yīng)的處理
*對(duì) ApplicationConfig、RegistryConfig 等配置類(lèi)進(jìn)行檢測(cè),為空則嘗試創(chuàng)建,若無(wú)法創(chuàng)建則拋出異常
2、doExport()在參數(shù)合法的情況下,調(diào)用doExportUrls()方法,該方法對(duì)多協(xié)議,多注冊(cè)中心進(jìn)行了支持。
3、doExportUrls() 方法調(diào)用 doExportUrlsFor1Protocol()方法,該方法實(shí)現(xiàn)了由具體服務(wù)到Invoker的轉(zhuǎn)換和Invoker到Exporter的轉(zhuǎn)換。
備注:
invoker由ProxyFactory創(chuàng)建,Dubbo默認(rèn)的ProxyFactory的實(shí)現(xiàn)類(lèi)是JavassistProxyFactory。
我是割草的小豬頭,不斷學(xué)習(xí),不斷進(jìn)步,后續(xù)陸續(xù)更新Dubbo系列的文章,如您有興趣一起了解,歡迎關(guān)注,如文章中有不妥之處,歡迎指正!
Dubbo系列文章一--Dubbo重點(diǎn)掌握模塊
Dubbo系列文章二--配置文件加載過(guò)程
Dubbo系列文章三--Dubbo源碼結(jié)構(gòu)及實(shí)現(xiàn)方