Apache Dubbo 是一款微服務開發框架,它提供了 RPC通訊 與 微服務治理 兩大關鍵能力。這意味著,使用 Dubbo 開發的微服務,將具備相互之間的遠端發現與通訊能力, 同時利用 Dubbo 提供的豐富服務治理能力,可以實現諸如服務發現、負載均衡、流量排程等服務治理訴求。同時 Dubbo 是高度可擴充套件的,使用者幾乎可以在任意功能點去定製自己的實現,以改變框架的預設行為來滿足自己的業務需求

  本文主要給大家講解下Dubbo的擴充套件點原理。

一、SPI介紹

  JDK中的SPI(Service Provider Interface)提供了一種基於介面的擴充套件機制,主要實現步驟如下:

  • 定義一個介面作為一個標準。
  • 在實現擴充套件點的工程中建立一個META-INF/services目錄,以定義的介面的全類名作為檔名建立一個檔名,將實現類的全類名寫入到檔案中。
  • 在需要使用擴充套件點的地方呼叫java.util.ServiceLoader#load(java.lang.Class)方法,傳入介面的全類名,返回java.util.ServiceLoader,ServiceLoader是一個Iterable的實現類,可以通過迭代器獲取到所有的擴充套件,進行執行。

具體不清楚的可以參考下我的另一篇專門介紹SPI的文章:

Java SPI內容詳解 學習交流 463257262

二、Dubbo擴充套件詳解

1.Dubbo中的擴充套件點的增強

  在Dubbo中的擴充套件點主要是對JDK的擴充套件點思想做了增強,主要增強了一下功能:

  • 全類名檔案中的內容通過key-value的規範書寫,載入時也是K-V的儲存方式,增加擴充套件點查詢的靈活性
  • JDK中的擴充套件點的載入會一次性的將所有的擴充套件點載入到記憶體中,如果有些擴充套件點沒用,但是改擴充套件點初始化很耗時,JDK也會將所有的擴充套件點載入到記憶體中,這些會造成一些浪費,而Dubbo中的擴充套件點會按需進行載入(載入時傳入擴充套件點的name,這也是需要依賴於檔案的K-V格式)
  • Dubbo增加了對擴充套件點 IoC 和 AOP 的支援,一個擴充套件點可以直接 setter 注入其它擴充套件點。同時在對擴充套件點進行依賴注入時,也會通過掃描到的Wrapper對擴充套件點實現進行包裝。

2.Dubbo中擴充套件點的使用方式

  • 定義一個介面,在介面上標註一個@SPI,標識這是一個擴充套件點
  • 在擴充套件點實現工程中建立檔案:/META-INF/dubbo/擴充套件點全類名
  • 在檔案中定義擴充套件點實現的k-v格式資料
helloService=com.bobo.spring.cloud.alibaba.consumer.spi.impl.HelloServiceImpl
  • 呼叫如下程式碼獲取擴充套件的實現進行呼叫
HelloService HelloService = ExtensionLoader
.getExtensionLoader(HelloService.class)
.getExtension("helloService");
System.out.println(HelloService.sayHello("wangxing"));

3.Dubbo擴充套件點原始碼分析

  在Dubbo中存在了以下三種類型的擴充套件點(Q群:463257262):

  1. 指定名稱擴充套件點
  2. 自適應擴充套件點
  3. 啟用擴充套件點

3.1 自適應擴充套件點原始碼分析

  在Dubbo中,通過在介面上標註【@SPI】標識該介面是一個擴充套件點,同時在其擴充套件點實現類或方法上,如果存在【@Adaptive】註解,則表示該類或方法是一個自適應的擴充套件點。標註在類上時,表示該擴充套件類是預設的自適應擴充套件點,標註在方法上時,表示該方法是自適應擴充套件點,將會重寫該方法,使得Dubbo能夠在執行時獲取到具體的擴充套件點。加下來就進入原始碼的分析吧…

  Dubbo的擴充套件點的入口如下:

HelloService helloService2 = ExtensionLoader
.getExtensionLoader(HelloService.class)
.getAdaptiveExtension();

  首先進入到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 an interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
} ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}

  該方法主要做了以下幾件事

  • 對傳入的的擴充套件點進行判斷

    空值判斷

    是否是介面判斷

    是否標識了@SPI註解的判斷,這也印證了前面我們說的只有標識了@SPI註解的介面Dubbo才會認為它是個擴充套件點介面

  • 根據型別從EXTENSION_LOADERS快取中獲取ExtensionLoader,獲取到就直接返回ExtensionLoader。(EXTENSION_LOADERS是一個CurrentHashMap集合,key為擴充套件點介面的.class物件,value為該擴充套件點對應的ExtensionLoader)

  • 如果快取中未獲取到的ExtensionLoader,以擴充套件點.class物件為key,建立一個ExtensionLoader物件為value儲存到EXTENSION_LOADERS中,返回建立的ExtensionLoader。到此就可以獲取到一個ExtensionLoader了,通過返回的ExtensionLoader物件可以獲得對應的擴充套件點的實現物件

接下來進入ExtensionLoader類中的構造方法,看看ExtensionLoader例項化時做了什麼

private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

  在構造器中為type屬性複製傳入的擴充套件點的.class物件。同時通過自適應擴充套件點的方式獲取到了一個ExtensionFactory的擴充套件點的實現,賦值給objectFactory。這裡先不詳細說明會具體獲取到哪個實現,本節分析完成再回過來看。

  通過以上的步驟,一個初始化完成的ExtensionLoader物件已經被獲取到了,分析getAdaptiveExtension()獲取自適應擴充套件點實現的流程

  進入getAdaptiveExtension()

public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
// 如果獲取到的例項為null,切快取的錯誤不能null,丟擲異常
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
} synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
// 異常資訊快取起來,下一次進來時如果發現是建立例項是出現異常,就直接丟擲異常。這裡的設計應該是當擴充套件點建立異常時避免多次執行建立流程的優化
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}

該方法主要做了以下幾件事

  • 從快取cachedAdaptiveInstance中獲取擴充套件點實現,存在該擴充套件點物件,則直接返回該例項。cachedAdaptiveInstance是一個Holder物件,主要用於快取該擴充套件點的實現的具體例項,因為這裡只會返回一個自適應擴充套件點的實現(有多個實現類標註了則按檔案定義順序取最後一個),實現對於每個``ExtensionLoader`來說,自適應擴充套件點是單例的。

  • 如果擴充套件點實現不存在,呼叫createAdaptiveExtension()建立一個具體的實現,並將該例項set到cachedAdaptiveInstance中快取起來。

建立擴充套件點實現的具體流程是在createAdaptiveExtension方法中

 private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

  該方法主要做了以下幾件事

  • 呼叫getExtensionClasses(),顧名思義,該方法主要是獲取擴充套件點的所有實現的.class物件。
  • 如果快取的cachedAdaptiveClass 物件不為null,直接返回。(cachedAdaptiveClass是一個class物件,用於儲存該擴充套件點的自適應擴充套件點的實現,即是該擴充套件點的實現類中存在有將@Adaptive標註在類上的預設自適應擴充套件點)
  • 如果為快取有cachedAdaptiveClass物件,則呼叫createAdaptiveExtensionClass建立一個cachedAdaptiveClass,並複製給cachedAdaptiveClass

接下來首先進入getExtensionClasses()方法

private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}

  該方法首先會從cachedClasses(cachedClasses也是一個holder,用於儲存每個擴充套件點的所有擴充套件實現的map集合)獲取該.class物件,存在則直接返回,否則呼叫loadExtensionClasses方法載入擴充套件點的classs,將載入到的classes存到cachedClasses中。

接下來進入loadExtensionClasses

private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName(); Map<String, Class<?>> extensionClasses = new HashMap<>(); for (LoadingStrategy strategy : strategies) {
// 掃描每個載入策略目錄中的擴充套件點實現
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
// 對alibaba的踐行相容
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
} return extensionClasses;
}

  首先呼叫cacheDefaultExtensionName()方法。再給介面標註@SPI的時候,可以給個預設的value值,表示指定的預設的擴充套件點實現,如@SPI("dubbo")public interface Protocol 表示預設的擴充套件點為擴充套件點實現名為dubbo的實現。而cacheDefaultExtensionName方法就是通過註解獲取到該擴充套件點的預設擴充套件點name,賦值給cachedDefaultName

  遍歷strategies,獲取到多個LoadingStrategy,通過stratery.directory()獲取到需要掃描的目錄,以下是Dubbo中預設的三種策略的實現

  • DubboInternalLoadingStrategy --> META-INF/dubbo/internal/
  • DubboLoadingStrategy -->META-INF/dubbo/
  • ServicesLoadingStrategy --> META-INF/services/

  在Dubbo中,建立ExtensionLoader物件時,會load到所有的LoadingStrategy,這裡利用的是JDK原生的SPI的方式,將LoadingStrategy的所有擴充套件實現都載入進來,儲存到strategies中。所以如果需要擴充套件Dubbo中的掃描的路徑,按照JDK的原生方式進行擴充套件即可

  進入到loadDirectory方法

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls = null;
ClassLoader classLoader = findClassLoader(); // try to load from ExtensionLoader's ClassLoader first
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
urls = extensionLoaderClassLoader.getResources(fileName);
}
} if (urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
} if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}

  該方法首先掃描傳入路徑下的所有的以type全類名命名的檔案,獲取到資源,將獲取到的檔案轉換成Resource,傳入到loadResource()方法中

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
// 按行讀取檔案中的每行資料
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;
// 通過等號分割,等號前的為key(擴充套件點name),等號後的為類的全類名
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}

  首先按行讀取檔案,對讀取到的每一行資料通過=號進行分割,=號前為擴充套件點的名字,=號後的為擴充套件點的具體擴充套件的實現類的全類名

  通過Class.forName將實現類載入到記憶體中。傳入到loadClass()方法

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) throws NoSuchMethodException {
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
// 會覆蓋掉之前儲存的`cachedAdaptiveClass`
cacheAdaptiveClass(clazz, overridden);
} else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
} else {
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
// 如果擴充套件檔案中的name為空,則呼叫findAnnotationName方法獲取擴充套件點名字,具體命名方式這裡就不詳細看了
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
} String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}

  該方法主要主要做了以下幾件事

  • 判斷該物件是否實現了擴充套件點介面,未實現則丟擲異常
  • 判斷給例項是否是自適應擴充套件點,是,呼叫cacheAdaptiveClass方法將該擴充套件點複製到cachedAdaptiveClass成員變數中。
  • 判斷該例項是否是擴充套件點的wrapper,是則呼叫cachedWrapperClasses方法將該例項儲存到cachedWrapperClasses中。擴充套件點實現是否是wrapper的判斷條件為該實現類中存在一個以擴充套件點為入參的構造方法時。
  • 對name進行分割,獲取到單個擴充套件點名字,檢查是否是擴充套件點,是,則將該例項儲存到cachedActivates中
  • 快取擴充套件點的名字,儲存到cachedNames中,以擴充套件點具體實現類的.class為key,擴充套件點name為value
  • 呼叫saveInExtensionClass方法,將擴充套件點名字及其實現的.class儲存到extensionClasses()集合中。

  到這裡,該擴充套件點在專案中的所有實現將被載入完成,且已經區分出了實現中,自適應擴充套件點,wrapper等不同型別的實現。然後我們回到

  再次回到getAdaptiveExtensionClass()方法,當執行完getExtensionClasses();方法之後,如果cacheAdaptiveClass為null,表示該擴充套件點沒有預設的自適應擴充套件點,此時擴充套件點需要將需要自適應擴充套件的方法上標註@Adaptive(),並且該方法中需要傳入URL物件,因為Dubbo中需要將都是通過URL來攜帶配置的。

  將呼叫createAdaptiveExtensionClass()方法動態建立一個自適應擴充套件點

private Class<?> createAdaptiveExtensionClass() {
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}

  該方法會動態生成一個自適應擴充套件點的類,然後編譯通過編譯器編譯,載入其.class檔案到記憶體中。返回該動態類的.class物件。

  生成的動態類程式碼如下:

import org.apache.dubbo.common.extension.ExtensionLoader;

public class HelloService$Adaptive implements com.wangx.spring.cloud.alibaba.consumer.spi.HelloService {
public java.lang.String sayHello(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註解中指定該引數的名稱,default為SPI上定義的預設擴充套件點實現
String extName = url.getParameter("hello.service","default");
if (extName == null)
throw new IllegalStateException("Failed to get extension (com.wangx.spring.cloud.alibaba.consumer.spi.HelloService) name from url (" + url.toString() + ") use keys([hello.service])");
// 根據名稱獲取到該擴充套件點型別的擴充套件實現
com.wangx.spring.cloud.alibaba.consumer.spi.HelloService extension = (com.wangx.spring.cloud.alibaba.consumer.spi.HelloService) ExtensionLoader.getExtensionLoader(com.wangx.spring.cloud.alibaba.consumer.spi.HelloService.class).getExtension(extName);
return extension.sayHello(arg0, arg1);
}
}

  該類實現了 擴充套件點介面,重寫了擴充套件點中的自適應擴充套件方法。該方法體現的是,在執行時通過傳入的URL的資訊動態的獲取處理當前URL時的擴充套件點的實現。

  到這裡就可以返回各種情況下的自適應擴充套件點的.class物件了,接下來再次回到createAdaptiveExtension方法中,通過以上的一系列操作,我們已經獲取到了自適應擴充套件點的.class物件,並呼叫反射建立一個擴充套件點實現的物件。然後呼叫injectExtension進行依賴注入

private T injectExtension(T instance) {

        if (objectFactory == null) {
return instance;
} try {
for (Method method : instance.getClass().getMethods()) {
if (!isSetter(method)) {
continue;
}
/**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
} try {
String property = getSetterProperty(method);
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
} }
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}

  在injectExtension方法中,有如下幾個操作:

  • 判斷是否存在objectFactory,為null,則直接返回例項物件
  • 遍歷該物件的所有方法,過濾掉不是setter方法及被標註了@DisableInject註解的方法,過濾表setter方法引數型別為特定型別及原生型別的方法,setter方法的引數就是需要被注入的物件。
  • 根據setter方法獲取被依賴注入的屬性名稱,然後通過 objectFactory.getExtension(pt, property);獲取到被注入物件例項,執行setter方法進行依賴注入。

  在初始化ExtensionLoader物件時,objectFactory是通過如下程式碼獲取的

objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

  通過上面的一些列分析,現在已經可以知道這是一個ExtensionFactory的自適應的擴充套件點,根據我們自適應擴充套件點的獲取方式,我們可以推斷出該擴充套件點的自適應擴充套件點的實現。

  在ExtensionFactory的所有子類實現中,我們找到了AdaptiveExtensionFactory類,該類上標註了@Adaptive,所以可以推斷出objectFacotory的指向的就是AdaptiveExtensionFactory類的物件。

  下面來看看AdaptiveExtensionFactory類中的實現:

@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()) {
list.add(loader.getExtension(name));
}
factories = Collections.unmodifiableList(list);
} @Override
public <T> T getExtension(Class<T> type, String name) {
for (ExtensionFactory factory : factories) {
T extension = factory.getExtension(type, name);
if (extension != null) {
return extension;
}
}
return null;
} }

  在AdaptiveExtensionFactory的建構函式中,會先獲取到ExtensionFactory型別的ExtensionLoader,然後通過呼叫loader.getSupportedExtensions()獲取到ExtensionFactory的所有擴充套件實現的名字。通過loader.getExtension(name)根據名稱獲取到所有擴充套件點,儲存到factories中。

  在injectExtension中呼叫getExtension()方法時,將會遍歷初始化時獲取到的所有的ExtensionFactory的擴充套件點。只要在其中的一個擴充套件到那點中找到該擴充套件物件的例項,則直接返回。傳入的物件type和name,獲取Dubbo環境下可能存在的擴充套件點。

  在injectExtension方法中返回的依賴注入完成的物件,即是我們需要獲取的自適應擴充套件點物件

3.2 根據名稱獲取擴充套件點

  根據名稱獲取擴充套件點,顧名思義,就是根據擴充套件點的名稱,獲取到擴充套件點對應的實現。這種方式在Dubbo中也被廣泛應用到,主要是可以通過URL中的引數或協議作為name,在執行時根據URl動態的獲取到不同方式的實現。比如獲取負載均衡器等

入口如下:

HelloService HelloService = ExtensionLoader.getExtensionLoader(HelloService.class).getExtension("helloService");

getExtensionLoader方法在上述中已經解釋清楚了,現在直接進入到getExtension方法中

public T getExtension(String name) {
//判斷傳入名稱是否為null
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
// 如果為true,獲取預設的擴充套件點,在getDefaultExtension方法中會呼叫getExtensionClasses->loadExtensionClasses方法,該方法中的cacheDefaultExtensionName會將預設擴充套件點的name賦值到cachedDefaultName中,所以當呼叫getDefaultExtension()即可獲得預設的擴充套件點實現
if ("true".equals(name)) {
return getDefaultExtension();
} final Holder<Object> holder = getOrCreateHolder(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;
}

  該方法主要做了以下幾個操作

  • 如果傳入的name為true,則獲取該擴充套件點的預設擴充套件點
  • 獲取或新建一個Holder,這裡跟自適應擴充套件點不同的是,自適應擴充套件點的只有一個實現會被儲存,而通過名稱獲取擴充套件點時,需要將每個name對應的擴充套件點實現包裝的holder放儲存到cachedInstances中,cachedInstances是一個map集合,儲存了name對應的擴充套件的實現。如果不存在該holder,則新建一個holder物件返回,獲取holder儲存的instance物件,如果不存在,則直接利用雙重檢查鎖建立一個單例的instance儲存到holder中。

  建立instance時,需要呼叫createExtension(name)方法

@SuppressWarnings("unchecked")
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}

  通過自適應擴充套件點的分析,我們已經知道了getExtensionClasses()方法返回的是擴充套件點name為key,value為具體擴充套件點實現類的class物件。然後可以通過name獲取到擴充套件點對應的class物件。然後通過該class物件,到EXTENSION_INSTANCES快取中獲取實現類的物件,如果不存在,則直接通過反射建立一個物件。

  接下來就跟自適應擴充套件點一樣,呼叫injectExtension進行依賴注入。依賴注入完成之後,將對該物件進行包裝,首先從載入時裝載的cachedWrapperClasses快取中獲取所有的wrapper擴充套件點,遍歷所有的裝飾器,將建立的實際的擴充套件點物件通過構造器傳入到wrapper中,反射創建出一個wrapper物件,在對該wrapper物件進行依賴注入,完成之後將該物件複製給instance。

  比如現在有一個擴充套件點S,擴充套件點實現F,該擴充套件點有A,B,C三個包裝器,那麼通過如上遍歷包裝器之後,最後的到的instance物件的結構可能經過了層層的巢狀,變成了這個樣子:A(B(C(F)))。呼叫instance時,將會從最外層開始執行該物件的方法,最終到最裡層才會執行實際擴充套件點的方法。這種設計使得包裝的實現更加簡潔和靈活。

  然後呼叫initExtension方法,如果該物件實現了Lifecycle介面,則呼叫initialize方法。最後返回一個被包裝的物件

3.3 啟用擴充套件點

  啟用擴充套件點的使用也需要在實現類上標註@Activate註解。註解中可以指定group和value,當不指定時,就無條件啟用,否則就按照指定的條件進行啟用,啟用擴充套件點的入口為:

List<HelloServiceActive> li  = ExtensionLoader.getExtensionLoader(HelloServiceActive.class).getActivateExtension(url,"helloService2");
for (HelloServiceActive helloServiceActive : li) {
helloServiceActive.say();
}

  需要傳入一個URL物件和一個需要獲取啟用的的擴充套件點的引數看key,比如在引數中設定url = url.addParameter("xing","xing");傳入的key為xing。進入getActivateExtension()方法,因為可以指定group等,所以最終呼叫的方法為

public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> activateExtensions = new ArrayList<>();
List<String> names = values == null ? new ArrayList<>(0) : asList(values);
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
getExtensionClasses();
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Object activate = entry.getValue(); String[] activateGroup, activateValue; if (activate instanceof Activate) {
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
} else {
continue;
}
if (isMatchGroup(group, activateGroup)
&& !names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) {
activateExtensions.add(getExtension(name));
}
}
activateExtensions.sort(ActivateComparator.COMPARATOR);
}
List<T> loadedExtensions = new ArrayList<>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(REMOVE_VALUE_PREFIX)
&& !names.contains(REMOVE_VALUE_PREFIX + name)) {
if (DEFAULT_KEY.equals(name)) {
if (!loadedExtensions.isEmpty()) {
activateExtensions.addAll(0, loadedExtensions);
loadedExtensions.clear();
}
} else {
loadedExtensions.add(getExtension(name));
}
}
}
if (!loadedExtensions.isEmpty()) {
activateExtensions.addAll(loadedExtensions);
}
return activateExtensions;
}

  經過我們對上面兩種方式的分析,其實第三種方式我們已經能夠很輕鬆的看懂了。主要流程有,先遍歷掃描是裝載在cachedActivates中的所有啟用擴充套件點,跟url和group進行匹配,匹配成功,則通過擴充套件點的名name通過根據名稱獲取擴充套件點的方式獲取擴充套件點,儲存到list中。然後遍歷根據key獲取到的value解析出來的擴充套件點名稱,通過該名稱獲取到擴充套件點裝載到list中,然後返回activateExtensions();

  這樣就完成了通過啟用擴充套件點的獲取。