Dubbo——ExtensionLoader的工作原理

ExtensionLoader的工作原理

ExtensionLoader是整個擴展機制的主要邏輯類,在這個類裏面實現了配置的加載、擴展類緩存、自適應對象生成等所有工作。

工作流程

ExtensionLoader的邏輯入口可以分爲getExtension、getAdptiveExtension、getActivateExtension三個,分別是獲取普通擴展類、獲取自適應擴展類、獲取自動激活的擴展類。總體邏輯都是從調用這三個方法開始的,每個方法可能會有不同的重載方法,根據不同的傳入參數進行調整:
在這裏插入圖片描述
三個入口中,getActivateExtension對getExtension的依賴比較重,getAdaptiveExtension則相對獨立。

getActivateExtension

getActivateExtension方法只是根據不同的條件同時激活多個普通擴展類。因此,該方法中只會做一些通用的邏輯判斷,如果接口是否包含@Activate註解、匹配條件是否符合等。最終還是調用getExtesion方法獲得具體擴展點實現類。

getExtension(String name)

getExtension(String name)是整個擴展加載器中最核心的方法,實現了一個完整的普通擴展類加載過程。加載過程中的每一步,都會先檢查緩存中是否已經存在所需的數據,如果存在則直接從緩存中讀取,沒有則重新加載。這個方法每次只會根據名稱返回一個擴展點實現類。

初始化過程可以分爲4步:

  1. 框架讀取SPI對應路徑下的配置文件,並根據配置加載所有擴展類並緩存(不初始化)。
  2. 根據傳入的名稱初始化對應的擴展類。
  3. 嘗試查找符合條件的包裝類:
    3.1 包含擴展點的setter方法,例如setProtocol(Protocol protocol)方法會自動注入protcol擴展點實現;
    3.2 包含與擴展類型相同的構造函數,爲其注入擴展類實例,例如本次初始化了一個ClassA,初始化完成後,會尋找構造參數中需要ClassA的包裝類(Wrapper),然後注入ClassA實例,並初始化這個包裝類。
  4. 返回對應的擴展類實例。
getAdaptiveExtension

getAdaptiveExtension也相對獨立,只有加載配置信息部分與getExtension共用了同一個方法。和獲取普通擴展類一樣,框架會先檢查緩存彙總是否有已經初始化好的Adaptive實例,沒有則調用createAdaptiveExtension重新初始化。

初始化分爲4步:

  1. 和getExtension一樣先加載配置文件。
  2. 生成自適應類的代碼字符串。
  3. 獲取類加載器和編譯器,並用編譯器編譯剛纔生成的代碼字符串。Dubbo以供有三種類型的編譯器實現。
  4. 返回對應的自適應類實例。

GetExtension的實現原理

當調用getExtension(String name)方法是,會先檢查緩存中是否有現成的數據沒有則調用createExtension開始穿件。這裏有個特殊點,如果getExtension傳入的name是true,則加載並返回默認擴展類。

在調用createExtension開始創建的過程中,也會先檢查緩存中是否有配置信息,如果不存在擴展類,則會從META-INF/service/、META-INF/dubbo/、META-INF/dubbo/internal/這幾個路徑中讀取所有的配置文件,通過I/O讀取字符流,然後通過解析字符串,得到配置文件中對應的擴展點實現類的全程(如com.alibaba.dubbo.common.extensionloader.activate.impl.GroupActivateeExtImpl)。

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
加載完擴展點配置之後,再通過反射獲得所有擴展實現類並緩存起來。注意,此處僅僅是把Class加載到JVM中,並沒有做Class初始化。在加載Class文件時,會根據Class上的註解來判斷擴展點類型,在根據類型分類做緩存:
在這裏插入圖片描述
在這裏插入圖片描述
最後根據傳入的name找到對應的類並通過Class.forName方法進行初始化,併爲其注入依賴的其他擴展類(自動加載特性)。當擴展類初始化後,會檢查一次包裝擴展類Set<Class<?>> wrapperClass,查找包含與擴展點類型相同的構造函數,爲其注入剛初始化的擴展類:

在這裏插入圖片描述
在injectExtension方法中可以爲類注入依賴屬性,它使用了ExtensionFactory#getExtension(Class<T> type, String name)來獲取對應的bean實例。

injectExtension方法總體實現了類似Spring的IOC機制,其實現原理比較簡單:首先通過反射獲取類的所有方法,然後遍歷以字符串set開頭的方法,得到set方法的參數類型,再通過ExtensionFactory尋找參數類型相同的擴展類實例,如果找到,就設值進入:

在這裏插入圖片描述

包裝類的構造參數注入,也是通過injectExtension方法實現的。

getAdaptiveExtension的實現原理

在getAdaptiveExtension()方法中,會爲擴展點接口自動生成實現類字符串,實現類主要包含以下邏輯:

爲接口中每個有@Adaptive註解的方法生成默認實現(沒有註解的方法則生成空實現),每個默認實現都會從URL中提取Adaptive參數值,並以此爲一句動態加載擴展點。然後框架使用不同的編譯器,把實現類字符串編譯爲自適應類並返回。

生成代碼的邏輯主要分爲7步:

  1. 生成package、import、類名等頭部信息。此處只會引入一個類ExtensionLoader。爲了不寫其他類的import方法,其他方法調用時全部使用全路徑。類名稱會變爲"接口名稱 + $Adaptive"的格式。例如:Transporter接口會生成Transporter$Adaptive。
  2. 遍歷接口的所有方法,獲取方法的返回類型、參數類型、異常類型等。爲第(3)步判斷是否爲空值做準備。
  3. 生成參數爲空校驗代碼如參數是否爲空的校驗。如果有遠程調用,還會添加Invocation參數爲空的校驗。
  4. 默認生成實現類名稱。如果@Adaptive註解中沒有設定默認值,則根據類名稱生成,如YyyInvokerWrapper會被轉換爲yyy.invoker.wrapper。生成的規則是不斷找大寫字符,並把他們用"."連接起來。得到默認實現類名稱後,還需要知道這個實現是哪個擴展點的。
  5. 生成獲取擴展點名稱的代碼。根據@Adaptive註解中配置的key值生成不同的獲取代碼,例如:如果是@Adaptive(“protocol”),則會生成url.getProtocol()。
  6. 生成獲取具體擴展實現類代碼。最終還通過getExtension(extName)方法獲取字是一個擴展類的真正實現。如果根據URL中配置的key沒有找到對應的實現類,則會使用第(4)步中生成的默認實現類名稱去找。
  7. 生成調用結果代碼

演示代碼生成過程:
SPI配置文件中的配置:
在這裏插入圖片描述
自適應接口,echo方法上有@Adaptive註解:
在這裏插入圖片描述
對應於"impl1"的實現類:
在這裏插入圖片描述
在測試方法中調動這個自適應類:
在這裏插入圖片描述
控制檯打印:
在這裏插入圖片描述

會生成如下自適應代碼:

package org.apache.dubbo.common.extensionloader.adaptive;
//只會導入這一個類,其它類都是以全路徑方式調用
import org.apache.dubbo.common.extension.ExtensionLoader;

public class SimpleExt$Adaptive implements org.apache.dubbo.common.extensionloader.ext1.SimpleExt {
	public String echo(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {
		if(arg0 == null) throw new IllegalArgumentException("url == null");
		org.apache.dubbo.common.URL url = arg0;
		/*
		 * 注意:如果@Adaptive註解沒有傳入key參數,則默認會把類名轉化爲key
		 * 如:SimpleExt會轉化爲 simple.ext
		 * 根據key獲取對應的擴展點實現名稱,第一個參數是key,第二個是獲取不到時的默認值
		 * URL中沒有"simple.ext"這個key,因此extName取值impl1

		 * 如果@Adaptive註解中有key參數,如@Adaptive("key1"),則會變爲
		 * url.getParameter("key1", "impl1");
		 */
		String extName = url.getParameter("simple.ext", "impl");

		if(extName == null) throw new IllegalStateException(...);
		org.apache.dubbo.common.extensionloader.ext1.SimlExt extension = 
		(org.apache.dubbo.common.extsionloader.ext1.SimpleExt)ExtensionLoader
		.getExensionLoader(org.apache.dubbo.common.extesionloader.ext1.SimpleExt.class)
		.getExtension(extName);//實現類變爲配置文件中的SimpleExtImpl1
		
		//最終調用真實的擴展點方法,並返回調用結果
		return extension.echo(arg0, arg1); 
	}
}

生成完代碼之後就要對代碼進行編譯,生成一個新的Class。Dubbo中的編譯器也是一個自適應接口,但@Adaptive註釋是加在實現類AdaptiveCompiler上的。這樣一來AdaptiveCompiler就會作爲該自適應類的默認實現,不需要做代碼生成和編譯就可以使用了。

如果一個接口上既有@SPI(“impl1”),方法上又有@Adaptive(“impl2”)註解,那麼最終動態生成的實現方法會是url.getParameter("impl2", "impl1");,即優先通過@Adaptive註解傳入的key去查找擴展實現類;如果沒找到,則通過@SPI註解中的key去查找;如果@SPI註解中沒有默認值,則把類名轉換爲key,再去查找。

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

getActivateExtension的實現原理

getActivateExtension(URL url, String key, String group)方法可以獲取所有自動激活擴展點。參數分別是URL、URL中指定的key(多個則使用逗號隔開)和URL中指定的組信息(group)。其實現邏輯非常簡單,當調用該方法時,主線流程分爲4步:

  1. 檢查緩存,如果緩存中沒有,則初始化所有擴展實現的集合。
  2. 遍歷整個@Activate註解集合,根據傳入URL匹配條件(匹配group、name等),得到所有符合激活條件的擴展類實現。然後根據@Activate中配置的before、after、order等參數進行排序。
  3. 遍歷所有用戶自定義擴展類名稱,根據用戶URL配置的順序,調整擴展點激活順序(遵循用戶在URL中配置的順序,例如URL爲test://localhost/test?ext=order1,default,則擴展點ext的激活順序會遵循先order1再default,其中default代表所有由@Activate註解的擴展點。)
  4. 返回所有自動激活類集合。

獲取Activate擴展類實現,也是通過getExtension得到的。因此,可以認爲getExtension是其他兩種Extension的基石。

此處有一點需要注意,如果URL的參數中傳入了-default,則所有的默認@Activate都不會被激活,只有URL參數中指定的擴展點會被激活。如果傳入了"-"斧蛤開頭的擴展點名,則該擴展點也不會被自動激活。例如:-xxxx,表示名字爲xxxx的擴展點不會被激活。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章