擴充套件點載入機制(ExtensionLoader)
概述
來源:
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
大概結構如下:
com.alibaba.dubbo.common.extension
|
|--factory
| |--AdaptiveExtensionFactory #稍後解釋
| |--SpiExtensionFactory #稍後解釋
|
|--support
| |--ActivateComparator
|
|--Activate #自動啟用載入擴充套件的註解
|--Adaptive #自適應擴充套件點的註解
|--ExtensionFactory #擴充套件點物件生成工廠介面
|--ExtensionLoader #擴充套件點載入器,擴充套件點的查詢,校驗,載入等核心邏輯的實現類
|--SPI #擴充套件點註解
其中最核心的類就是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
getExtension(name)
-> createExtension(name) #如果無快取則建立
-> getExtensionClasses().get(name) #獲取name對應的擴充套件型別
-> 例項化擴充套件類
-> injectExtension(instance) # 擴充套件點注入
-> instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)) #迴圈遍歷所有wrapper實現,例項化wrapper並進行擴充套件點注入
getAdaptiveExtension
public T getAdaptiveExtension()
-> createAdaptiveExtension() #如果無快取則建立
-> getAdaptiveExtensionClass().newInstance() #獲取AdaptiveExtensionClass
-> getExtensionClasses() # 載入當前擴充套件所有實現,看是否有實現被標註為@Adaptive
-> createAdaptiveExtensionClass() #如果沒有實現被標註為@Adaptive,則動態建立一個Adaptive實現類
-> createAdaptiveExtensionClassCode() #動態生成實現類java程式碼
-> compiler.compile(code, classLoader) #動態編譯java程式碼,載入類並例項化
-> injectExtension(instance)
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。
相關推薦
擴充套件點載入機制(ExtensionLoader)
概述 來源: Dubbo的擴充套件點載入從JDK標準的SPI(Service Provider Interface)擴充套件點發現機制加強而來。 Dubbo改進了JDK標準的SPI的以下問題: JDK標準的SPI會一次性例項化擴充套件點所有實現,如果有
Dubbo——擴充套件點載入機制
一、getAdaptiveExtension()方法:獲取擴充套件點實現類的介面卡類例項。從ExtensionLoader.cachedAdaptiveInstance變數中獲取,若為空,則呼叫createAdaptiveExtension方法建立介面卡類的例項,並將介面卡類的例項快取到cachedAda
Dubbo擴充套件點載入機制
概述 來源: Dubbo的擴充套件點載入從JDK標準的SPI(Service Provider Interface)擴充套件點發現機制加強而來。 Dubbo改進了JDK標準的SPI的以下問題: JDK標準的SPI會一次性例項化擴充套件點所有實現,如果有擴充套件實現
Dubbo 擴充套件點載入機制:從 Java SPI 到 Dubbo SPI
SPI 全稱為 Service Provider Interface,是一種服務發現機制。當程式執行呼叫介面時,會根據配置檔案或預設規則資訊載入對應的實現類。所以在程式中並沒有直接指定使用介面的哪個實現,而是在外部進行裝配。 要想了解 Dubbo 的設計與實現,其中 Dubbo SPI 載入機制是必須瞭解
Dubbo 的 SPI 機制(三)(Extension 擴充套件點補充)
相關部落格: Dubbo的SPI機制(一)(Java的SPI) Dubbo的SPI機制(二)(Dubbo優化後的SPI實現) Dubbo 的 Extension 主要是基於 SPI 思想實現的自己的 SPI 的工具。 在上一篇部落格(Dubbo的SP
Dubbo/Dubbox的服務暴露(二)-擴充套件點機制
上文書留的疑問,這兩句到底在幹啥 Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, regis
Dubbo原始碼分析——擴充套件點機制
Dubbo作為開源框架,必須要提供很多的可擴充套件點。Dubbo的擴充套件點載入採用了微核心外掛式的開發模式,它是比較符合OCP原則。 微核心 由一個外掛生命週期管理容器,構成微核心, 核心不包括任何功能,這樣可以確保所有功能都能被替換,並且框架實現的功能,
dubbo擴充套件點機制
spring是如何啟動容器的 常見的一種在本地使用main方法啟動spring的方法 public static void main(String[] args) throws Exception { ClassPathXmlAp
Dubbo擴充套件點機制分析
一、擴充套件點配置詳見我在《Java的SPI機制分析》文章中關於Dubbo的SPI機制的介紹,在此不再贅述。二、擴充套件點流程分析之SPI 下面以Container載入的過程為例,來說明SPI擴充套件的實現流程:所有加上@SPI註解的擴充套件點可以有不同的擴充套件,Co
DexClassLoader和PathClassLoader類載入機制
分析 sbin 分享 return bject _id ise 否則 nts 0x00 在DexClassLoader和PathClassLoader載入Dex流程一文中,我們分析了dex文件怎樣形成了DexFile結構體。本文中解說類載入機制,實際上就是生
《深入理解Java虛擬機器》個人讀書總結——虛擬機器類載入機制
我們都知道Java虛擬機器是用來執行我們編譯好的.class檔案的,class檔案中夾帶類的各種資訊,虛擬機器要執行這些檔案,第一件事就是要載入到虛擬機器中,這就引出了這次總結的問題——虛擬機器是如何載入這些class檔案的?載入後虛擬機器是怎麼處理檔案中夾帶的資訊的? 類載入機制
tomcat執行載入機制,dns地址解析,tcp/ip三握四揮(全)
一.瞭解從輸入url到顯示頁面過程中都發生了什麼: 1.通過url並利用DNS解析出目的主機的ip地址; 2.找到目標主機,建立TCP/IP連線; 3.Tomcat監聽
JVM的虛擬機器類載入機制
首先,我們要知道為什麼會存在類的載入機制。Java語言編寫的.java在經過編譯器編譯後會生成.class檔案,這個和C\C++語言是不一樣的。 C語言它們是會被編譯生成為本地機器碼,然後在被執行。這種做法的缺點就是無法完成編寫程式碼的跨平臺使用。想想就知道,windows下編譯好的程式碼在li
關於Class物件、類載入機制、虛擬機器執行時記憶體佈局的全面解析和推測
簡介: 本文是對Java的類載入機制,Class物件,反射原理等相關概念的理解、驗證和Java虛擬機器中記憶體佈局的一些推測。本文重點講述瞭如何理解Class物件以及Class物件的作用。 歡迎探討,如有錯誤敬請指正 如需轉載,請註明出處 http://www.cnblogs.com/nul
JVM原理(二)類載入機制與GC演算法
一. 類的載入機制 過程 將.class的二進位制資料讀入記憶體,放入方法區中 在堆中建立一個java.lang.Class物件,封裝類在方法區中的資料結構,並提供訪問方法區資料結構的介面 類的生命週期 類的載入過程
php自動載入機制
為什麼要實現自動載入機制? 在大專案中,就不用每次include/require檔案,省心又高效,只要你不嫌累的話,就可以不使用。 一、spl_autoload_register 語法: sql_autoload_register(callback $function_n
Java jvm 載入機制及 其中解釋執行和編譯執行的區別
jvm載入機制 https://www.cnblogs.com/Qian123/p/5707562.html https://www.cnblogs.com/lingz/archive/2018/07/31/9394238.html 以前有句話說:“Java是解釋執行的 ” 。現在看
Android的so檔案載入機制詳解
今日科技快訊 10月30日,小米集團跌超4%,再創上市以來新低,市值下破2600億港元關口。此前,財政部發布的《2018年會計資訊質量檢查公告》顯示,在2017年度會計執法檢查中發現,部分企業跨境轉移利潤、逃避繳納稅收等問題比較突出。在被點名的網際網路企業中,就包括
【jvm】jvm的類載入機制
前言:提到jvm的類載入機制,就不得不說我當年的沙雕經歷了,當年不明白為啥面試官都喜歡問jvm的類載入機制,當時心想學這東西有啥用,它怎麼載入關我啥事呀,能寫程式碼不就好了嗎?但無奈應試教育教會了我,雖然不知道為啥要學,但人家要考,你就得學,然後學唄,學完算是知道它是怎麼載入類的了,但依舊沒能深刻理
從阿里巴巴面試題到java類載入機制
首先很經典的阿里巴巴面試題 加上我自己的一些疑惑程式碼 public class Text { public static int k = 0; public final int k1 = 3; //自己加的 public static Text t1 = new Text("