Dubbo2.7.3版本原始碼學習系列六: Dubbo服務匯出原始碼解析
前言
- 上篇文章: Dubbo2.7.3版本原始碼學習系列五: 學習Dubbo服務匯出原始碼前置知識點(ProxyFactory和Wrapper類),我們們瞭解了Dubbo的ProxyFactory和Wrapper類的作用。本篇文章,我們們開始進入Dubbo服務匯出的原始碼解讀,在這個環節,我們們會用到之前Dubbo系列總結到的知識點(自適應擴充套件類、Wrapper、ProxyFactory)。
一、官網對服務匯出的簡介
-
官網描述
Dubbo 服務匯出過程始於 Spring 容器釋出重新整理事件,Dubbo 在接收到事件後,會立即執行服務匯出邏輯。整個邏輯大致可分為三個部分,第一部分是前置工作,主要用於檢查引數,組裝 URL。第二部分是匯出服務,包含匯出服務到本地 (JVM),和匯出服務到遠端兩個過程。第三部分是向註冊中心註冊服務,用於服務發現。
-
通過官網的描述,我們們可以知道服務匯出的原始碼開始於Spring的重新整理事件(這裡對Spring容器重新整理事件不太瞭解的,可以參考此篇文章spring 5.0.x原始碼學習系列十: 觀察者設計模式與Spring 事件驅動模型)。Dubbo在進行服務匯出時,會做三個步驟:
1、前置工作:主要用於檢查引數、組裝URL <=====> 類似於Spring在初始化bean時定義的一系列BeanDefinition
2、匯出服務:包括服務匯出到本地和匯出到遠端
3、服務註冊:向註冊中心註冊當前被匯出的服務
二、前置工作:檢查引數、組裝URL
- 在Dubbo做前置工作(檢查引數、組裝URL)之前,Dubbo得先把當前服務對應的一些配置從配置檔案或Spring 配置類中解析出來,其次才是執行所謂的前置工作。在這一個個過程中,用到了spring的許多過程點,在這裡,我將用到的spring擴充套件點及其作用都一一羅列了出來。
2.1 Dubbo服務匯出整合Spring的第一個擴充套件點NamespaceHandlerSupport`針對xml而言)
-
我們都知道,使用Dubbo來整合spring時,官方提供給我們兩種方式:
第一:基於註解
第二:基於XML
基於註解的解析,在之前的Dubbo2.7.3版本原始碼學習系列三: Dubbo註解版本載入配置原理文章中有總結到。這裡就不過多解釋了。
基於XML的解析,這裡簡單說明下:
-
以xml的格式來啟動spring上下文,其中我們可以在xml中寫如下類似的標籤:
<dubbo:service id="test" interface="org.apache.dubbo.demo.DemoService" ref="demoService"/>
這種標籤,最終會被spring給解析出來,會對應ServiceBean類。這樣的結果是有地方配置的,詳看org.apache.dubbo.config.spring.schema.DubboNamespaceHandler類。其中主要看它的
init
方法,如下:
在init方法中,當我們配置**<dubbo:service />**類似的標籤時,spring會解析我們的配置併為對應的型別建立BeanDefinition。(每一個dubbo:service標籤對應一個ServiceBean型別的BeanDefinition)。
-
-
因此,通過第一個擴充套件點後,我們在xml配置的資訊已經變成Spring的BeanDefinition了,為spring構建bean建立的條件。
2.2 Dubbo服務匯出整合Spring的第二個擴充套件點BeanPostProcessor
-
先讚歎下Spring的BeanPostProcessor擴充套件點,它真的太牛逼了。可以對bean做任何處理,包括代理、自定義填充屬性等等。其中如果這個bean是實現了不同Aware介面,都會進行回撥。其中對於處理aware的入口為ApplicationContextAwareProcessor類,在此類的postProcessBeforeInitialization方法中會對各種aware做處理。換言之就是:我們可以通過ApplicationContextAwareProcessor類來獲取spring上下文的許多東西,原始碼如下:
-
此擴充套件點的作用比較簡單,就是為bean填充內部需要的相關aware介面。那ServiceBean來舉例,ServiceBean主要實現了ApplicationContextAware和ApplicationEventPublisherAware介面,因此只是為了填充內部的ApplicationEventPublisher和ApplicationContext屬性,僅此而已。
2.3 Dubbo服務匯出整合Spring的第三個擴充套件點InitializingBean
-
此擴充套件點在dubbo中就比較重要了,它是利用此擴充套件點來填充額外的一些配置,比如我們在xml中配置的如下標籤
<dubbo:application name="demo-provider"/> <dubbo:registry address="zookeeper://127.0.0.1:2181" /> <dubbo:protocol name="dubbo"/>
最終都會通過此後置處理器將ServiceBean(拿ServiceBean舉例)內部的一些protocols、application、module、registries等配置屬性給填充,相當於把當前服務與其他共享的配置給關聯起來,進而知道當前服務屬於哪個應用程式、共用哪些協議。當然前提還是得配置好且被spring解析到。在Dubbo這些配置類中,都會存在一個叫ConfigManager的類中,此類為單例(懶漢式),內部存放了當前dubbo應用程式的所有配置,其中包括共用的配置和每個服務私有的配置。
-
因此,通過Spring的InitializingBean擴充套件點,我們可以將當前暴露的服務與其他配置相關聯,比如當前暴露出去的服務支援哪些協議、使用哪個註冊中心、屬於哪個應用程式。
2.4 Dubbo服務匯出整合Spring的第四個擴充套件點ApplicationListener
-
在ServiceBean中,它還實現了ApplicationListener介面。這代表著這個類對spring的ContextRefreshedEvent事件感興趣(詳見
org.springframework.context.support.AbstractApplicationContext#publishEvent(org.springframework.context.ApplicationEvent
)方法,當spring容器初始化後,會發布ContextRefreshedEvent事件,此時就會通知所有訂閱了此事件的監聽者)。同時,在Dubbo服務暴露的開發者文件中也有提到,Dubbo服務暴露的核心就是Spring容器的重新整理事件,如下為Dubbo官網的原話,點此連結檢視:Dubbo 服務匯出過程始於 Spring 容器釋出重新整理事件,Dubbo 在接收到事件後,會立即執行服務匯出邏輯
因此,上述三個擴充套件點都是為Dubbo服務匯出奠定了基礎,服務匯出的第一步不是要檢查引數麼?要檢查,首先得把我們們配置的資訊給解析出來嘛,不然檢查什麼呢?
2.5 從Spring的事件驅動模型擴充套件點開始,正式進入Dubbo的服務匯出邏輯
-
根據我們最開始的總結,服務匯出分為如下三步:
1、前置工作:主要用於檢查引數、組裝URL <=====> 類似於Spring在初始化bean時定義的一系列BeanDefinition 2、匯出服務:包括服務匯出到本地和匯出到遠端 3、服務註冊:向註冊中心註冊當前被匯出的服務
-
其服務匯出入口如下:
PS:Dubbo中使用了大量的責任鏈設計模式,
可以提前瞭解下它
,否則在閱讀原始碼的過程中會遇到一些困難
2.5.1 前置條件的第一步:檢查配置
- 檢查配置的細節在這裡就不詳細說了,大致的就是檢查使用者配置的合法性以及應用程式沒有相關配置時,則使用預設值。同時會開啟註冊中心功能,具體的邏輯可參考org.apache.dubbo.config.ServiceConfig#checkAndUpdateSubConfigs方法。
2.5.2 前置條件的第一步:組裝URL
-
組裝URL的過程建議參考官方文件2.1.3 組裝 URL章節。這裡以如下配置為例描述下生成的URL
在組裝URL的過程中,每個URL將會對應一個操作,比如我們在匯出服務時,只需要將一些描述服務的基本資訊放置在URL中即可。比如,我們需要將服務往註冊中心註冊時,URL中必須包含跟註冊中心相關的資訊才行。因此,在這個過程中,URL是一直在變化的,這個變化的情況視當前要操作的業務邏輯而定。比如,在進行服務匯出時,URL的變化為如下原始碼註釋所示:
其中服務匯出整體的URL結構如下所示:此URL代表著當前服務的一些基本資訊,比如它位於哪一個應用程式(application變數),位於哪一臺主機上(host變數),在spring中對應的bean的名稱(bean.name變數),服務的型別(interface),包含哪些方法(methods)。而在將服務註冊到註冊中心時,URL又是另外一些模樣。這裡等到服務註冊步驟時再羅列出來。
-
組裝URL的步驟比較麻煩,建議仔細閱讀官網,因為URL針對Dubbo而言超級重要,以下文字來自於官網:
URL 是 Dubbo 配置的載體,通過 URL 可讓 Dubbo 的各種配置在各個模組之間傳遞。URL 之於 Dubbo,猶如水之於魚,非常重要。
官網都給我們們開後門了,就差壓著我們們學習URL了,還不加緊把URL整明白?
三、匯出服務
3.1 為匯出的服務構建Invoker物件
-
URL組裝好了之後,我們們就可以大膽的將服務匯出了。
-
為匯出的服務構建Invoker物件的程式碼還是在doExportUrlsFor1Protocol方法中,匯出服務包含兩個:本地匯出和遠端匯出。
-
在本例中,我們配置的註冊中心只有一個,就是我們們配置的registry標籤。
<dubbo:registry address="zookeeper://127.0.0.1:2181" />
因此,當前服務需要被註冊到註冊中心的URL將被解析成如下模樣:
-
為上述配置的服務生成Invoker物件
這裡我們要注意下:構建Invoker物件時,傳入進去的URL並不是我們最開始構建的URL(描述當前服務的URL),而是根據註冊中心配置的URL,那麼我們之前構建的URL有什麼用呢?有用的,我們可以在構建Invoker物件時看到,url最終會變成一個字串(url.toFullString()
)新增到registruURL中去,也就是說,Invoker中的URL,主體是當前遍歷的協議的內容,只不過內部加了一個叫key為**EXPORT_KEY(export)**的引數,其中value為當前暴露出服務的整個URL(即最開始組裝的URL)。如下圖所示(registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString())
程式碼的執行結果):
因此,我們應該能大概明白invoker物件中url屬性的結構了。
3.2 RegistryProtocol作為匯出服務到遠端的入口
-
同樣的,服務匯出的入口也在doExportUrlsFor1Protocol方法中,其原始碼如下:
由註釋中的分析可知,我們們把視角挪到org.apache.dubbo.registry.integration.RegistryProtocol#export
由上述的原始碼註釋可知:RegistryProtocol的export方法中主要做了兩件事情:1、利用服務匯出的URL進行匯出服務 2、向註冊中心註冊服務
3.3 根據服務提供者的配置執行真實的服務匯出邏輯
-
真實的服務匯出邏輯,我們把程式碼定位到:org.apache.dubbo.registry.integration.RegistryProtocol#doLocalExport,其原始碼如下:
正如上述原始碼中的註釋所示,我們忽略Protocol的Wrapper類,直接把程式碼定位到DubboProtocl的export方法
-
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#export原始碼如下:
真實的匯出邏輯比較簡單,就是開啟了一個netty伺服器以及為invoker構建了一個export物件。此段邏輯執行結束後,我們們再把目光放到org.apache.dubbo.registry.integration.RegistryProtocol#export處,我們最開始是從org.apache.dubbo.registry.integration.RegistryProtocol#export內部的doLocalExport方法進入,完成服務匯出邏輯,並返回一個ExporterChangeableWrapper物件。
3.4 匯出服務總結
- 內容比較簡單,主要做了如下兩件事:
- 為匯出的服務構建invoker物件,此invoker物件比較特殊,以註冊中心的url為主體,描述服務的url將作為引數儲存在invoker中。
- 解析invoker物件,拿到註冊中心的url以及描述服務的url,再針對描述服務的url來執行真正的服務匯出邏輯(走具體的服務匯出協議,以及開啟對應的伺服器)
- 按照官網的流程來說的話,服務匯出結束了,剩下的就是服務註冊流程了
四、服務註冊
-
服務註冊的流程我們們繼續接著org.apache.dubbo.registry.integration.RegistryProtocol#export進行分析,其原始碼如下:
在註冊服務的過程中,最終會呼叫register方法,其內部的程式碼大家看了之後應該就能明白了: -
服務註冊流程比較簡單了,就是自適應擴充套件類registryFactory根據url中的protocol屬性來決定使用哪個註冊中心來完成服務註冊。
五、總結
- 整個服務匯出過程是嚴格按照官網的流程來進行總結的,把官方沒有描述的一些技術給完善了。要了解服務匯出的底層原理,首先得把自適應擴充套件機制、ProxyFactory、Wrapper類搞明白,還有個最重要的是要時刻關注URL的變化,因為很多自適應擴充套件機制的程式碼,程式碼沒有變,但因為url的某個引數發生了變化,就變成另外一個類來執行邏輯了。
- 如果你覺得我的文章有用的話,歡迎點贊和關注。?
- I’m a slow walker, but I never walk backwards
相關文章
- Dubbo原始碼學習之-服務匯出原始碼
- Dubbo原始碼解析之服務匯出過程原始碼
- Dubbo服務暴露原始碼解析②原始碼
- Dubbo原始碼解析之服務釋出與註冊原始碼
- Dubbo原始碼解析之服務叢集原始碼
- Dubbo服務呼叫過程原始碼解析④原始碼
- Dubbo原始碼解析之服務引入過程原始碼
- Dubbo原始碼解析之服務呼叫過程原始碼
- Dubbo原理和原始碼解析之服務引用原始碼
- Dubbo原始碼之服務端的釋出原始碼服務端
- Dubbo原始碼解析之服務端接收訊息原始碼服務端
- Dubbo原始碼之服務引用原始碼
- dubbo服務者原始碼分期原始碼
- Dubbo原始碼分析(六)服務引用的具體流程原始碼
- dubbo原始碼分析02:服務引用原始碼
- Dubbo原始碼分析之服務引用原始碼
- Dubbo原始碼分析之服務暴露原始碼
- Dubbo原始碼分析十一、服務路由原始碼路由
- Dubbo原始碼分析(五)Dubbo呼叫鏈-服務端原始碼服務端
- Dubbo原始碼分析(三)Dubbo的服務引用Refer原始碼
- Java併發包原始碼學習系列:同步元件CountDownLatch原始碼解析Java原始碼元件CountDownLatch
- Java併發包原始碼學習系列:同步元件CyclicBarrier原始碼解析Java原始碼元件
- Java併發包原始碼學習系列:同步元件Semaphore原始碼解析Java原始碼元件
- Dubbo原始碼分析(七)服務目錄原始碼
- Dubbo原始碼學習之-通過原始碼看看dubbo對netty的使用原始碼Netty
- Dubbo原始碼解析之SPI原始碼
- dubbo原始碼解析-spi(五)原始碼
- Laravel原始碼解析 — 服務容器Laravel原始碼
- Dubbo 實現原理與原始碼解析系列 —— 精品合集原始碼
- Spring Cloud系列(三):Eureka原始碼解析之服務端SpringCloud原始碼服務端
- EOS原始碼學習系列原始碼
- Dubbo原始碼解析之客戶端初始化及服務呼叫原始碼客戶端
- Java併發包原始碼學習系列:執行緒池ThreadPoolExecutor原始碼解析Java原始碼執行緒thread
- Java併發包原始碼學習系列:JDK1.8的ConcurrentHashMap原始碼解析Java原始碼JDKHashMap
- Java併發包原始碼學習系列:執行緒池ScheduledThreadPoolExecutor原始碼解析Java原始碼執行緒thread
- Spark 原始碼系列(六)Shuffle 的過程解析Spark原始碼
- Spring原始碼深度解析(郝佳)-學習-原始碼解析-Spring MVCSpring原始碼MVC
- Dubbo原始碼學習之-SPI介紹原始碼