華爲大佬親自手碼Dubbo服務暴露源碼解析!此次夠清楚了吧!

目錄

  • 0.配置解析
  • 1.開始export
  • 2.組裝URL
  • 3.服務暴露
  • 疑問解析

先放一張官網的服務暴露時序圖,對咱們梳理源碼有很大的幫助。注:不管是暴露仍是導出或者是其餘翻譯,都是描述export的,只是翻譯不一樣。面試

 

0.配置解析

​ 在Spring的配置文件中,Dubbo指明瞭DubboNamespaceHandler類做爲標籤解析。spring

 

​ 與服務相關的顯然就是service,找到對應的ServiceBean類,進入這個類,開始服務暴露的源碼分析。這個類位於Dubbo源碼config模塊-spring模塊下的根目錄。apache

1.開始export

​ export也是上面時序圖中最開始的一個方法,從這個方法名也知道,這就是服務暴露或者叫出口最關鍵的方法。進入ServiceBean類,在這個類中一共有兩處調用了此方法。即onApplicationEventafterPropertiesSet,瞭解過Spring Bean生命週期的朋友看到這兩個方法確定眼熟,果真,這個類實現了相關的接口:數組

 

​ 看一下onApplicationEvent方法:緩存

 

​ 從它的if判斷條件調用的幾個方法名能夠看出,若是是延遲暴露、還未暴露過且支持暴露就能夠執行export方法了。這裏說一下,這個isDelay方法有點迷惑,字面意思應該爲是否延遲,返回ture表明延遲。可是實際意思卻爲返回true表明不延遲,由於這個判斷條件是delaynull || delay-1,表明沒有設置延遲。因此這個方法中的export纔是第一個觸發的。網絡

​ 接着進入到export方法。這個方法會跳轉到ServiceConfig類,是ServiceBean的父類,也正好符合 時序圖。app

 

​ 這幾個if的做用就是判斷是否須要暴露和延遲暴露。若是不須要暴露就返回,不然都會執行doExport方法的。進入這個方法,這個方法代碼不少,前面一堆if都是檢測配置信息的,關注的重點在doExportUrls方法。框架

 

​ Dubbo是支持多註冊中心和多協議的,在這裏就表現出來了。獲取到的註冊中心URL放到一個list裏面。其中loadRegistries方法就是根據配置組裝成相關的URL並返回,如加載註冊中心地址、檢查地址是否合法、添加配置信息等。我們先關注重點,這個方法就不跟下去了,否則沒完沒了。至於組裝後的URL能夠debug本身看看,大概樣子以下:jvm

 

2.組裝URL

​ 進入到doExportUrlsFor1Protocol方法,這個比較重要。從它的名字能夠看出,它的做用是組裝暴露URL。ide

 

​ 這個方法很長,主要就是建立一個map而後添加各類值,包括配置信息、提供的服務等等。因爲這個方法分支很是多,官網給了各個分支含義的解釋,配合源碼能很好理解其意思:

// 獲取 ArgumentConfig 列表
for (遍歷 ArgumentConfig 列表) {
    if (type 不爲 null,也不爲空串) {    // 分支1
        1. 經過反射獲取 interfaceClass 的方法列表
        for (遍歷方法列表) {
            1. 比對方法名,查找目標方法
        	2. 經過反射獲取目標方法的參數類型數組 argtypes
            if (index != -1) {    // 分支2
                1. 從 argtypes 數組中獲取下標 index 處的元素 argType
                2. 檢測 argType 的名稱與 ArgumentConfig 中的 type 屬性是否一致
                3. 添加 ArgumentConfig 字段信息到 map 中,或拋出異常
            } else {    // 分支3
                1. 遍歷參數類型數組 argtypes,查找 argument.type 類型的參數
                2. 添加 ArgumentConfig 字段信息到 map 中
            }
        }
    } else if (index != -1) {    // 分支4
		1. 添加 ArgumentConfig 字段信息到 map 中
    }
}

​ 固然,若是你沒有配置相關的信息,如dubbo:method,在debug源碼時,壓根就不會進入到這些分支裏面。如今咱們看一下URL長啥樣:

 

​ 能夠看到協議已經變成了dubbo,具體的服務接口也顯示了出來。而map的值就存在parameters當中。

3.服務暴露

​ 依舊在doExportUrlsFor1Protocol方法裏,具體的服務URL已經組裝好了,接下來就是服務暴露了。先看這麼一段代碼:

 

​ 這段代碼有兩個關鍵點,已經在圖中標註。第一處是先進行本地暴露。第二處判斷若是有註冊中心,就會進行遠程暴露。註冊中心的URL在doExportUrls中已經獲取了。

​ 先看本地暴露,進入到exportLocal方法:

 

​ exportLocal方法比較簡單,根據協議頭判斷是否須要暴露服務,若是須要,就建立一個新的URL

​ 咱們看一下這個URL長啥樣:

 

​ 協議變成了injvm,從這個協議名稱就能夠猜想到,這個在一個jvm內的協議。IP地址也從遠程註冊中心的IP地址變成了本機地址。

​ 本地URL組裝好後,會建立一個exporter對象。這個對象是由protocol的export方法生成,咱們點進這個抽象方法,會發現它有一個@Adaptive註解。這個註解修飾方法時會生成一個代理類。主要配合SPI機制使用,SPI的做用簡單的說就是提供一個標準化的接口,可能有不一樣的實現,而這個實現類的路徑咱們就放在一個固定的位置,讓框架去讀取。一樣的用法也在proxyFactory.getInvoker()中。關於SPI的解析放在最後。這個export的具體實現方法以下圖:

 

​ 所在類爲InjvmProtocol。這個實現方法就不說了,主要就是根據傳入的參數進行封裝,咱們直接看最終的exporter:

 

​ 能夠看到,已經找到了服務接口的實現類了。最後就是將exporter添加到exporters中,這個exporters是本地的一個集合,專門緩存exporter。

​ 接着就是遠程暴露了,其實和本地暴露的目的同樣,都要封裝成invoker——>exporter,最後添加到exporters中,還多了一步註冊。首先依舊是經過getInvoker封裝成invoker。(這裏說句題外話,能夠根據參數的協議類型找到這些抽象方法的實現類。Dubbo命名很嚴謹,好比參數中,URL的協議爲registry,那麼其實現類就是RegistryProtocol。至於爲何要封裝成invoker咱們最後再分析,如今只需理解這麼作是爲了屏蔽細節,統一暴露)。

 

​ 封裝成invoker後又弄了一層wrapperInvoker,點進這個類,能夠發現其實就給invoker額外封裝一層,能夠提供更多信息以及一些工具方法,好比ServiceConfig、檢測是否有效。

​ 接着主要區別在export方法當中,其實現方法在RegistryProtocol類中(由於參數wrapperInvoker的url協議爲registry)。實現方法部分截圖以下:

 

​ 這個方法主要作了以下工做:

​ 1.調用doLocalExport導出服務

​ 2.向註冊中心註冊

​ 3.向註冊中心訂閱override數據

​ 4.建立並返回DestroyableExporter

​ 首先進入到doLocalExport方法,這個方法主要就是會調用DubboProtocol的export方法,爲了不過多的代碼截圖把本身弄昏了,就不貼這個方法了。這個方法開頭一樣的,根據invoker獲取URL,關鍵在於它調用了一個openServer。看到這個方法名應該知道是啥意思了,即打開服務。好傢伙,終於要結束了麼。

 

​ 這個方法很清晰,獲取註冊中心的IP和端口號、檢查緩存、建立server。接着跟進源碼,bind過程,主要關注Transports的bind方法。這裏Dubbo也是用Adaptive註解和SPI機制,實現了拓展功能。它會根據傳入的參數選擇不一樣類型的Transport,默認是NettyTransporter。接下來就是Netty服務啓動的相關過程了,之前寫過相關博客,就不跟進了。

​ 接着,咱們看上上張截圖,有一個if會判斷是否須要註冊,若是須要註冊就會向註冊中心註冊。咱們接着跟蹤源碼,一直到以下方法:

 

​ 看到了Zookeeper客戶端,到這裏就明白了,是向Zookeeper添加信息。咱們最後看一下Zookeeper裏面的內容。咱們打開Zookeeper客戶端,查看一下服務:

 

​ 能夠發現,已經有咱們註冊的服務了。最好下個可視化的Zookeeper客戶端,能夠進入到這些目錄,能夠找到Provider的IP地址。

疑問解析

  • 爲何要本地暴露?
  • 調用本地服務時,避免網絡通訊。
  • 爲何要封裝成invoker和export?
  • 前面的源碼分析中,本地和遠程都通過了封裝invoker和export兩個步驟。export是服務暴露的最終形態,其包含invoker以及其餘更多信息,好比註冊中心、服務接口、實現類等等信息。下面是官網的一張截圖:

 

  • 官網是這麼說的:因爲 Invoker 是 Dubbo 領域模型中很是重要的一個概念,不少設計思路都是向它靠攏、或轉換爲它。這個所謂的靠攏就如圖中顯示的那樣,無論在消費者方仍是服務提供方,均會出現Invoker,它表明一個可執行體,並屏蔽了內部細節。既然它這麼重要,咱們就看一下它是若是建立的。其是由proxyFactory.getInvoker建立而來,經過debug找到它的實現類:

 

  • 上面的方法在JavassistProxyFactory類中,其中寫了doInvoke方法,比較簡單,只是轉發了invokeMethod。其中AbstractProxyInvoker是一個抽象類,實現了Invoker接口。而這個Wrapper的做用是包裹目標類,僅可經過getWrapper(Classs)建立子類。子類能夠對入參Class進行解析,拿到類方法、成員變量等信息。在這裏,目標類就是暴露服務的實現類。關於Wrapper的分析內容很是多,這裏記錄一下官網的解析:http://dubbo.apache.org/zh/docs/v2.7/dev/source/export-service/#221-invoker-%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B。
  • SPI是什麼?
  • SPI(Service Provider Interface),其做用前面也說了,就是定義一個標準接口,這個接口的實現由用戶決定。這樣作的好處就是提升了框架的拓展性。可是這個接口的實現放在哪,得讓框架知道。在Java SPI中,規定在META-INF/services/ 目錄下,建立一個以接口全路徑名命名的文件,文件中寫出接口實現類的全路徑名。而後Java就會去遍歷加載這些實現類並建立實例。前面說了Java SPI,可是Dubbo並無用Java規定的方法,而是本身實現了SPI機制。能夠從ServiceLoader.load()方法跟蹤源碼看一下,Java SPI機制是遍歷了全部的實現類,而不是按需加載,形成了沒必要要的浪費。說到Dubbo SPI,那麼它的規定目錄在哪?在META-INF/dubbo/internal目錄下。咱們從源碼的該路徑下找個文件看看。

 

​ 能夠看到Dubbo SPI的配置文件內容是鍵值對的形式,這樣就能夠實現按需加載。根據key值,獲取全路徑名,而後加載。 若是須要本身自定義,就直接在MEATA-INF/dubbo/目錄下建立配置文件便可。一樣的,相似Java SPI中的ServiceLoader,Dubbo中叫ExtensionLoader。這個類的幾個方法,做用很明確,也不復雜,這裏就不跟蹤了。其中getExtensionLoader方法,入參是須要加載的接口,這個方法會檢查是否有對應類型的ExtensionLoader對象,若是沒有就新建一個。createExtension方法就是根據名字獲取對應的實現類,這樣就實現了按需加載。

 

最後還給你們整理了一份面試寶典!有須要的只須要添加小編的vx:mxzFAFAFA便可免費獲取!

相關文章
相關標籤/搜索