本篇文章主要是閱讀了dubbo官方文件:

關於服務的暴露和引用,相信很多同學瞭解不是很透徹。這篇檔案主要是帶大家一步步探究其中的實現,順便記錄下這個過程中學到的其他知識,由於dubbo是一個很成熟的框架了,用到的技術也很多,裡面定義了很多類和介面十分複雜,所以我們一步步去分析篇幅可能有些長,周邊知識也非常多,我在文中都列了相關拓展知識的連結。

從dubbo的配置講起:

Dubbo 服務暴露過程分析

這裡主要用到了spring自定標籤功能,關於spring自定義配置,大家可以檢視:

這裡主要定義了一個dubbo的名稱空間,然後編寫了對應的xsd文件,dubbo的xsd文件在jar包中的META-INF/dubbo。xsd。

Dubbo 服務暴露過程分析

然後dubbo。xsd中我們主要關注service和reference標籤。

然後對標籤的處理類dubbo中的定義寫在META-INF/spring。handlers

Dubbo 服務暴露過程分析

我們看到dubbo標籤的解析主要用到了DubboNamespaceHandler 這個類。然後我們開啟DubboNamespaceHandler這個類的原始碼。

Dubbo 服務暴露過程分析

我們可以看到dubbo自定義了一個DubboBeanDefinitionParser類去解析上面的標籤,並且自定義了ServiceBean和ReferenceBean。然後我們再開啟DubboBeanDefinitionParser這個類。

Dubbo 服務暴露過程分析

這裡我們主要關注parse這個方法,這個方法邏輯比較多,我也沒有讀得十分清楚,不過大體意思就是拿到xml中所有配置的基本資訊,然後定義成spring中的BeanDefinition。關於BeanDefinition,讀者可以看這篇文章:

大體就是對spring的Bean的抽象,主要儲存類名、scope、屬性、建構函式引數列表、依賴的bean、是否是單例類、是否是懶載入等,其實就是將Bean的定義資訊儲存到這BeanDefinition相應的屬性中,後面對Bean的操作就直接對BeanDefinition進行,例如拿到這個BeanDefinition後,可以根據裡面的類名、建構函式、建構函式引數,使用反射進行物件建立。這裡我對其中一點比較感興趣,就是service裡面的ref引數:

Dubbo 服務暴露過程分析

因為這裡把一個interface對映到了一個可例項化的class,而且還是執行時bean的名字,所以我看了這個ref的解析實現,這個解析主要有兩個:

(1)寫class屬性,不寫ref:

Dubbo 服務暴露過程分析

這段的意思是如果用了class標籤,spring會生成相應的class的BeanDefinition,並建立了一個BeanDefinitionHolder來代表執行時的bean,並且這個bean的名字是id+Impl。

(2)寫ref,不寫class。

Dubbo 服務暴露過程分析

如果有ref,就用RuntimeBeanReference作為建立時的bean。

這樣就可以將本來是interface,例項化的時候是另外一個bean,並且這個bean也會存在於ioc的Bean建立鏈條中。

如果ref和class都寫了,則以ref為準,因為ref的程式碼在後面,哈哈^_^

以上就是dubbo如何從xml讀取到定義成spring的BeanDefinition,接下來,我們再看一下bean例項化的時候是如何暴露服務的。

serviceBean是何時暴露服務的

Dubbo 服務暴露過程分析

我們看ServiceBean裡面實現了ApplicationListener,ApplicationContextAware,InitializingBean介面,這3個介面分別會在spring啟動的不同時機依次呼叫,他們呼叫的為順序 2 -> 1 -> 3:

Dubbo 服務暴露過程分析

然後我們看下ServiceBean的三個介面:

Dubbo 服務暴露過程分析

沒什麼好說的,初始化一些變數

Dubbo 服務暴露過程分析

基本做的也是一些初始化的變數,這裡關注一個方法:

BeanFactoryUtils。beansOfTypeIncludingAncestors , 大概的作用就是獲取這個上下文所有指定class的Bean,如上面的providerConfigMap,傳入的引數就是ProviderConfig。class。

然後主要關注一下最後的方法export:

Dubbo 服務暴露過程分析

然後這個serviceBean實現的第3個介面最後執行,也沒太多東西,其實也是呼叫export,估計是為了防止前面的方法沒有執行吧!

Dubbo 服務暴露過程分析

然後我們關注這個export,這個export方法就是將本地服務暴露給外面呼叫的過程,這樣就保證了spring容器在初始化完成的時候,所有的serivceBean都暴露服務了。接下來,我們再看看export做了哪些事情。

serviceBean暴露服務做了哪些事情

我們看看export的原始碼:

Dubbo 服務暴露過程分析

主要就判斷該服務是否已經開啟,以及是否需要延遲開啟,實際邏輯在doExport裡面,我們再看doExport的程式碼。

Dubbo 服務暴露過程分析

別的沒什麼好說的,monitor是監控中心的配置,registries是註冊中心配置,protocols 是釋出者配置,別的暫不知道幹嘛用,暫時不管,這些都會在checkDefault裡面初始化provider這個變數的實話初始化,開啟這個類看到

Dubbo 服務暴露過程分析

主要就是host,port,環境路徑等資訊,然後這些資訊都是從系統變數中拿的,看checkDefault裡面就知道。

Dubbo 服務暴露過程分析

Dubbo 服務暴露過程分析

然後取的都是系統變數裡面的dubbo。字首的變數,dubbo每個服務執行的時候都自動會設定這些引數吧(我猜)。另外這裡還有個初始化這個類的測試單元,很好理解了。

Dubbo 服務暴露過程分析

然後繼續回到doExport。

Dubbo 服務暴露過程分析

這裡複習了一下Class。forName,具體內容可以參考:

,主要就是把介面類載入進來。

checkInterfaceAndMethods檢查interfaceClass不能為空,必須是interface型別,驗證方法必須存在。

checkRef檢查ref不能為空,必須實現interface介面,具體程式碼自己看了。

Dubbo 服務暴露過程分析

然後後面checkApplication初始化application變數

checkRegistry初始化registries變數

checkProtocol初始化protocols變數

appendProperties方法前面有提過

checkStubAndMock方法不知道幹嘛的,忽略

主要看看doExportUrls方法

Dubbo 服務暴露過程分析

然後loadRegistries

Dubbo 服務暴露過程分析

這個方法是將registries變數裡面的每個地址,拼上application和registryconfig裡面的引數,拼成一個registryUrl(不是最後生成的url)帶引數的標準格式,如:www。xxx。com?key1=value1&key2=value2。然後返回這些url的列表,自己一個服務生成的url例子:

registryUrl:

registry://172。23。2。101:2181/com。alibaba。dubbo。registry。RegistryService?application=oic-dubbo-provider&dubbo=2。6。1&logger=slf4j&pid=15258®ister=true®istry=zookeeper×tamp=1528958780785

然後再遍歷protocols變數,將protocols列表中的每個protocol根據url暴露出去,主要是doExportUrlsFor1Protocol方法。

Dubbo 服務暴露過程分析

然後這個方法前期就一堆塞引數到map,最後也是跟上面生成registryUrl差不多,只不過多加了一些module,provider和自己的一些引數,拼成一個更長的url。下面這個就是我上面那個服務生成的完整url:

dubbo://10。8。0。28:12000/com。tyyd。oic。service。PushMessageService?accepts=1000&anyhost=true&application=oic-dubbo-provider&bind。ip=10。8。0。28&bind。port=12000&buffer=8192&charset=UTF-8&default。service。filter=dubboCallDetailFilter&dubbo=2。6。1&generic=false&interface=com。tyyd。oic。service。PushMessageService&iothreads=9&logger=slf4j&methods=deletePushMessage,getPushMessage,batchPushMessage,addPushMessage,updatePushMessage,qryPushMessage&payload=8388608&pid=15374&queues=0&retries=0&revision=1。0。0&serialization=hessian2&side=provider&threadpool=fixed&threads=100&timeout=6000×tamp=1528959454516&version=1。0。0

Dubbo 服務暴露過程分析

上面可以看到,url地址包含了版本號,介面名,方法列表,序列化方法,過期時間等這個介面bean所有需要用到的上下文資訊,並且地址頭也由registry改成了dubbo。因為包含了所有上下文的資訊,所以這個url的用處很大,後面看程式碼就知道了。

這部分後面url還拼加了一些拓展類,監控url等資訊,具體就不細看了,主要看看轉成invoker和並建立exporter這個階段

Dubbo 服務暴露過程分析

這裡的話,我比較好奇proxyFactory這個變數是如何生成的,這個根據官方文件說可能是javassistProxyFactory或者JdkProxyFactory中的任意一個,當然,這裡是一個重點,就是動態代理,關於動態代理的知識可以看這裡:

然後javassistProxyFactory和JdkProxyFactory都是動態代理的生成技術,只不過一個是用位元組碼生成,一個是用jdk生成。

然後這個proxyFactory變數的生成我們看到:

Dubbo 服務暴露過程分析

然後網上查了下,原來這裡有個很牛批的技術,叫做SPI,相關說明參考:

大致就是同一個介面,可以有不同的實現類,僅透過配置就可以動態修改具體用哪個實現類,而且這個拿到的實現還是單例模式

具體dubbo中的說明文章中也有描述,可以看到

Dubbo 服務暴露過程分析

proxyFactory介面寫了SPI標籤,所以這裡預設使用的就是javassistProxyFactory。

然後我們看看javassistProxyFactory的實現裡面寫了什麼東西。

Dubbo 服務暴露過程分析

這裡生成了一個Wrapper,這個Wrapper是透過位元組碼生成的,大概是對於傳入的proxy實現類進一步抽象,可以根據方法名,引數等去呼叫相應實現類的方法。更多可以看看這篇文章:

然後wrapper被封裝到一個Invoker裡面,url主要放在invoker裡面。

Dubbo 服務暴露過程分析

然後回到前面,用生成的invoker再封裝成一個DelegateProviderMetaDataInvoker,這個類跟invoker區別不大,只是多放了this這個serviceBean的資訊,方便後面使用

然後呼叫用portocol的export方法,我們看看portocol是哪來的

Dubbo 服務暴露過程分析

這個是又是熟悉的SPI技術,我們再看看Portocol介面的SPI標籤

Dubbo 服務暴露過程分析

標籤是dubbo,使用我開啟DubboPortocol類

Dubbo 服務暴露過程分析

然後上面有一些獲取url的引數,還是上面那個例子,打斷點看到的值是:

Dubbo 服務暴露過程分析

具體怎麼拿就不看了。下面主要看看openServer方法。

Dubbo 服務暴露過程分析

這裡也直接跟進來,看到具體的值了,然後到createServer裡面

Dubbo 服務暴露過程分析

也沒太多東西,主要關注Exchangers。bind(url,requestHandler)。

然後我們一路跟進去,發現

Dubbo 服務暴露過程分析

Dubbo 服務暴露過程分析

Dubbo 服務暴露過程分析

預設生成的Exchanger是headerExchanger。我們再看看HeaderExchanger。

Dubbo 服務暴露過程分析

HeaderExchanger的引數是一個Transporters。bind引數,我們繼續跟進去

Dubbo 服務暴露過程分析

然後getTransporter方法繼續跟:

Dubbo 服務暴露過程分析

老套路了,我們看Transporter介面

Dubbo 服務暴露過程分析

看到是nettyTransporter,繼續跟:

Dubbo 服務暴露過程分析

到這裡,終於看到有點熟悉的NettyServer了,我們再看看NettyServer這個類

Dubbo 服務暴露過程分析

這個方法用了父類AbstractServer建構函式,還有ChannelHandlers。wrap方法,我們先看看這個方法

Dubbo 服務暴露過程分析

可以看到ChannelHandlers是一個單例模式,這裡又用了SPI建立了Dispatcher類

Dubbo 服務暴露過程分析

AllDispatcher。NAME 是值是all ,我們開啟AllDispatcher類

Dubbo 服務暴露過程分析

再看AllChannelHandler

Dubbo 服務暴露過程分析

super的父類

Dubbo 服務暴露過程分析

定義了一個執行緒池,SPI建立的,是fixThreadPool,就不進去看了(終於找到jdk基本的類了)

然後關於fixThreadPool的可以看這篇:

還有個DataStore:

Dubbo 服務暴露過程分析

Dubbo 服務暴露過程分析

大體就是一個ConcurrentMap套ConcurrentMap的類,這邊就看到這裡吧

回到nettyServer的父類AbstractServer的構造方法

Dubbo 服務暴露過程分析

然後還是之前那個服務,我們能看到用到了url裡面的引數,所以說dubbo的所有服務的上下文基本都在生成的url裡面了。 這裡我們主要看下doOpen方法

Dubbo 服務暴露過程分析

然後這裡開始基本就是些跟jdk本身比較相關的地方了,還有就是終於看到netty相關的包了。

這裡dubbo封裝了一個NamedThreadFactory(發現dubbo也用了好多工廠模式),開啟這個類

Dubbo 服務暴露過程分析

看到這個類自定義了類名字首和,執行緒組,是個設計不錯的工廠類,我們也可以參考使用。

然後後面就是netty的使用了,另外說一下,dubbo預設用的還是比較老的netty3,用法介紹:

然後netty的詳解看這篇:

netty是同步非阻塞的,阻塞非阻塞是針對應用程式而言的,同步非同步的是針對作業系統而言的。然後這裡自己實現了一個netty的編解碼器,dubbo這邊封裝了很多自己的類,剝絲抽繭其實還是很簡單的,可以參考這篇:

Dubbo 服務暴露過程分析

然後dubbo編碼序列化效率的分析可以看這篇文章:

具體的原始碼我就不細看了,預設的序列化協議是hessian2。

然後回到最前面,想想我們走了這麼遠到底做了什麼?

Dubbo 服務暴露過程分析

是不是給一個service標籤的serviceBean生成了一個代理好的Invoker,這個invoker放在一個叫exporter的物件下,然後這個exporter放在了serviceBean的一個exporters變數下,準備被呼叫,然後建立的nettyServer放在了serviceBean的變數protocol下的一個變數serverMap裡面,這樣一個serverBean的netty服務,方法代理類是不是都生成好了。這裡注意protocol是單例生成的,所以如果有bean開啟過nettyServer,別的bean就不會再開啟。

然後回到很前面的ServiceConfig的doExport裡面:

Dubbo 服務暴露過程分析

發現他把生成好了serviceBean放到了一個ApplicationModel裡面,至此dubbo服務分析到此結束。