天天看點

Dubbo之SPI實作原理詳解

開篇

 SPI全稱為Service Provider Interface,是一種服務提供機制,比如在現實中我們經常會有這種場景,就是對于一個規範定義方而言(可以了解為一個或多個接口),具體的服務實作方是不可知的(可以了解為對這些接口的實作類),那麼在定義這些規範的時候,就需要規範定義方能夠通過一定的方式來擷取到這些服務提供方具體提供的是哪些服務,而SPI就是進行這種定義的。

JDK SPI例子

說明:

  • 首先規範制定方會定義一個接口org.apache.jdk.spi.example.IHello 。
  • 其次在項目目錄下的META-INF/service名稱為org.apache.jdk.spi.example.IHello的檔案,包含SPI實作接口全路徑。
  • 通過ServiceLoader加載通路調用即可。
  • 對于jdk的SPI,其主要存在兩個問題,為每個接口提供的服務一般盡量隻提供一個,因為jdk的SPI預設會将所有目标檔案中定義的所有子類都讀取到傳回使用;當定義多個子類實作時,無法動态的根據配置來使用不同的配置。
---- 定義接口
package org.apache.jdk.spi.example;

public interface IHello {
    void sayHello();
}

---- 定義實作1
package org.apache.jdk.spi.example;

public class HelloImpl1 implements IHello {
    @Override
    public void sayHello() {
        System.out.println("我是Impl1");
    }
}

---- 定義實作2
package org.apache.jdk.spi.example;

public class HelloImpl2 implements IHello {
    @Override
    public void sayHello() {
        System.out.println("我是Impl2");
    }
}

---- META-INF/services目錄檔案 org.apache.jdk.spi.example.IHello
org.apache.jdk.spi.example.HelloImpl1
org.apache.jdk.spi.example.HelloImpl2


---- 測試檔案内容
package org.apache.jdk.spi.example;

import java.util.Iterator;
import java.util.ServiceLoader;

public class ServiceLoaderDemo {
    public static void main(String[] args){
        ServiceLoader<IHello> s = ServiceLoader.load(IHello.class);
        Iterator<IHello> iHelloIterator = s.iterator();

        while (iHelloIterator.hasNext()) {
            IHello iHello = iHelloIterator.next();
            iHello.sayHello();
        }
    }
}           
Dubbo之SPI實作原理詳解

Dubbo SPI例子

  • 定義PlantsWater的接口并通過@SPI注解進行注解,注解可選擇帶預設值。
  • 将watering()方法使用@Adaptive注解進行了标注,表示該方法在自動生成的子類中是需要動态實作的方法。
  • 增加grant()方法是為了表明不帶@Adaptive在自動生成的子類方法内部會抛出異常。
  • 為PlantsWater增加兩個實作,AppleWater和BananaWater,實際調用通過參數控制。
  • 在META-INF/dubbo下建立一個檔案,該檔案的名稱是目标接口的全限定名,這裡是org.apache.dubbo.spi.example.PlantsWater,在該檔案中需要指定該接口所有可提供服務的子類。
  • 定義主函數ExtensionLoaderDemo模拟SPI調用的驗證。
----定義基礎應用類

public interface Fruit {}
public class Apple implements Fruit {}
public class Banana implements Fruit{}



----定義SPI類

@SPI("banana")
public interface PlantsWater {

    Fruit grant();

    @Adaptive
    String watering(URL url);
}


public class AppleWater implements PlantsWater {
    public Fruit grant() {
        return new Apple();
    }

    public String watering(URL url) {
        System.out.println("watering apple");
        return "watering finished";
    }
}


public class BananaWater implements PlantsWater {

    public Fruit grant() {
        return new Banana();
    }

    public String watering(URL url) {
        System.out.println("watering banana");
        return "watering success";
    }
}



----resources檔案 org.apache.dubbo.spi.example.PlantsWater

apple=org.apache.dubbo.spi.example.AppleWater
banana=org.apache.dubbo.spi.example.BananaWater


------測試代碼内容

public class ExtensionLoaderDemo {

    public static void main(String[] args) {
        // 首先建立一個模拟用的URL對象
        URL url = URL.valueOf("dubbo://192.168.0.101:20880?plants.water=apple");
        // 通過ExtensionLoader擷取一個PlantsWater對象,getAdaptiveExtension已經加載了所有SPI類
        PlantsWater plantsWater = ExtensionLoader.getExtensionLoader(PlantsWater.class)
                .getAdaptiveExtension();
        // 使用該PlantsWater調用其"自适應标注的"方法,擷取調用結果
        String result = plantsWater.watering(url);
        System.out.println(result);
    }
}


-----實際輸出内容

十月 11, 2019 7:48:51 下午 org.apache.dubbo.common.logger.LoggerFactory info
資訊: using logger: org.apache.dubbo.common.logger.jcl.JclLoggerAdapter
watering apple
watering finished

Process finished with exit code 0           
Dubbo之SPI實作原理詳解

JDK 和 Dubbo SPI簡單對比

Dubbo 的擴充點加載是基于JDK 标準的 SPI 擴充點發現機制增強而來的,Dubbo 改進了 JDK 标準的 SPI 的以下問題:

  • JDK 标準的 SPI 會一次性執行個體化擴充點所有實作,如果有擴充實作初始化很耗時,但如果沒用上也加載,會很浪費資源。
  • 如果擴充點加載失敗,就失敗了,給使用者沒有任何通知。比如:JDK 标準的ScriptEngine,如果Ruby ScriptEngine 因為所依賴的 jruby.jar 不存在,導緻 Ruby ScriptEngine 類加載失敗,這個失敗原因被吃掉了,當使用者執行 ruby 腳本時,會報空指針異常,而不是報Ruby ScriptEngine不存在。
  • 增加了對擴充點 IoC 和 AOP 的支援,一個擴充點可以直接 setter 注入其它擴充點。

Dubbo SPI實作原理

dubbo對于SPI的實作主要是在ExtensionLoader這個類中,這個類主要有三個方法:

  • getExtension():主要用于擷取名稱為name的對應的子類的對象,這裡如果子類對象如果有AOP相關的配置,這裡也會對其進行封裝;
  • getAdaptiveExtension():使用定義的裝飾類來封裝目标子類,具體使用哪個子類可以在定義的裝飾類中通過一定的條件進行配置;
  • getExtensionLoader():加載目前接口的子類并且執行個體化一個ExtensionLoader對象。
public T getExtension(String name);
public T getAdaptiveExtension();
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type);           

getExtension()

  • getExtension()方法的主要作用是擷取name對應的子類對象傳回。
  • 其實作方式是首先讀取定義檔案中的子類,然後根據不同的子類對象的功能的不同,比如使用@Adaptive修飾的裝飾類和用于AOP的Wrapper類,将其封裝到不同的緩存中。
  • 最後根據傳入的name擷取其對應的子類對象,并且使用相應的Wrapper類對其進行封裝。

如下是getExtension()方法的源碼:

public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }

        // 如果名稱為true,則傳回預設的子類對象,這裡預設的子類對象的name定義在目标接口的@SPI注解中
        if ("true".equals(name)) {
            return getDefaultExtension();
        }

        // 檢視目前是否已經緩存有儲存目标對象的執行個體的Holder對象,緩存了則直接傳回,
        // 沒緩存則建立一個并緩存起來
        final Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();

        // 如果無法從Holder中擷取目标對象的執行個體,則使用雙檢查法為目标對象建立一個執行個體
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    // 建立name對應的子類對象的執行個體
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }



    public T getDefaultExtension() {
        getExtensionClasses();
        if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
            return null;
        }
        // 通過cachedDefaultName去擷取對應的子類執行個體
        return getExtension(cachedDefaultName);
    }


    private void cacheDefaultExtensionName() {
        // cachedDefaultName取自SPI的參數當中
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation == null) {
            return;
        }

        String value = defaultAnnotation.value();
        if ((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];
            }
        }
    }           
  • 關于對于目标對象的擷取,首先是從緩存裡取,沒取到才會進行建立。
  • 這裡需要說明的是,如果傳入的name為true,那麼就會傳回預設的子類執行個體,而預設的子類執行個體是通過其名稱進行映射的,該名稱存儲在目标接口的@SPI注解中。

createExtension()方法的源碼:

private T createExtension(String name) {
        // 擷取目前名稱對應的子類類型,如果不存在,則抛出異常
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            // 擷取目前class對應的執行個體,如果緩存中不存在,則執行個體化一個并緩存起來
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }

            // 為生成的執行個體通過其set方法注入對應的執行個體,這裡執行個體的擷取方式不僅可以通過SPI的方式
            // 也可以通過Spring的bean工廠擷取
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
                    // 執行個體化各個wrapper對象,并将目标對象通過wrapper的構造方法傳入,
                    // 另外還會通過wrapper對象的set方法對其依賴的屬性進行注入
                    instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                }
            }
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                    type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }           

在createExtension()方法中,其主要做了三件事:

  • 加載定義檔案中的各個子類,然後将目标name對應的子類傳回後進行執行個體化。
  • 通過目标子類的set方法為其注入其所依賴的bean,這裡既可以通過SPI,也可以通過Spring的BeanFactory擷取所依賴的bean,injectExtension(instance)。
  • 擷取定義檔案中定義的wrapper對象,然後使用該wrapper對象封裝目标對象,并且還會調用其set方法為wrapper對象注入其所依賴的屬性。

關于wrapper對象,這裡需要說明的是,其主要作用是為目标對象實作AOP。wrapper對象有兩個特點:

    1. 與目标對象實作了同一個接口;
    1. 有一個以目标接口為參數類型的構造函數。這也就是上述createExtension()方法最後封裝wrapper對象時傳入的構造函數執行個體始終可以為instance執行個體的原因。

getExtensionClasses()方法的源碼

private Map<String, Class<?>> getExtensionClasses() {
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    // 加載定義檔案,并且将定義的類按照功能緩存在不同的屬性中,即:
                    // a. 目标class類型緩存在cachedClasses;
                    // b. wrapper的class類型緩存在cachedWrapperClasses;
                    // c. 用于裝飾的class類型緩存在cachedAdaptiveClass;
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }



    private Map<String, Class<?>> loadExtensionClasses() {
        // 擷取目标接口上通過@SPI注解定義的預設子類對應的名稱,并将其緩存在cachedDefaultName中
        cacheDefaultExtensionName();


        // 分别在META-INF/dubbo/internal、META-INF/dubbo、META-INF/services目錄下
        // 擷取定義檔案,并且讀取定義檔案中的内容,這裡主要是通過META-INF/dubbo/internal
        // 擷取目标定義檔案
        Map<String, Class<?>> extensionClasses = new HashMap<>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }



    private void cacheDefaultExtensionName() {
        // 擷取目标接口上通過@SPI注解定義的預設子類對應的名稱,并将其緩存在cachedDefaultName中
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation == null) {
            return;
        }

        String value = defaultAnnotation.value();
        if ((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];
            }
        }
    }           
  • loadExtensionClasses()主要是分别從三個目錄中讀取定義檔案,讀取該檔案,并且進行緩存。

loadDirectory()方法的源碼:

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
        String fileName = dir + type;
        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 occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
    }



    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        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;
                            int i = line.indexOf('=');
                            if (i > 0) {
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                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);
                        }
                    }
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", class file: " + resourceURL + ") in " + resourceURL, t);
        }
    }           
  • 這裡主要是對每個目錄進行加載,然後依次加載定義檔案的内容,而對定義檔案内容的處理主要是在loadResource()方法中,在對檔案中每一行記錄進行處理之後,其其最終是調用的loadClass()方法加載目标class的。

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 occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }

        // 如果子類上标注有@Adaptive注解,說明其是一個裝飾類,則将其緩存在cachedAdaptiveClass中,
        // 需要注意的是,一個接口隻能為其定義一個裝飾類
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);

        // 這裡判斷子類是否是一個wrapper類,判斷方式就是檢查其是否有隻含一個目标接口類型參數的構造函數,
        // 有則說明其是一個AOP的wrapper類
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            // 走到這裡說明目前子類不是一個功能型的類,而是最終實作具體目标的子類
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                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)) {
                // 緩存ActivateClass類
                cacheActivateClass(clazz, names[0]);

                // 将目标子類緩存到extensionClasses中
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
    }


    private void cacheActivateClass(Class<?> clazz, String name) {
        // 擷取子類上的@Activate注解,該注解的主要作用是對子類進行分組的,
        // 對于分組之後的子類,可以通過getActivateExtension()來擷取
        Activate activate = clazz.getAnnotation(Activate.class);
        if (activate != null) {
            cachedActivates.put(name, activate);
        } else {
            // 相容alibaba版本的注解
            com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
            if (oldActivate != null) {
                cachedActivates.put(name, oldActivate);
            }
        }
    }


    private void saveInExtensionClass(Map<String, Class<?>> extensionClasses, Class<?> clazz, String name) {
        // 将目标子類緩存到extensionClasses中
        Class<?> c = extensionClasses.get(name);
        if (c == null) {
            extensionClasses.put(name, clazz);
        } else if (c != clazz) {
            String duplicateMsg = "Duplicate extension " + type.getName() + " name " + name + " on " + c.getName() + " and " + clazz.getName();
            logger.error(duplicateMsg);
            throw new IllegalStateException(duplicateMsg);
        }
    }           

loadClass()方法主要作用是對子類進行劃分,這裡主要劃分成了三部分:

  • 使用@Adaptive注解标注的裝飾類;
  • 包含有目标接口類型參數構造函數的wrapper類
  • 目标處理具體業務的子類。

總結而言,getExtension()方法主要是擷取指定名稱對應的子類。在擷取過程中,首先會從緩存中擷取是否已經加載過該子類,如果沒加載過則通過定義檔案加載,并且使用擷取到的wrapper對象封裝目标對象傳回。

getAdaptiveExtension()

  • ExtensionLoader在加載了定義檔案之後會對子類進行一個劃分,使用@Adaptive進行标注的子類和使用@Adaptive标注子類方法。
  • 使用@Adaptive進行标注的子類該子類的作用主要是用于對目标類進行裝飾的,進而實作一定的目的。
  • 使用@Adaptive進行标注的方法,其使用的方式主要是在目标接口的某個方法上進行标注,這個時候,dubbo就會通過javassist位元組碼生成工具來動态的生成目标接口的子類對象,該子類會對該接口中标注了@Adaptive注解的方法進行重寫,而其餘的方法則預設抛出異常,通過這種方式可以達到對特定的方法進行修飾的目的。

getAdaptiveExtension()方法源碼

public T getAdaptiveExtension() {
        // 從緩存中擷取裝飾類的執行個體,存在則直接傳回,不存在則建立一個緩存起來,然後傳回
        Object instance = cachedAdaptiveInstance.get();
        if (instance == 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;
    }           
  • 從緩存中擷取目标類的執行個體,不存在則建立一個該執行個體。

createAdaptiveExtension()方法源碼

private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }


    private Class<?> getAdaptiveExtensionClass() {
        // 擷取目标extensionClasses,如果無法擷取到,則在定義檔案中進行加載
        getExtensionClasses();

       // 如果目标類型有使用@Adaptive标注的子類型,則直接使用該子類作為裝飾類
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }

        // 如果目标類型沒有使用@Adaptive标注的子類型,則嘗試在目标接口中查找是否有使用@Adaptive标注的
        // 方法,如果有,則為該方法動态生成子類裝飾代碼
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

    private Class<?> createAdaptiveExtensionClass() {
        // 建立子類代碼的字元串對象
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();

        // 擷取目前dubbo SPI中定義的Compiler接口的子類對象,預設是使用javassist,
        // 然後通過該對象來編譯生成的code,進而動态生成一個class對象
        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);
    }
           
  • createAdaptiveExtension()首先委托給getAdaptiveExtensionClass()方法擷取一個裝飾類執行個體,然後通過injectExtension()方法調用該執行個體的set方法來注入其所依賴的屬性值。
  • 對于沒有使用@Adaptive标注的子類時,才會使用Javassist來為目标接口生成其子類的裝飾方法。
  • 對于使用@Adaptive标注的子類時,直接傳回子類。
  • createAdaptiveExtensionClass()動态生成目标接口的子類字元串,然後通過javassit來編譯該子類字元串,進而動态生成目标class。

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;
    }           
  • 對于ExtensionLoader的擷取,其實作過程比較簡單,主要是從緩存中擷取,如果緩存不存在,則執行個體化一個并且緩存起來。

ExtensionLoader加載流程圖

Dubbo之SPI實作原理詳解

參考