Dubbo擴充套件點載入機制
概述
來源:
Dubbo的擴充套件點載入從JDK標準的SPI(Service Provider Interface)擴充套件點發現機制加強而來。
Dubbo改進了JDK標準的SPI的以下問題:
-
JDK標準的SPI會一次性例項化擴充套件點所有實現,如果有擴充套件實現初始化很耗時,但如果沒用上也載入,會很浪費資源。
-
如果擴充套件點載入失敗,連擴充套件點的名稱都拿不到了。比如:JDK標準的ScriptEngine,通過getName();獲取指令碼型別的名稱,但如果RubyScriptEngine因為所依賴的jruby.jar不存在,導致RubyScriptEngine類載入失敗,這個失敗原因被吃掉了,和ruby對應不起來,當用戶執行ruby指令碼時,會報不支援ruby,而不是真正失敗的原因。
-
增加了對擴充套件點IoC和AOP的支援,一個擴充套件點可以直接setter注入其它擴充套件點。
約定:
在擴充套件類的jar包內,放置擴充套件點配置檔案:META-INF/dubbo/介面全限定名,內容為:配置名=擴充套件實現類全限定名,多個實現類用換行符分隔。
(注意:這裡的配置檔案是放在你自己的jar包內,不是dubbo本身的jar包內,Dubbo會全ClassPath掃描所有jar包內同名的這個檔案,然後進行合併)
擴充套件Dubbo的協議示例:
在協議的實現jar包內放置文字檔案:META-INF/dubbo/com.alibaba.dubbo.rpc.Protocol,內容為:
xxx=com.alibaba.xxx.XxxProtocol
實現內容:
-
package com.alibaba.xxx;
-
import com.alibaba.dubbo.rpc.Protocol;
-
public class XxxProtocol implemenets Protocol {
-
// ...
-
}
注意: 擴充套件點使用單一例項載入(請確保擴充套件實現的執行緒安全性),Cache在ExtensionLoader中
特性
- 擴充套件點自動包裝
- 擴充套件點自動裝配
- 擴充套件點自適應
- 擴充套件點自動啟用
相關文件可以參考dubbo的官方文件 ,本文主要通過分析相關的原始碼來體會dubbo的擴充套件點框架提供的特性。
原始碼分析
dubbo的擴充套件點框架主要位於這個包下:
com.alibaba.dubbo.common.extension
大概結構如下:
- <code>com.alibaba.dubbo.common.extension
- |
- |--factory
- | |--AdaptiveExtensionFactory #稍後解釋
- | |--SpiExtensionFactory #稍後解釋
- |
- |--support
- | |--ActivateComparator
- |
- |--Activate #自動啟用載入擴充套件的註解
- |--Adaptive #自適應擴充套件點的註解
- |--ExtensionFactory #擴充套件點物件生成工廠介面
- |--ExtensionLoader #擴充套件點載入器,擴充套件點的查詢,校驗,載入等核心邏輯的實現類
- |--SPI #擴充套件點註解</code>
其中最核心的類就是ExtensionLoader
,幾乎所有特性都在這個類中實現。
ExtensionLoader
沒有提供public
的構造方法,但是提供了一個public static
的getExtensionLoader
,這個方法就是獲取ExtensionLoader
例項的工廠方法。其public
成員方法中有三個比較重要的方法:
- getActivateExtension :根據條件獲取當前擴充套件可自動啟用的實現
- getExtension : 根據名稱獲取當前擴充套件的指定實現
- getAdaptiveExtension : 獲取當前擴充套件的自適應實現
這三個方法將會是我們重點關注的方法;* 每一個ExtensionLoader
例項僅負責載入特定SPI
擴充套件的實現*。因此想要獲取某個擴充套件的實現,首先要獲取到該擴充套件對應的ExtensionLoader
例項,下面我們就來看一下獲取ExtensionLoader
例項的工廠方法getExtensionLoader
:
-
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
-
if (type == null)
-
throw new IllegalArgumentException("Extension type == null");
-
if(!type.isInterface()) {
-
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
-
}
-
if(!withExtensionAnnotation(type)) { // 只接受使用@SPI註解註釋的介面型別
-
throw new IllegalArgumentException("Extension type(" + type +
-
") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
-
}
-
// 先從靜態快取中獲取對應的ExtensionLoader例項
-
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
-
if (loader == null) {
-
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); // 為Extension型別建立ExtensionLoader例項,並放入靜態快取
-
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
-
}
-
return loader;
-
}
該方法需要一個Class
型別的引數,該引數表示希望載入的擴充套件點型別,該引數必須是介面,且該介面必須被@SPI
註解註釋,否則拒絕處理。檢查通過之後首先會檢查ExtensionLoader快取中是否已經存在該擴充套件對應的ExtensionLoader
,如果有則直接返回,否則建立一個新的ExtensionLoader
負責載入該擴充套件實現,同時將其快取起來。可以看到對於每一個擴充套件,dubbo中只會有一個對應的ExtensionLoader
例項。
接下來看下ExtensionLoader
的私有建構函式:
-
private ExtensionLoader(Class<?> type) {
-
this.type = type;
-
// 如果擴充套件型別是ExtensionFactory,那麼則設定為null
-
// 這裡通過getAdaptiveExtension方法獲取一個執行時自適應的擴充套件型別(每個Extension只能有一個@Adaptive型別的實現,如果沒有dubbo會動態生成一個類)
-
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
-
}
這裡儲存了對應的擴充套件型別,並且設定了一個額外的objectFactory
屬性,他是一個ExtensionFactory
型別,ExtensionFactory
主要用於載入擴充套件的實現:
-
@SPI
-
public interface ExtensionFactory {
-
/**
-
* Get extension.
-
*
-
* @param type object type.
-
* @param name object name.
-
* @return object instance.
-
*/
-
<T> T getExtension(Class<T> type, String name);
-
}
同時ExtensionFactory
也被@SPI
註解註釋,說明他也是一個擴充套件點,從前面com.alibaba.dubbo.common.extension
包的結構圖中可以看到,dubbo內部提供了兩個實現類:SpiExtensionFactory
和 AdaptiveExtensionFactory
,實際上還有一個SpringExtensionFactory
,不同的實現可以已不同的方式來完成擴充套件點實現的載入,這塊稍後再來學習。從ExtensionLoader
的建構函式中可以看到,如果要載入的擴充套件點型別是ExtensionFactory
是,object
欄位被設定為null。由於ExtensionLoader
的使用範圍有限(基本上侷限在ExtensionLoader
中),因此對他做了特殊對待:在需要使用ExtensionFactory
的地方,都是通過對應的自適應實現來代替。
預設的ExtensionFactory
實現中,AdaptiveExtensionFactotry
被@Adaptive
註解註釋,也就是它就是ExtensionFactory
對應的自適應擴充套件實現(每個擴充套件點最多隻能有一個自適應實現,如果所有實現中沒有被@Adaptive
註釋的,那麼dubbo會動態生成一個自適應實現類),也就是說,所有對ExtensionFactory
呼叫的地方,實際上呼叫的都是AdpativeExtensionFactory
,那麼我們看下他的實現程式碼:
-
@Adaptive
-
public class AdaptiveExtensionFactory implements ExtensionFactory {
-
private final List<ExtensionFactory> factories;
-
public AdaptiveExtensionFactory() {
-
ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
-
List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
-
for (String name : loader.getSupportedExtensions()) { // 將所有ExtensionFactory實現儲存起來
-
list.add(loader.getExtension(name));
-
}
-
factories = Collections.unmodifiableList(list);
-
}
-
public <T> T getExtension(Class<T> type, String name) {
-
// 依次遍歷各個ExtensionFactory實現的getExtension方法,一旦獲取到Extension即返回
-
// 如果遍歷完所有的ExtensionFactory實現均無法找到Extension,則返回null
-
for (ExtensionFactory factory : factories) {
-
T extension = factory.getExtension(type, name);
-
if (extension != null) {
-
return extension;
-
}
-
}
-
return null;
-
}
-
}
看完程式碼大家都知道是怎麼回事了,這貨就相當於一個代理入口,他會遍歷當前系統中所有的ExtensionFactory
實現來獲取指定的擴充套件實現,獲取到擴充套件實現或遍歷完所有的ExtensionFactory
實現。這裡呼叫了ExtensionLoader
的getSupportedExtensions
方法來獲取ExtensionFactory
的所有實現,又回到了ExtensionLoader
類,下面我們就來分析ExtensionLoader
的幾個重要的例項方法。
方法呼叫流程
getExtension
- <code>getExtension(name)
- -> createExtension(name) #如果無快取則建立
- -> getExtensionClasses().get(name) #獲取name對應的擴充套件型別
- -> 例項化擴充套件類
- -> injectExtension(instance) # 擴充套件點注入
- -> instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)) #迴圈遍歷所有wrapper實現,例項化wrapper並進行擴充套件點注入</code>
getAdaptiveExtension
- <code>public T getAdaptiveExtension()
- -> createAdaptiveExtension() #如果無快取則建立
- -> getAdaptiveExtensionClass().newInstance() #獲取AdaptiveExtensionClass
- -> getExtensionClasses() # 載入當前擴充套件所有實現,看是否有實現被標註為@Adaptive
- -> createAdaptiveExtensionClass() #如果沒有實現被標註為@Adaptive,則動態建立一個Adaptive實現類
- -> createAdaptiveExtensionClassCode() #動態生成實現類java程式碼
- -> compiler.compile(code, classLoader) #動態編譯java程式碼,載入類並例項化
- -> injectExtension(instance)</code>
getActivateExtesion
該方法有多個過載方法,不過最終都是呼叫了三個引數的那一個過載形式。其程式碼結構也相對剪短,就不需要在列出概要流程了。
詳細程式碼分析
getAdaptiveExtension
從前面ExtensionLoader
的私有建構函式中可以看出,在選擇ExtensionFactory
的時候,並不是呼叫getExtension(name)
來獲取某個具體的實現類,而是呼叫getAdaptiveExtension
來獲取一個自適應的實現。那麼首先我們就來分析一下getAdaptiveExtension
這個方法的實現吧:
-
public T getAdaptiveExtension() {
-
Object instance = cachedAdaptiveInstance.get(); // 首先判斷是否已經有快取的例項物件
-
if (instance == null) {
-
if(createAdaptiveInstanceError == null) {
-
synchronized (cachedAdaptiveInstance) {
-
instance = cachedAdaptiveInstance.get();
-
if (instance == null) {
-
try {
-
instance = createAdaptiveExtension(); // 沒有快取的例項,建立新的AdaptiveExtension例項
-
cachedAdaptiveInstance.set(instance);
-
} catch (Throwable t) {
-
createAdaptiveInstanceError = t;
-
throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
-
}
-
}
-
}
-
}
-
else {
-
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
-
}
-
}
-
return (T) instance;
-
}
首先檢查快取的adaptiveInstance是否存在,如果存在則直接使用,否則的話呼叫createAdaptiveExtension
方法來建立新的adaptiveInstance並且快取起來。也就是說對於某個擴充套件點,每次呼叫ExtensionLoader.getAdaptiveExtension
獲取到的都是同一個例項。
-
private T createAdaptiveExtension() {
-
try {
-
return injectExtension((T) getAdaptiveExtensionClass().newInstance()); // 先獲取AdaptiveExtensionClass,在獲取其例項,最後進行注入處理
-
} catch (Exception e) {
-
throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
-
}
-
}
在createAdaptiveExtension
方法中,首先通過getAdaptiveExtensionClass
方法獲取到最終的自適應實現型別,然後例項化一個自適應擴充套件實現的例項,最後進行擴充套件點注入操作。先看一個getAdaptiveExtensionClass
方法的實現:
-
private Class<?> getAdaptiveExtensionClass() {
-
getExtensionClasses(); // 載入當前Extension的所有實現,如果有@Adaptive型別,則會賦值為cachedAdaptiveClass屬性快取起來
-
if (cachedAdaptiveClass != null) {
-
return cachedAdaptiveClass;
-
}
-
return cachedAdaptiveClass = createAdaptiveExtensionClass(); // 沒有找到@Adaptive型別實現,則動態建立一個AdaptiveExtensionClass
-
}
他只是簡單的呼叫了getExtensionClasses
方法,然後在判adaptiveCalss快取是否被設定,如果被設定那麼直接返回,否則呼叫createAdaptiveExntesionClass
方法動態生成一個自適應實現,關於動態生成自適應實現類然後編譯載入並且例項化的過程這裡暫時不分析,留到後面在分析吧。這裡我們看getExtensionClassses
方法:
-
private Map<String, Class<?>> getExtensionClasses() {
-
Map<String, Class<?>> classes = cachedClasses.get(); // 判斷是否已經載入了當前Extension的所有實現類
-
if (classes == null) {
-
synchronized (cachedClasses) {
-
classes = cachedClasses.get();
-
if (classes == null) {
-
classes = loadExtensionClasses(); // 如果還沒有載入Extension的實現,則進行掃描載入,完成後賦值給cachedClasses變數
-
cachedClasses.set(classes);
-
}
-
}
-
}
-
return classes;
-
}
在getExtensionClasses
方法中,首先檢查快取的cachedClasses,如果沒有再呼叫loadExtensionClasses
方法來載入,載入完成之後就會進行快取。也就是說對於每個擴充套件點,其實現的載入只會執行一次。我們看下loadExtensionClasses
方法:
-
private Map<String, Class<?>> loadExtensionClasses() {
-
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
-
if(defaultAnnotation != null) {
-
String value = defaultAnnotation.value(); // 解析當前Extension配置的預設實現名,賦值給cachedDefaultName屬性
-
if(value != null && (value = value.trim()).length() > 0) {
-
String[] names = NAME_SEPARATOR.split(value);
-
if(names.length > 1) { // 每個擴充套件實現只能配置一個名稱
-
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
-
+ ": " + Arrays.toString(names));
-
}
-
if(names.length == 1) cachedDefaultName = names[0];
-
}
-
}
-
// 從配置檔案中載入擴充套件實現類
-
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
-
loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
-
loadFile(extensionClasses, DUBBO_DIRECTORY);
-
loadFile(extensionClasses, SERVICES_DIRECTORY);
-
return extensionClasses;
-
}
從程式碼裡面可以看到,在loadExtensionClasses
中首先會檢測擴充套件點在@SPI
註解中配置的預設擴充套件實現的名稱,並將其賦值給cachedDefaultName
屬性進行快取,後面想要獲取該擴充套件點的預設實現名稱就可以直接通過訪問cachedDefaultName
欄位來完成,比如getDefaultExtensionName
方法就是這麼實現的。從這裡的程式碼中又可以看到,具體的擴充套件實現型別,是通過呼叫loadFile
方法來載入,分別從一下三個地方載入:
- META-INF/dubbo/internal/
- META-INF/dubbo/
- META-INF/services/
那麼這個loadFile
方法則至關重要了,看看其原始碼:
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) { String fileName = dir + type.getName(); // 配置檔名稱,掃描整個classpath try { // 先獲取該路徑下所有檔案 Enumeration<java.net.URL> urls; ClassLoader classLoader = findClassLoader(); if (classLoader != null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } if (urls != null) { // 遍歷這些檔案並進行處理 while (urls.hasMoreElements()) { java.net.URL url = urls.nextElement(); // 獲取配置檔案路徑 try { BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8")); try { String line = null; while ((line = reader.readLine()) != null) { // 一行一行讀取(一行一個配置) final int ci = line.indexOf('#'); if (ci >= 0) line = line.substring(0, ci); line = line.trim(); if (line.length() > 0) { try { String name = null; int i = line.indexOf('='); // 等號分割 if (i > 0) { name = line.substring(0, i).trim(); // 副檔名稱 line = line.substring(i + 1).trim(); // 擴充套件實現類 } if (line.length() > 0) { Class<?> clazz = Class.forName(line, true, classLoader); // 載入擴充套件實現類 if (! type.isAssignableFrom(clazz)) { // 判斷型別是否匹配 throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface."); } if (clazz.isAnnotationPresent(Adaptive.class)) { // 判斷該實現類是否@Adaptive,是的話不會放入extensionClasses/cachedClasses快取 if(cachedAdaptiveClass == null) { // 第一個賦值給cachedAdaptiveClass屬性 cachedAdaptiveClass = clazz; } else if (! cachedAdaptiveClass.equals(clazz)) { // 只能有一個@Adaptive實現,出現第二個就報錯了 throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); } } else { // 不是@Adaptive型別 try { clazz.getConstructor(type); // 判斷是否Wrapper型別 Set<Class<?>> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); wrappers = cachedWrapperClasses; } wrappers.add(clazz); //放入到Wrapper實現類快取中 } catch (NoSuchMethodException e) { //不是Wrapper型別,普通實現型別 clazz.getConstructor(); if (name == null || name.length() == 0) { name = findAnnotationName(clazz); if (name == null || name.length() == 0) { if (clazz.getSimpleName().length() > type.getSimpleName().length() && clazz.getSimpleName().endsWith(type.getSimpleName())) { name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase(); } else { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url); } } } String[] names = NAME_SEPARATOR.split(name); // 看是否配置了多個name if (names != null && names.length > 0) { Activate activate = clazz.getAnnotation(Activate.class); // 是否@Activate型別 if (activate != null) { cachedActivates.put(names[0], activate);// 是則放入cachedActivates快取 } // 遍歷所有name for (String n : names) { if (! cachedNames.containsKey(clazz)) { cachedNames.put(clazz, n); // 放入Extension實現類與名稱對映快取,每個class只對應第一個名稱有效 } Class<?> c = extensionClasses.get(n); if (c == null) { extensionClasses.put(n, clazz); // 放入到extensionClasses快取,多個name可能對應一個Class } else if (c != clazz) { // 存在重名 throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); } } } } } } } catch (Throwable t) { IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t); exceptions.put(line, e); } } } // end of while read lines } finally { reader.close(); } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", class file: " + url + ") in " + url, t); } } // end of while urls } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t); } }
程式碼比較長,大概的事情呢就是解析配置檔案,獲取擴充套件點實現對應的名稱和實現類,並進行分類處理和快取。當loadFile
方法執行完成之後,以下幾個變數就會被附上值:
- cachedAdaptiveClass : 當前Extension型別對應的AdaptiveExtension型別(只能一個)
- cachedWrapperClasses : 當前Extension型別對應的所有Wrapper實現型別(無順序)
- cachedActivates : 當前Extension實現自動啟用實現快取(map,無序)
- cachedNames : 擴充套件點實現類對應的名稱(如配置多個名稱則值為第一個)
當loadExtensionClasses
方法執行完成之後,還有一下變數被賦值:
- cachedDefaultName : 當前擴充套件點的預設實現名稱
當getExtensionClasses
方法執行完成之後,除了上述變數被賦值之外,還有以下變數被賦值:
- cachedClasses : 擴充套件點實現名稱對應的實現類(一個實現類可能有多個名稱)
其實也就是說,在呼叫了getExtensionClasses
方法之後,當前擴充套件點對應的實現類的一些資訊就已經載入進來了並且被快取了。後面的許多操作都可以直接通過這些快取資料來進行處理了。
回到createAdaptiveExtension
方法,他呼叫了getExtesionClasses
方法載入擴充套件點實現資訊完成之後,就可以直接通過判斷cachedAdaptiveClass
快取欄位是否被賦值盤確定當前擴充套件點是否有預設的AdaptiveExtension實現。如果沒有,那麼就呼叫createAdaptiveExtensionClass
方法來動態生成一個。在dubbo的擴充套件點框架中大量的使用了快取技術。
建立自適應擴充套件點實現型別和例項化就已經完成了,下面就來看下擴充套件點自動注入的實現injectExtension
:
-
private T injectExtension(T instance) {
-
try {
-
if (objectFactory != null) {
-
for (Method method : instance.getClass().getMethods()) {
-
if (method.getName().startsWith("set")
-
&& method.getParameterTypes().length == 1
-
&& Modifier.isPublic(method.getModifiers())) {// 處理所有set方法
-
Class<?> pt = method.getParameterTypes()[0];// 獲取set方法引數型別
-
try {
-
// 獲取setter對應的property名稱
-
String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
-
Object object = objectFactory.getExtension(pt, property); // 根據型別,名稱資訊從ExtensionFactory獲取
-
if (object != null) { // 如果不為空,說set方法的引數是擴充套件點型別,那麼進行注入
-
method.invoke(instance, object);
-
}
-
} catch (Exception e) {
-
logger.error("fail to inject via method " + method.getName()
-
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
-
}
-
}
-
}
-
}
-
} catch (Exception e) {
-
logger.error(e.getMessage(), e);
-
}
-
return instance;
-
}
這裡可以看到,擴充套件點自動注入的一句就是根據setter方法對應的引數型別和property名稱從ExtensionFactory
中查詢,如果有返回擴充套件點例項,那麼就進行注入操作。到這裡getAdaptiveExtension
方法就分析完畢了。
getExtension
這個方法的主要作用是用來獲取ExtensionLoader
例項代表的擴充套件的指定實現。已擴充套件實現的名字作為引數,結合前面學習getAdaptiveExtension
的程式碼,我們可以推測,這方法中也使用了在呼叫getExtensionClasses
方法的時候收集並快取的資料,其中涉及到名字和具體實現型別對應關係的快取屬性是cachedClasses
。具體是是否如我們猜想的那樣呢,學習一下相關程式碼就知道了:
-
public T getExtension(String name) {
-
if (name == null || name.length() == 0)
-
throw new IllegalArgumentException("Extension name == null");
-
if ("true".equals(name)) { // 判斷是否是獲取預設實現
-
return getDefaultExtension();
-
}
-
Holder<Object> holder = cachedInstances.get(name);// 快取
-
if (holder == null) {
-
cachedInstances.putIfAbsent(name, new Holder<Object>());
-
holder = cachedInstances.get(name);
-
}
-
Object instance = holder.get();
-
if (instance == null) {
-
synchronized (holder) {
-
instance = holder.get();
-
if (instance == null) {
-
instance = createExtension(name);// 沒有快取例項則建立
-
holder.set(instance);// 快取起來
-
}
-
}
-
}
-
return (T) instance;
-
}
接著看createExtension
方法的實現:
-
private T createExtension(String name) {
-
Class<?> clazz = getExtensionClasses().get(name); // getExtensionClass內部使用cachedClasses快取
-
if (clazz == null) {
-
throw findException(name);
-
}
-
try {
-
T instance = (T) EXTENSION_INSTANCES.get(clazz); // 從已建立Extension例項快取中獲取
-
if (instance == null) {
-
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
-
instance = (T) EXTENSION_INSTANCES.get(clazz);
-
}
-
injectExtension(instance); // 屬性注入
-
// Wrapper型別進行包裝,層層包裹
-
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
-
if (wrapperClasses != null && wrapperClasses.size() > 0) {
-
for (Class<?> wrapperClass : wrapperClasses) {
-
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
-
}
-
}
-
return instance;
-
} catch (Throwable t) {
-
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
-
type + ") could not be instantiated: " + t.getMessage(), t);
-
}
-
}
從程式碼中可以看到,內部呼叫了getExtensionClasses
方法來獲取當前擴充套件的所有實現,而getExtensionClassse
方法會在第一次被呼叫的時候將結果快取到cachedClasses
變數中,後面的呼叫就直接從快取變數中獲取了。這裡還可以看到一個快取EXTENSION_INSTANCES
,這個快取是ExtensionLoader
的靜態成員,也就是全域性快取,存放著所有的擴充套件點實現型別與其對應的已經例項化的例項物件(是所有擴充套件點,不是某一個擴充套件點),也就是說所有的擴充套件點實現在dubbo中最多都只會有一個例項。
拿到擴充套件點實現型別對應的例項之後,呼叫了injectExtension
方法對該例項進行擴充套件點注入,緊接著就是遍歷該擴充套件點介面的所有Wrapper來對真正的擴充套件點例項進行Wrap操作,都是對通過將上一次的結果作為下一個Wrapper的建構函式引數傳遞進去例項化一個Wrapper物件,最後總返回回去的是Wrapper型別的例項而不是具體實現類的例項。
這裡或許有一個疑問: 從程式碼中看,不論instance
是否存在於EXTENSION_INSTANCE
,都會進行擴充套件點注入和Wrap操作。那麼如果對於同一個擴充套件點,呼叫了兩次createExtension
方法的話,那不就進行了兩次Wrap操作麼?
如果外部能夠直接呼叫createExtension
方法,那麼確實可能出現這個問題。但是由於createExtension
方法是private
的,因此外部無法直接呼叫。而在ExtensionLoader
類中呼叫它的getExtension
方法(只有它這一處呼叫),內部自己做了快取(cachedInstances
),因此當getExtension
方法內部呼叫了一次createExtension
方法之後,後面對getExtension
方法執行同樣的呼叫時,會直接使用cachedInstances
快取而不會再去呼叫createExtension
方法了。
getActivateExtension
getActivateExtension
方法主要獲取當前擴充套件的所有可自動啟用的實現。可根據入參(values)調整指定實現的順序,在這個方法裡面也使用到getExtensionClasses
方法中收集的快取資料。
-
public List<T> getActivateExtension(URL url, String[] values, String group) {
-
List<T> exts = new ArrayList<T>();
-
List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values); // 解析配置要使用的名稱
-
// 如果未配置"-default",則載入所有Activates擴充套件(names指定的擴充套件)
-
if (! names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
-
getExtensionClasses(); // 載入當前Extension所有實現,會獲取到當前Extension中所有@Active實現,賦值給cachedActivates變數
-
for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) { // 遍歷當前擴充套件所有的@Activate擴充套件
-
String name = entry.getKey();
-
Activate activate = entry.getValue();
-
if (isMatchGroup(group, activate.group())) { // 判斷group是否滿足,group為null則直接返回true
-
T ext = getExtension(name); // 獲取擴充套件示例
-
// 排除names指定的擴充套件;並且如果names中沒有指定移除該擴充套件(-name),且當前url匹配結果顯示可啟用才進行使用
-
if (! names.contains(name)
-
&& ! names.contains(Constants.REMOVE_VALUE_PREFIX + name)
-
&& isActive(activate, url)) {
-
exts.add(ext);
-
}
-
}
-
}
-
Collections.sort(exts, ActivateComparator.COMPARATOR); // 預設排序
-
}
-
// 對names指定的擴充套件進行專門的處理
-
List<T> usrs = new ArrayList<T>();
-
for (int i = 0; i < names.size(); i ++) { // 遍歷names指定的副檔名
-
String name = names.get(i);
-
if (! name.startsWith(Constants.REMOVE_VALUE_PREFIX)
-
&& ! names.contains(Constants.REMOVE_VALUE_PREFIX + name)) { // 未設定移除該擴充套件
-
if (Constants.DEFAULT_KEY.equals(name)) { // default表示上面已經載入並且排序的exts,將排在default之前的Activate擴充套件放置到default組之前,例如:ext1,default,ext2
-
if (usrs.size() > 0) { // 如果此時user不為空,則user中存放的是配置在default之前的Activate擴充套件
-
exts.addAll(0, usrs); // 注意index是0,放在default前面
-
usrs.clear(); // 放到default之前,然後清空
-
}
-
} else {
-
T ext = getExtension(name);
-
usrs.add(ext);
-
}
-
}
-
}
-
if (usrs.size() > 0) { // 這裡留下的都是配置在default之後的
-
exts.addAll(usrs); // 新增到default排序之後
-
}
-
return exts;
-
}
總結
基本上將dubbo的擴充套件點載入機制學習了一遍,有幾點可能需要注意的地方:
- 每個
ExtensionLoader
例項只負責載入一個特定擴充套件點實現 - 每個擴充套件點對應最多隻有一個
ExtensionLoader
例項 - 對於每個擴充套件點實現,最多隻會有一個例項
- 一個擴充套件點實現可以對應多個名稱(逗號分隔)
- 對於需要等到執行時才能決定使用哪一個具體實現的擴充套件點,應獲取其自使用擴充套件點實現(AdaptiveExtension)
@Adaptive
註解要麼註釋在擴充套件點@SPI
的方法上,要麼註釋在其實現類的類定義上- 如果
@Adaptive
註解註釋在@SPI
介面的方法上,那麼原則上該介面所有方法都應該加@Adaptive
註解(自動生成的實現中預設為註解的方法拋異常) - 每個擴充套件點最多隻能有一個被AdaptiveExtension
- 每個擴充套件點可以有多個可自動啟用的擴充套件點實現(使用
@Activate
註解) - 由於每個擴充套件點實現最多隻有一個例項,因此擴充套件點實現應保證執行緒安全
- 如果擴充套件點有多個Wrapper,那麼最終其執行的順序不確定(內部使用
ConcurrentHashSet
儲存)
TODO:
- 學習一下動態生成
AdaptiveExtension
類的實現過程
官方文件描述動態生成的AdaptiveExtension
程式碼如下:
-
package <擴充套件點介面所在包>;
-
public class <擴充套件點介面名>$Adpative implements <擴充套件點介面> {
-
public <有@Adaptive註解的介面方法>(<方法引數>) {
-
if(是否有URL型別方法引數?) 使用該URL引數
-
else if(是否有方法型別上有URL屬性) 使用該URL屬性
-
# <else 在載入擴充套件點生成自適應擴充套件點類時拋異常,即載入擴充套件點失敗!>
-
if(獲取的URL == null) {
-
throw new IllegalArgumentException("url == null");
-
}
-
根據@Adaptive註解上宣告的Key的順序,從URL獲致Value,作為實際擴充套件點名。
-
如URL沒有Value,則使用預設擴充套件點實現。如沒有擴充套件點, throw new IllegalStateException("Fail to get extension");
-
在擴充套件點實現呼叫該方法,並返回結果。
-
}
-
public <有@Adaptive註解的介面方法>(<方法引數>) {
-
throw new UnsupportedOperationException("is not adaptive method!");
-
}
-
}
規則如下:
- 先在URL上找
@Adaptive
註解指定的Extension名; - 如果不設定則預設使用Extension介面類名的點分隔小寫字串(即對於Extension介面com.alibaba.dubbo.xxx.YyyInvokerWrapper的預設值為String[] {“yyy.invoker.wrapper”})。
- 使用預設實現(@SPI指定),如果沒有設定預設擴充套件,則方法呼叫會丟擲IllegalStateException。