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

快速啟動?

dubbo.apache.org
圖標

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

從dubbo的配置講起:

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

spring中增加自定義配置支持 - mahuan2 - 博客園?

www.cnblogs.com
圖標

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

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

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

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

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

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

https://blog.csdn.net/windrui/article/details/52973915?

blog.csdn.net

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

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

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

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

(2)寫ref,不寫class。

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

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

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

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

serviceBean是何時暴露服務的

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

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

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

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

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

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

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

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

serviceBean暴露服務做了哪些事情

我們看看export的源碼:

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

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

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

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

然後繼續回到doExport。

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

Class.forName()用法詳解 - bcombetter - 博客園?

www.cnblogs.com

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

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

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

然後後面checkApplication初始化application變數

checkRegistry初始化registries變數

checkProtocol初始化protocols變數

appendProperties方法前面有提過

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

主要看看doExportUrls方法

然後loadRegistries

這個方法是將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&register=true&registry=zookeeper&timestamp=1528958780785

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

然後這個方法前期就一堆塞參數到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&timestamp=1528959454516&version=1.0.0

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

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

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

java動態代理實現與原理詳細分析 - Gonjian - 博客園?

www.cnblogs.com
圖標

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

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

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

https://blog.csdn.net/qiangcai/article/details/77750541?

blog.csdn.net

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

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

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

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

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

https://blog.csdn.net/synpore/article/details/79148260?

blog.csdn.net

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

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

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

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

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

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

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

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

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

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

默認生成的Exchanger是headerExchanger。我們再看看HeaderExchanger.

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

然後getTransporter方法繼續跟:

老套路了,我們看Transporter介面

看到是nettyTransporter,繼續跟:

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

這個方法用了父類AbstractServer構造函數,還有ChannelHandlers.wrap方法,我們先看看這個方法

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

AllDispatcher.NAME 是值是all ,我們打開AllDispatcher類

再看AllChannelHandler

super的父類

定義了一個線程池,SPI創建的,是fixThreadPool,就不進去看了(終於找到jdk基本的類了)

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

https://blog.csdn.net/czd3355/article/details/52608567?

blog.csdn.net

還有個DataStore:

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

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

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

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

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

看到這個類自定義了類名前綴和,線程組,是個設計不錯的工廠類,我們也可以參考使用。

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

7.3 netty3基本使用 - 趙計剛 - 博客園?

www.cnblogs.com
圖標

然後netty的詳解看這篇:

https://blog.csdn.net/haoyuyang/article/details/53231585?

blog.csdn.net

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

netty之編解碼 - 小不點丶 - 博客園?

www.cnblogs.com
圖標

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

https://blog.csdn.net/moonpure/article/details/53175519?

blog.csdn.net

具體的源碼我就不細看了,默認的序列化協議是hessian2.

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

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

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

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


推薦閱讀:
相关文章