天天看點

Dubbo系列講解之擴充點實作原理分析【2萬字分享】一、SPI介紹二、Dubbo擴充詳解

Dubbo系列講解之擴充點實作原理分析【2萬字分享】一、SPI介紹二、Dubbo擴充詳解
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();

  這樣就完成了通過激活擴充點的擷取。