本篇文章主要是閱讀了dubbo官方文檔:
關於服務的暴露和引用,相信很多同學瞭解不是很透徹。這篇文件主要是帶大家一步步探究其中的實現,順便記錄下這個過程中學到的其他知識,由於dubbo是一個很成熟的框架了,用到的技術也很多,裡面定義了很多類和介面十分複雜,所以我們一步步去分析篇幅可能有些長,周邊知識也非常多,我在文中都列了相關拓展知識的鏈接。
這裡主要用到了spring自定標籤功能,關於spring自定義配置,大家可以查看:
這裡主要定義了一個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,讀者可以看這篇文章:
大體就是對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裡面實現了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做了哪些事情。
我們看看export的源碼:
主要就判斷該服務是否已經打開,以及是否需要延遲打開,實際邏輯在doExport裡面,我們再看doExport的代碼。
別的沒什麼好說的,monitor是監控中心的配置,registries是註冊中心配置,protocols 是發布者配置,別的暫不知道幹嘛用,暫時不管,這些都會在checkDefault裡面初始化provider這個變數的實話初始化,打開這個類看到
主要就是host,port,環境路徑等信息,然後這些信息都是從系統變數中拿的,看checkDefault裡面就知道。
然後取的都是系統變數裡面的dubbo.前綴的變數,dubbo每個服務運行的時候都自動會設置這些參數吧(我猜)。另外這裡還有個初始化這個類的測試單元,很好理解了。
然後繼續回到doExport。
這裡複習了一下Class.forName,具體內容可以參考:
,主要就是把介面類載入進來。
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®ister=true®istry=zookeeper×tamp=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×tamp=1528959454516&version=1.0.0
上面可以看到,url地址包含了版本號,介面名,方法列表,序列化方法,過期時間等這個介面bean所有需要用到的上下文信息,並且地址頭也由registry改成了dubbo。因為包含了所有上下文的信息,所以這個url的用處很大,後面看代碼就知道了。
這部分後面url還拼加了一些拓展類,監控url等信息,具體就不細看了,主要看看轉成invoker和並創建exporter這個階段
這裡的話,我比較好奇proxyFactory這個變數是如何生成的,這個根據官方文檔說可能是javassistProxyFactory或者JdkProxyFactory中的任意一個,當然,這裡是一個重點,就是動態代理,關於動態代理的知識可以看這裡:
然後javassistProxyFactory和JdkProxyFactory都是動態代理的生成技術,只不過一個是用位元組碼生成,一個是用jdk生成。
然後這個proxyFactory變數的生成我們看到:
然後網上查了下,原來這裡有個很牛批的技術,叫做SPI,相關說明參考:
大致就是同一個介面,可以有不同的實現類,僅通過配置就可以動態修改具體用哪個實現類,而且這個拿到的實現還是單例模式
具體dubbo中的說明文章中也有描述,可以看到
proxyFactory介面寫了SPI標籤,所以這裡默認使用的就是javassistProxyFactory。
然後我們看看javassistProxyFactory的實現裡面寫了什麼東西.
這裡生成了一個Wrapper,這個Wrapper是通過位元組碼生成的,大概是對於傳入的proxy實現類進一步抽象,可以根據方法名,參數等去調用相應實現類的方法。更多可以看看這篇文章:
然後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的可以看這篇:
還有個DataStore:
大體就是一個ConcurrentMap套ConcurrentMap的類,這邊就看到這裡吧
回到nettyServer的父類AbstractServer的構造方法
然後還是之前那個服務,我們能看到用到了url裡面的參數,所以說dubbo的所有服務的上下文基本都在生成的url裡面了。 這裡我們主要看下doOpen方法
然後這裡開始基本就是些跟jdk本身比較相關的地方了,還有就是終於看到netty相關的包了。
這裡dubbo封裝了一個NamedThreadFactory(發現dubbo也用了好多工廠模式),打開這個類
看到這個類自定義了類名前綴和,線程組,是個設計不錯的工廠類,我們也可以參考使用。
然後後面就是netty的使用了,另外說一下,dubbo默認用的還是比較老的netty3,用法介紹:
然後netty的詳解看這篇:
netty是同步非阻塞的,阻塞非阻塞是針對應用程序而言的,同步非同步的是針對操作系統而言的。然後這裡自己實現了一個netty的編解碼器,dubbo這邊封裝了很多自己的類,剝絲抽繭其實還是很簡單的,可以參考這篇:
然後dubbo編碼序列化效率的分析可以看這篇文章:
具體的源碼我就不細看了,默認的序列化協議是hessian2.
然後回到最前面,想想我們走了這麼遠到底做了什麼?
是不是給一個service標籤的serviceBean生成了一個代理好的Invoker,這個invoker放在一個叫exporter的對象下,然後這個exporter放在了serviceBean的一個exporters變數下,準備被調用,然後創建的nettyServer放在了serviceBean的變數protocol下的一個變數serverMap裡面,這樣一個serverBean的netty服務,方法代理類是不是都生成好了。這裡注意protocol是單例生成的,所以如果有bean打開過nettyServer,別的bean就不會再打開。
然後回到很前面的ServiceConfig的doExport裡面:
發現他把生成好了serviceBean放到了一個ApplicationModel裡面,至此dubbo服務分析到此結束。