Dubbo SPI機制(上):一個普通的擴展類是如何加載的

這一篇我們先不講Dubbo中的具體業務邏輯,我們來打基礎,聊一聊Dubbo中的SPI機制。

Dubbo SPI是幹啥的

瞭解一個技術,得先知道它是爲了解決什麼問題而產生的。那麼Dubbo SPI是幹什麼的呢?

按照官網的描述,Dubbo是一款高性能的RPC框架,是爲了解決應用間的服務治理問題而誕生的。
服務治理會涉及到很多方面的內容,如網絡連接、集羣容錯、服務路由、負載均衡等。這麼多的內容,每個都有不同的解決方案。如網絡連接,可以由netty實現,也可以由mina實現。

Dubbo並沒有侷限於某一種解決方案,而是博採衆長。它通過一種機制,讓用戶可以自行擴展加載想要的功能實現。這種機制就是Dubbo SPI。

Dubbo SPI與Java SPI的淵源

你可能瞭解過Java SPI機制(如果不瞭解,建議瞭解一下),Dubbo SPI是在Java SPI的基礎之上,又進行了一層功能的擴展得到的。相較於Java SPI方式,Dubbo SPI具有以下幾個方面的優勢:

  • 按需加載
    Java SPI無論你是否需要這個擴展類,都會將其加載到內存中。這樣可能會造成資源浪費。Dubbo則不然,採用了一種按需加載擴展的方式,避免不必要的資源浪費
  • 擴展間的IOC和AOP
    Dubbo SPI機制還實現了一種擴展間的注入與切入功能。簡單來說,一個擴展類可以注入另一個擴展類中,外層包裝的擴展可以做更多的事,如:流量統計、監控等,好處不言而喻。

Dubbo SPI使用

Dubbo SPI的使用和Java SPI非常相像,也並不複雜,這裏就不再贅述。如果你不太瞭解,建議去官網看一下。

一個普通擴展類是如何加載的

在Dubbo中,標註了@SPI的接口,即被認爲是Dubbo SPI擴展類.。

接下來我們聊聊一個普通Dubbo SPI擴展類是如何加載的。首先從Dubbo中最長見到的一種擴展調用方式開始:

ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(DubboProtocol.NAME);

這行代碼顧名思義,就是先查找Protocol這個類的加載器,然後利用加載器獲取 Dubbo協議擴展類。
前半段的加載器獲取邏輯我們暫時忽略,先從getExtension來說起。

不看源碼實現,先想一想,如果要你實現一個getExtension方法,你會做哪些事情?

  • 擴展類加載完了之後,你是不是要將它緩存起來,方便下次直接獲取?
  • 擴展類是從哪裏獲取的?對,配置文件。那麼是不是要先讀取文件,知道有哪些擴展類?
  • 上面說到擴展類支持IOC和AOP,那麼在實例化擴展類的時候,是否應該有對擴展類中進行注入的邏輯?

帶着上面幾個猜測,我們來看一下getExtension的源碼,看看一個普通擴展類是如何加載進來的。

public T getExtension(String name) {
	// $-- 空校驗
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    // $-- 特殊處理,true則進行默認擴展類加載
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    // $-- 嘗試根據name從緩存中獲取類實例,沒有則創建
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<Object>());
        holder = cachedInstances.get(name);
    }
    Object instance = holder.get();
    // $-- double check
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                // $-- 創建擴展類,並進行緩存
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

在getExtension方法裏,Dubbo主要進行了空校驗,並對一些特殊邏輯進行判斷處理。接着就是一套“查緩存,緩存不存在創建並緩存”的套路了。在Dubbo的源碼裏存在大量這樣的緩存套路使用,這樣的緩存使用對提升效率是非常明顯的。
注意:這裏緩存的是擴展類的實例

接下來看一下createExtension創建擴展類的邏輯。

private T createExtension(String name) {
    // $-- 先獲取該name對應的Class類
    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);
        }
        // $-- 擴展類實例的依賴注入(setter)
        injectExtension(instance);
        // $-- 對包裝擴展類進行依賴注入(constructor)
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
            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);
    }
}

createExtension先要根據name獲取對應的擴展類,如果系統中都沒有這個擴展類,那麼生成擴展類實例就無從談起了。
然後又是一套緩存的套路,通過反射創建了該擴展類的實例,放入緩存中。
接下來就是對擴展類進行IOC注入的邏輯了,主要對擴展類實例內部的setter方法進行注入。以上面DubboProtocol的加載爲例,這裏就是找DubboProtocol類中的setter方法,如果有擴展類可以注入,就進行注入。
隨後是對包裝擴展類進行依賴注入,使用的是構造器注入。這裏與上述setter方法是不同的。同樣以DubboProtocol爲例,這裏是對系統中以Protocol爲構造函數的擴展類進行注入,注入的就是當前的DubboProtocol類。相當於給DubboProtocol外面裝飾了一下再返回(裝飾器模式)。舉個例子,如:ProtocolListenerWrapper類

從文件中加載擴展類

關於擴展類注入的邏輯我們稍後再聊,先看一下Dubbo從文件中加載擴展類Class的實現邏輯。
方法getExtensionClasses會加載當前系統中所有的擴展點類

private Map<String, Class<?>> getExtensionClasses() {
    // $-- 緩存老套路
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // $-- 加載所有擴展類,生成包含所有擴展類Class的一個Map
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

這裏沒有啥邏輯,依然是一個緩存的老套路。不同的是這裏緩存的是擴展類Class,而不是實例。
加載的邏輯還要往下看

private Map<String, Class<?>> loadExtensionClasses() {
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if (defaultAnnotation != null) {
        String value = defaultAnnotation.value();
        if ((value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);
            // $-- @SPI註解只允許指定一個默認值
            if (names.length > 1) {
                throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                        + ": " + Arrays.toString(names));
            }
            // $-- @SPI指定了一個默認值,緩存起來
            if (names.length == 1) cachedDefaultName = names[0];    
        }
    }

    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    // $-- 加載擴展類資源目錄
    loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadDirectory(extensionClasses, DUBBO_DIRECTORY);
    loadDirectory(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}

這裏首先對擴展類@SPI註解的使用進行了一下校驗。如果@SPI註解中配置了兩個默認值,則拋異常。方法中的type就是我們要加載的Protocol接口,Protocol接口上使用了@SPI(“dubbo”)進行修飾,代表默認使用dubbo擴展類。
隨後就是文件的讀取與擴展類的加載了。這裏可以看到,Dubbo默認的擴展類目錄有以下三個:

  • META-INF/services/
  • META-INF/dubbo/
  • META-INF/dubbo/internal/

那麼它是如何讀取文件,加載擴展類資源的呢,且看loadDirectory方法

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
    String fileName = dir + type.getName();
    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 resourceURL = urls.nextElement();
                // $-- 加載資源文件中的擴展類
                loadResource(extensionClasses, classLoader, resourceURL);
            }
        }
    } catch (Throwable t) {
        logger.error("Exception when load extension class(interface: " +
                type + ", description file: " + fileName + ").", t);
    }
}

這裏的主要邏輯實際上是獲取類加載器,然後將文件的讀取交給loadResource方法

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
   try {
        BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
        try {
            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;
                        int i = line.indexOf('=');
                        if (i > 0) {
                            // $-- name爲i擴展類的key,如 @SPI("dubbo")中的dubbo,line爲類名
                            name = line.substring(0, i).trim();
                            line = line.substring(i + 1).trim();
                        }
                        if (line.length() > 0) {
                            // $-- 反射創建class並加載
                            loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                        }
                    } 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);
                    }
                }
            }
        } finally {
            reader.close();
        }
    } catch (Throwable t) {
        logger.error("Exception when load extension class(interface: " +
                type + ", class file: " + resourceURL + ") in " + resourceURL, t);
    }
}

到這裏,我們終於看到熟悉的文件讀取操作了。
Dubbo擴展類文件的格式一般都是 “key=value” 樣式的,並且支持 “#” 作爲註釋符的。

具體的加載類操作依然要往下看,loadClass方法是真正的加載類方法。

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    // $-- 校驗擴展類與接口是否類型匹配
    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.");
    }
    // $-- 該類上是否使用了Adaptive註解,有則進行緩存
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        if (cachedAdaptiveClass == null) {
            cachedAdaptiveClass = clazz;
        } else if (!cachedAdaptiveClass.equals(clazz)) {
            throw new IllegalStateException("More than 1 adaptive class found: "
                    + cachedAdaptiveClass.getClass().getName()
                    + ", " + clazz.getClass().getName());
        }
    } else if (isWrapperClass(clazz)) {
        // $-- 是否爲包裝擴展類(構造函數注入),是則加入set緩存
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    } else {
        clazz.getConstructor();
        // $-- 此處應該是兼容舊方法(@Extension註解)的處理邏輯
        if (name == null || name.length() == 0) {
            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 (names != null && names.length > 0) {
            // $-- 是否爲自激活擴展類,是則加入自動激活緩存
            Activate activate = clazz.getAnnotation(Activate.class);
            if (activate != null) {
                cachedActivates.put(names[0], activate);
            }
            // $-- 普通擴展類,類名加入緩存,類加入緩存
            for (String n : names) {
                if (!cachedNames.containsKey(clazz)) {
                    cachedNames.put(clazz, n);
                }
                Class<?> c = extensionClasses.get(n);
                if (c == null) {
                    extensionClasses.put(n, clazz);
                } else if (c != clazz) {
                    throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                }
            }
        }
    }
}

這裏首先校驗了擴展類類型。
隨後對@Adaptive註解進行了判斷處理。如果類上標註了@Adaptive,則代表其爲默認實現,會進行緩存。關於@Adaptive註解,我們這裏暫時不說,後續再聊。

接着判斷了該擴展類是否存在包裝擴展類,如果有的話,就加入到緩存。包裝擴展類的邏輯,我們在注入的時候再統一講,這裏先知道有這麼一層處理邏輯。

如果上述特殊場景都不滿足,那麼我們就直接進入默認的加載處理邏輯:首先判斷了擴展類的name的取值,此處兼容了對舊的@Extension註解的處理邏輯。隨後就自激活擴展類(@Activate註解修飾)和普通擴展類分別加入到相應的緩存中。

這樣將三個目錄都讀取完成之後,擴展類就被直接加載到緩存中了。(o゜▽゜)o☆

擴展類注入

現在讓我們將注意力轉回到createExtension中。獲取到擴展類後,直接進行實例化,然後就是重頭戲注入了。
注入分爲兩種場景,分別是擴展類setter方法注入和包裝擴展類構造器注入。

擴展類setter注入

setter方法注入由方法injectExtension實現

private T injectExtension(T instance) {
   try {
        if (objectFactory != null) {
            for (Method method : instance.getClass().getMethods()) {
                // $-- set方法注入
                if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                    // $-- 方法上存在@DisableInject註解的,此處不進行注入
                    if (method.getAnnotation(DisableInject.class) != null) {
                        continue;
                    }
                    // $-- 獲取要注入的類型
                    Class<?> pt = method.getParameterTypes()[0];
                    try {
                        // $-- 獲取要注入的擴展類名,如 setProtcol ==> protocol
                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        // $-- 獲取注入類實例
                        Object object = objectFactory.getExtension(pt, property);
                        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;
}

objectFactory爲擴展加載器工廠,它的主要作用是生成擴展加載器,下面會詳細介紹,這裏先忽略。
整個setter注入的邏輯還是非常清晰的,配合代碼中的註釋,相應應該不用我來解釋什麼了。

包裝擴展類構造器注入

Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
    for (Class<?> wrapperClass : wrapperClasses) {
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
    }
}

首先判斷該擴展類是否有包裝,這裏是通過cachedWrapperClasses緩存來判斷的。
對於包裝擴展類,隨後進行包裝擴展類的實例化,以及該包裝擴展類的setter注入。

整個邏輯還是非常簡單的,唯一的問題在於cachedWrapperClass是如何賦值的呢?
回想讀取文件加載類時,會進行該類是否有包裝的判斷。如果有包裝,則加入到cachedWrapperClasses緩存中。

else if (isWrapperClass(clazz)) {
   // $-- 是否爲包裝擴展類(構造函數注入),是則加入set緩存
    Set<Class<?>> wrappers = cachedWrapperClasses;
    if (wrappers == null) {
        cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
        wrappers = cachedWrapperClasses;
    }
    wrappers.add(clazz);
}

isWrapperClass是其判斷代碼,主要是判斷要加載的類是否有當前接口的構造函數

private boolean isWrapperClass(Class<?> clazz) {
    try {
        // $-- 判斷Clazz是否有type類型的構造函數,沒有則拋異常
        clazz.getConstructor(type);
        return true;
    } catch (NoSuchMethodException e) {
        return false;
    }
}

這裏你可能有一點懵,我們舉Protocol的例子來說明一下吧。

當進行文件讀取,加載擴展類讀取時,會加載到類ProtocolListenerWrapper。這個類裏存在以Protocol爲參數的構造方法,說明ProtocolListenerWrapper這個類可以進行包裝擴展,因此就會在cachedWrapperClasses中記錄下來。

當我們通過createExtension來創建dubbo類型的Protocol時,判斷到其包裝擴展類緩存cachedWrapperClasses中存在ProtocolListenerWrapper類型的包裝類時,就會對DubboProtocol進行裝飾,返回的是ProtocolListenerWrapper類。(DubboProtocol的包裝類不只ProtocolListenerWrapper,這裏僅僅是作爲一個例子進行理解)

另外需要注意的是,代碼中並沒有對包裝的順序進行定義,所以理論上,文件讀取時,加載到cachedWrapperClasses緩存中的順序會直接影響到包裝的結果。

ExtensionLoader的獲取

上述就是整個普通擴展類加載的流程了,不知道你是否瞭然於胸了呢?

結合我個人的經歷,當我初讀這段代碼時,對type這個字段很迷惑,特別是在包裝擴展類緩存判斷這一塊兒。那麼這個type到底是啥?

別急,讓我們將視線轉換回一切的起點

ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(DubboProtocol.NAME);

你可能已經猜到了,這裏的Protocol.class就是type!
現在是時候分析一下這段代碼的前半段了:如何獲取某個擴展類的ExtensionLoader?

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!");
    }
    // $-- 是否有@SPI註解校驗
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type(" + type +
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }

    // $-- 緩存套路
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        // $-- 這裏創建type類型的ExtensionLoader
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

獲取擴展類加載器的這段代碼裏,主要先對要加載的類做了一些校驗,然後是緩存的老套路。我們主要來看一下創建ExtensionLoader的方法,也就是ExtensionLoader的構造方法。

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

到這裏可以看到,我們一直疑惑的type原來是在構造方法裏被賦值的。type代表的實際上是我們將要獲取的擴展類的類型定義。
objectFactory是擴展類加載器工廠,用來生成擴展類加載器ExtensionLoader的。這裏先判斷了一下我們要加載的類是否就是擴展類加載器工廠本身,如果是本身的話,則返回null,不用加載。否則,就再通過擴展類加載機制獲取ExtensionLoader的擴展類。

ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

像不像一個“套娃”?😂

Dubbo SPI中的ExtensionFactory體系

說到這裏,我們順便聊聊Dubbo 擴展類工廠的實現吧。
在Dubbo中,存在三種類型的擴展類加載器工廠。
Dubbo SPI中的ExtensionFactory體系

Dubbo中存在ExtensionFactory的三個實現類:SpiExtensionFactory、SpringExtensionFactory、AdaptiveExtensionFactory。其中AdaptiveExtensionFactory上有@Adaptive註解,代表這是ExtensionFactory的默認實現。

AdaptiveExtensionFactory的源碼如下

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    // $-- 持有了所有的ExtensionFactory
    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        // $-- 通過SPI獲取擴展類加載器工廠,然後放入factories中
        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) {
        // $-- 本質上還是調用了SPI和Spring兩個ExtensionFactory來getExtension,順序是SPI->Spring
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
}

實際上AdaptiveExtensionFactory只是一層殼,內部也是調用其他兩個ExtensionFactory的getExtension方法來實現的,且順序爲Spi->Spring

SpringExtensionFactory的中getExtension方法如下

public <T> T getExtension(Class<T> type, String name) {
    // $-- 遍歷Spring容器,根據名稱獲取bean
    for (ApplicationContext context : contexts) {
        if (context.containsBean(name)) {
            Object bean = context.getBean(name);
            if (type.isInstance(bean)) {
                return (T) bean;
            }
        }
    }

    logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());

    // $-- 如果要獲取的bean類型爲Object,返回null給它吧(畢竟所有類都是Object的子類)
    if (Object.class == type) {
        return null;
    }

    // $-- 遍歷Spring容器,根據類型獲取bean
    for (ApplicationContext context : contexts) {
        try {
            return context.getBean(type);
        } catch (NoUniqueBeanDefinitionException multiBeanExe) {
            logger.warn("Find more than 1 spring extensions (beans) of type " + type.getName() + ", will stop auto injection. Please make sure you have specified the concrete parameter type and there's only one extension of that type.");
        } catch (NoSuchBeanDefinitionException noBeanExe) {
            if (logger.isDebugEnabled()) {
                logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe);
            }
        }
    }

    logger.warn("No spring extension (bean) named:" + name + ", type:" + type.getName() + " found, stop get bean.");

    return null;
}

Spring擴展加載器工廠主要是從Spring上下文中獲取對應的bean,先根據名稱匹配,沒匹配到再根據類型匹配,如果還沒有匹配到,則返回null。

SpiExtensionFactory的getExtension方法如下

public <T> T getExtension(Class<T> type, String name) {
 if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
        // $-- 根據類型獲取所有的擴展點加載器
        ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
        // $-- 如果緩存的擴展點加載器不爲空,則直接返回Adaptive實例
        if (!loader.getSupportedExtensions().isEmpty()) {
            return loader.getAdaptiveExtension();
        }
    }
    return null;
}

可以看到,Spi擴展點加載器工廠的getExtension方法非常簡單。默認返回的是Adaptive類型的實例。

總結

以上就是一個普通擴展類加載的大致內容了。
由於篇幅已經比較長了,自激活擴展(@Activate)和自適應擴展(@Adaptive)源碼將在下一篇博文介紹。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章