Dubbo系列講解之擴展點實現原理分析【2萬字分享】

在這裡插入圖片描述

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();

  這樣就完成了通過激活擴展點的獲取。

Tags: