Dubbo2.7的Dubbo SPI實現原理細節

總結/朱季謙

本文主要記錄我對Dubbo SPI實現原理的理解,至於什麼是SPI,我這裏就不像其他博文一樣詳細地從概念再到Java SPI細細分析了,直接開門見山來分享我對Dubbo SPI的見解。

Dubbo SPI的機制比較類似Spring IOC的getBean()加載,當傳入一個存在的beanName,就可以返回該beanName對應的對象。同理,在Dubbo SPI中,我們同樣傳入一個存在的name,Dubbo框架會自動返回該key對應的對象。不難猜測,Dubbo SPI與Spring IOC在底層應該都有一個大致相似的邏輯,簡單的說,就是兩者都可通過beanName或key值,到框架裏的某個map緩存當中,找到對應的class類名,然後對該class類名進行反射生成對象,初始化完成該對象,最後返回一個完整的對象。然而,在這個過程當中,Spirng相對來說會更復雜,它裏面還有一堆後置處理器......

簡單舉一個例子,大概解釋一下Dubbo SPI實現原理,然後再進一步分析源碼。

首先,我在org.apache.dubbo.test目錄下,定義一個@SPI註解到接口:

package org.apache.dubbo.test;

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

@SPI("dog")
public interface Animal {
    void haveBehavior();
}

然後,在同一個目錄下,創建兩個實現該接口的類,分別爲Dog,Cat。

Dog——

package org.apache.dubbo.test;

public class Dog implements Animal {

    @Override
    public void haveBehavior() {
        System.out.println("狗會叫");
    }
}

Cat——

package org.apache.dubbo.test;

public class Cat implements Animal {
    @Override
    public void haveBehavior() {
        System.out.println("貓會抓老鼠");
    }
}

注意看,Animal接口的類名爲org.apache.dubbo.test.Animal,接下來,我們在resource目錄的/META_INF/dubbo需新建一個對應到該接口名的File文件,文件名與Animal接口的類名一致:org.apache.dubbo.test.Animal。之所以兩者名字要一致,是因爲這樣只需拿到Animal接口的類名,到resource目錄的/META_INF/dubbo,就可以通過該類名,定位到與Animal接口相對應的文件。

在Dubbo中,文件名org.apache.dubbo.test.Animal的文件裏,其實存了類似Spring bean那種的數據,即id對應bean class的形式——

cat=org.apache.dubbo.test.Cat
dog=org.apache.dubbo.test.Dog

這兩行數據,分別是Animal接口的實現類Cat和Dog的class全限名。

整個的目錄結構是這樣的——
image

最後寫一個測試類DubboSPITestTest演示一下效果——

package org.apache.dubbo.test;

import org.apache.dubbo.common.extension.ExtensionLoader;
import org.junit.jupiter.api.Test;

class DubboSPITestTest {

    @Test
    public void test(){

        Animal dog = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog");
        dog.haveBehavior();

        Animal cat = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");
        cat.haveBehavior();

    }
}

執行結果如下——

image

先簡單捋一下這個思路是怎麼實現的,ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog")這行代碼內部,會根據Animal接口完整名org.apache.dubbo.test.Animal找到某個指定目錄下的同名File文件org.apache.dubbo.test.Animal,然後按行循環解析文件裏的內容,以key-value形式加載到某個map緩存裏。

類似這樣操作(當然,源碼裏會更復雜些)——

Map<String,String> map = new HashMap<>();
map.put("cat","org.apache.dubbo.test.Cat");
map.put("dog","org.apache.dubbo.test.Dog");

當然,真實源碼裏,value存的是已根據類名得到的Class,但其實無論是類名還是Class,最後都是可以反射生成對象的,

這時候,就可以根據運行代碼去動態獲取到該接口對應的實現類了。例如,需要使用的是org.apache.dubbo.test.Cat這個實現類,那麼在調用getExtension("cat")方法中,我們傳入的參數是"cat",就會從剛剛解析文件緩存的map中,根據map.get("cat")拿到對應org.apache.dubbo.test.Cat。既然能拿到類名了,不就可以通過反射的方式生成該類的對象了嗎?當然,生成的對象裏可能還有屬性需要做注入操作,這就是Dubbo IOC的功能,這塊會在源碼分析裏進一步說明。當對象完成初始化後,就會返回生成的對象指向其接口引用Animal dog = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog")。

整個過程,就是可根據代碼去動態獲取到某個接口的實現類,方便靈活調用的同時,實現了接口和實現類的解耦。

Dubbo SPI是在Java SPI基礎上做了拓展,Java SPI中與接口同名的文件裏,並不是key- value的形式,純粹是按行來直接存放各實現類的類名,例如這樣子——

org.apache.dubbo.test.Cat
org.apache.dubbo.test.Dog

這就意味着,Java SPI在實現過程中,通過接口名定位讀取到resource中接口同名文件時,是無法做到去選擇性地根據某個key值來選擇某個接口的實現類,它只能全部讀取,再全部循環獲取到對應接口實現類調用相應方法。這就意味着,可能存在一些並非需要調用的實現類,也會被加載並生成對象一同返回來,無法做到按需獲取。

因此,Dubbo在原有基礎上,則補充了Java SPI無法按需通過某個key值去調用指定的接口實現類,例如上面提到的,Dubbo SPI可通過cat這個key,去按需返回對應的org.apache.dubbo.test.Cat類的實現對象。

下面就來分析一下具體實現的原理細節,以下代碼做案例。

Animal cat = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");
cat.haveBehavior();

先來分析ExtensionLoader.getExtensionLoader(Animal.class)方法——

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!");
    }
  
    //判斷是否具有@SPI註解
    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;
}

在這個方法裏,若傳進來的Class參數爲空或者非接口或者沒有@SPI註解,都會拋出一個IllegalArgumentException異常,說明傳進來的Class必須需要滿足非空,爲接口,同時具有@SPI註解修飾,才能正常往下執行。我們在這裏傳進來的是Animal.class,它滿足了以上三個條件。

@SPI("cat")
public interface Animal {
    void haveBehavior();
}

接下來,在最後這部分代碼裏,主要是創建一個ExtensionLoader對象。

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對象,該對象包括了兩個東西,一個是type,一個是objectFactory。這兩個東西在後面源碼裏都會用到。
image

創建完ExtensionLoader對象後,就會開始調用getExtension方法——

Animal cat = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");

進入到getExtension("cat")方法當中,內部會調用另一個重載方法getExtension(name, true)。

public T getExtension(String name) {
    return getExtension(name, true);
}

讓我們來看一下該方法內部實現——

public T getExtension(String name, boolean wrap) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    //step 1
    final Holder<Object> holder = getOrCreateHolder(name);
    Object instance = holder.get();
    
    //step 2
    //雙重檢查
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                //創建擴展實例
                instance = createExtension(name, wrap);
                //設置實例到holeder中
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

該方法主要有兩部分。

step 1,先從緩存裏查找name爲“cat”的對象是否存在,即調用getOrCreateHolder(name),在該方法裏,會去cachedInstances緩存裏查找。cachedInstances是一個定義爲ConcurrentMap<String, Holder>的map緩存。若cachedInstances.get(name)返回null的話,說明緩存裏還沒有name對應的對象數據,那麼就會創建一個key值爲name,value值爲new Holder<>()的鍵值對緩存。

private Holder<Object> getOrCreateHolder(String name) {
    Holder<Object> holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder<>());
        holder = cachedInstances.get(name);
    }
    return holder;
}

想必你一定會有疑惑,爲什麼這裏要創建一個new Holder<>()對象呢?

進到Holder類裏,就會發現,其內部用private修飾封裝一個泛型變量value,這就意味着,外部類是無法修改該value值,能起到一個封裝保護的作用。我們正在通過name='cat'去得到一個org.apache.dubbo.test.Cat實現類對象,該對象若能正常生成,最後就會封裝到一個Holder對象裏,再將Holder對象存放到cachedInstances緩存裏。

public class Holder<T> {
    private volatile T value;
    public void set(T value) {
        this.value = value;
    }
    public T get() {
        return value;
    }
}

因此,就會有該從緩存裏獲取Holder的操作——

//根據name爲“cat”去緩存裏查找封裝了org.apache.dubbo.test.Cat對象的Holder對象。
final Holder<Object> holder = getOrCreateHolder(name);
//若能查找到,就從Holder對象取出內部封裝的對象
Object instance = holder.get();

若holder.get()得到的對象爲null,說明還沒有生成該“cat”對應的org.apache.dubbo.test.Cat類對象。

那麼,就會繼續往下執行——

//雙重檢查
if (instance == null) {
    synchronized (holder) {
        instance = holder.get();
        if (instance == null) {
            //創建擴展實例
            instance = createExtension(name, wrap);
            //設置實例到holeder中
            holder.set(instance);
        }
    }
}

這裏用到了一個雙重檢查的操作,避免在多線程情況裏出現某一個線程創建了一半,另一個線程又開始創建同樣對象,就會出現問題。

這行instance = createExtension(name, wrap)代碼,主要實現的功能,就是得到“cat”對應的org.apache.dubbo.test.Cat類對象,然後將返回的對象通過holder.set(instance)封裝在Holder對象裏。

private T createExtension(String name, boolean wrap) {
    // step 1從配置文件中加載所有的擴展類,可得到"配置項名稱"到"配置類"的映射關係表
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            //step 2  將得到的clazz通過反射創建實例對象。
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        //step 3  對實例對象的屬性做依賴注入,即Dubbo IOC邏輯。
        injectExtension(instance);


        if (wrap) {
            List<Class<?>> wrapperClassesList = new ArrayList<>();
            if (cachedWrapperClasses != null) {
                wrapperClassesList.addAll(cachedWrapperClasses);
                wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                Collections.reverse(wrapperClassesList);
            }

            if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                for (Class<?> wrapperClass : wrapperClassesList) {
                    Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                    if (wrapper == null
                            || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                        //將當前instance作爲參數傳給Wrapper的構造方法,並通過反射創建Wrapper實例
                        //然後向Wrapper實例注入依賴,最後將Wrapper實例再次賦值給instance變量
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                    }
                }
            }
        }

        initExtension(instance);
        //step 4 返回初始化完成的對象
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                type + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}

createExtension(String name, boolean wrap)方法裏主要實現了以下

step 1 從配置文件中加載所有的擴展類,可得到"配置項名稱"到"配置類"的映射關係表。

step 2 將得到的clazz通過反射創建實例對象。

step 3 對實例對象的屬性做依賴注入,即Dubbo IOC邏輯。

step 4 返回初始化完成的對象

一、先來看第一步的代碼分析——

// step 1從配置文件中加載所有的擴展類,可得到"配置項名稱"到"配置類"的映射關係表
Class<?> clazz = getExtensionClasses().get(name);

其中getExtensionClasses()方法是獲取返回一個解析完接口對應Resource裏文件的Map<String, Class>緩存,代碼最後部分get(name)在這個案例裏,就是根據“cat”獲得“org.apache.dubbo.test.Cat”的Class。方法內部cachedClasses.get()返回的這個Map> classes正是存放了接口對應Resource文件裏key- value數據,即car=org.apache.dubbo.test.Cat和dog=org.apache.dubbo.test.Dog這類。

private Map<String, Class<?>> getExtensionClasses() {
    //從緩存中獲取已加載的拓展類:car=org.apache.dubbo.test.Cat
    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.get()返回值classes肯定爲null。這裏在classes==null時,同樣使用了一個雙重檢查的操作,最後會去調用loadExtensionClasses()方法,該方法主要做的一件事,就是去讀取到Resource中接口對應的文件進行解析,然後將解析的數據以key-value緩存到Map<String, Class<?>>裏,最後通過cachedClasses.set(classes)存入到cachedClasses裏,這裏的cachedClasses同樣是一個final定義的Holder對象,作用與前文提到的一致,都是封裝在內部以private修飾,防止被外部類破壞。

主要看下loadExtensionClasses()內部邏輯——

private Map<String, Class<?>> loadExtensionClasses() {
    //step 1 對SPI註解進行解析,獲取SPI默認對value
    cacheDefaultExtensionName();

    Map<String, Class<?>> extensionClasses = new HashMap<>();

    /**
     * step 2 strategies包含以下四種策略,代表查找四種不同目錄下的文件:
     *
     *      DubboInternalLoadingStrategy 表示目錄"META-INF/dubbo/internal/"
     *      DubboExternalLoadingStrategy 表示目錄""META-INF/dubbo/external/""
     *      DubboLoadingStrategy 表示目錄"META-INF/dubbo/"
     *      ServicesLoadingStrategy 表示目錄"META-INF/services/"
     */
    for (LoadingStrategy strategy : strategies) {
        //加載指定文件夾下對配置文件,找到SPI默認對value的class
        //apache
        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值。例如,在該例子裏,Animal的註解@SPI("cat"),那麼,通過cacheDefaultExtensionName()方法,即能獲取到註解@SPI裏的默認值“cat”。之所以獲取該註解的值,是用來當做默認值,即如果沒有傳入指定需要獲取的name,那麼就返回cat對應的類對象。

image

接着,就是遍歷四種不同目錄,查找是否有與接口Animal對應的文件。這裏的strategies是一個數組,裏面包含四種對象,每個對象代表查找某個目錄,包括"META-INF/dubbo/internal/"、"META-INF/dubbo/external/"、"META-INF/dubbo/"、"META-INF/services/",表示分別到這四種目錄去查看是否有滿足的文件。

for循環裏調用了兩次loadDirectory方法,羣分就是一個是查找apache版本的,一個是查找alibaba版本的,兩方法底層其實都是一樣,只需要關注其中一個實現即可。

   /**
     * step 2 strategies包含以下四種策略,代表查找四種不同目錄下的文件:
     *
     *      DubboInternalLoadingStrategy 表示目錄"META-INF/dubbo/internal/"
     *      DubboExternalLoadingStrategy 表示目錄"META-INF/dubbo/external/"
     *      DubboLoadingStrategy 表示目錄"META-INF/dubbo/"
     *      ServicesLoadingStrategy 表示目錄"META-INF/services/"
     */
    for (LoadingStrategy strategy : strategies) {
        //加載指定文件夾下對配置文件,找到SPI默認對value的class
        //apache
        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());
    }

在loadDirectory方法裏,就是定位到接口對應的File文件,獲取文件的路徑,然後調用loadResource方法對文件進行解析——

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                           boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
    //文件夾路徑+type 全限定名:META-INF/dubbo/internal/org.apache.dubbo.test.Animal
    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);
    }
}

loadResource方法主要是讀取File文件資源,然後循環遍歷文件裏的每一行記錄,跳過開頭爲#的註釋記錄,對cat=org.apache.dubbo.test.Cat形式的行記錄進行切割。通過這行代碼int i = line.indexOf('=')定位到等於號=的位置,然後以name = line.substring(0, i).trim()來截取等於號前面的字符串作爲key, 以 line = line.substring(i + 1).trim()截取等於號=後面的字符串作爲value,形成key-value鍵值對形式數據,進一步傳到 loadClass方法進行相應緩存。

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;
                        int i = line.indexOf('=');
                        if (i > 0) {
                            //以等於號 = 爲界,讀取key健 value值
                            name = line.substring(0, i).trim();
                            line = line.substring(i + 1).trim();
                        }
                        if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
                            //加載類,通過loadClass對類進行緩存
                            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);
    }
}

以cat=org.apache.dubbo.test.Cat數據爲例子,debug可以看到,最後解析得到的爲——

image

最後,到loadClass看一下是怎麼對從文件裏解析出來的key-value數據進行緩存,注意一點是,在執行該方法時,已將上文拿到的line="org.apache.dubbo.test.Cat"通過Class.forName(line, true, classLoader)生成了對應的Class。

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.");
    }
    //檢測目標類上是否有Adaptive註解
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        //設置cachedadaptiveClass緩存
        cacheAdaptiveClass(clazz, overridden);
    } else if (isWrapperClass(clazz)) {
        cacheWrapperClass(clazz);
    } else {
        //程序進入此分支,表明class是一個普通的拓展類
        //檢測class是否有默認的構造方法,如果沒有,則拋出異常
        clazz.getConstructor();
        if (StringUtils.isEmpty(name)) {
            //如果name爲空,則嘗試從Extension註解中獲取name,或使用小寫的類名作爲name
            name = findAnnotationName(clazz);
            if (name.length() == 0) {
                throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
            }
        }
        //names = ["cat"]
        String[] names = NAME_SEPARATOR.split(name);
        if (ArrayUtils.isNotEmpty(names)) {
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                //存儲Class到名稱的映射關係
                cacheName(clazz, n);
                //存儲名稱到Class的映射關係
                saveInExtensionClass(extensionClasses, clazz, n, overridden);
            }
        }
    }
}

這裏只需要關注最後的saveInExtensionClass方法,可以看到,最後將從文件裏解析出來的“cat”-->org.apache.dubbo.test.Cat存入到Map<String, Class<?>> extensionClasses緩存當中。

image

這個Map<String, Class<?>> extensionClasses緩存是在loadExtensionClasses()方法裏創建的,該loadExtensionClasses方法最後會將extensionClasses進行返回。

private Map<String, Class<?>> loadExtensionClasses() {
    ......
    //創建用來緩存存儲解析文件裏key-value數據
    Map<String, Class<?>> extensionClasses = new HashMap<>();

    for (LoadingStrategy strategy : strategies) {
        loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
        ......

    return extensionClasses;
}

到這一步,就完成了Animal接口對應的resource/META-INF/dubbo/org.apache.dubbo.test.Animal文件的解析,解析出來的數據存放到了extensionClasses這個Map緩存裏。

image

回顧先前的方法調用,可以看到,最終得到extensionClasses的map緩存,會返回到getExtensionClasses()方法,因此,在createExtension調用getExtensionClasses().get(name),就相當於是調用extensionClasses.get(name)。因爲傳到方法裏的參數name="cat",故而返回的Class即org.apache.dubbo.test.Cat。

image

接着往下執行,到代碼EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance())就是通過clazz.newInstance()反射創建了一個暫時還是空屬性的對象,同時緩存到EXTENSION_INSTANCES緩存裏,這是一個ConcurrentMap<Class<?>, Object>緩存,避免反覆進行反射創建對象。
image

實例化完成org.apache.dubbo.test.Cat對象的創建,接下來就是通過injectExtension(instance)對對象進行依賴注入了。主要功能就類似Spring IOC裏bean存在@Resource或者@Autowired註解的屬性時,該bean在實例化創建完對象後,需要對屬性進行補充,即將@Resource或者@Autowired註解的屬性通過反射的方式,指向另外的bean對象。在Dubbo IOC裏,同樣是類似的實現。首先,會過濾掉那些非setXxx()的方法,只對setXxx()方法處理。這種處理方式,就是截取set後面的字符,例如,存在這樣一個setHitInformService (HitInformService hitInformService)方法,那麼就會截取set後面的字符,並且對截取後的第一個字符做小寫處理,得到“hitInformService”。注意一點是,同時,會獲取參數裏的類型HitInformService,如果類型爲數組、String、Boolean、Character、Number、Date其中一個,則不會對其進行注入操作。反之,就會繼續往下執行。

/**
 * Dubbo IOC目前僅支持setter方式注入
 * @param instance
 * @return
 */
private T injectExtension(T instance) {

    if (objectFactory == null) {
        return instance;
    }

    try {
        //遍歷目標類的所有方法
        for (Method method : instance.getClass().getMethods()) {
            //檢測方法是否以set開頭,且方法僅有一個參數,且方法訪問級別爲public
            if (!isSetter(method)) {
                continue;
            }
            /**
             * Check {@link DisableInject} to see if we need auto injection for this property
             */
            if (method.getAnnotation(DisableInject.class) != null) {
                continue;
            }
            //獲取setter方法參數類型
            Class<?> pt = method.getParameterTypes()[0];
            //判斷該對象是否爲數組、String、Boolean、Character、Number、Date類型,若是,則跳出本次循環,繼續下一次循環
            if (ReflectUtils.isPrimitives(pt)) {
                continue;
            }

            try {
                //獲取屬性名,比如 setName方法對應屬性名name
                String property = getSetterProperty(method);
                /**
                 * objectFactory 變量的類型爲 AdaptiveExtensionFactory,
                 *      AdaptiveExtensionFactory 內部維護了一個 ExtensionFactory 列表,用於存儲其他類型的 ExtensionFactory。
                 *
                 * Dubbo 目前提供了兩種 ExtensionFactory,分別是 SpiExtensionFactory 和 SpringExtensionFactory。
                 *      前者用於創建自適應的拓展,後者是用於從 Spring 的 IOC 容器中獲取所需的拓展。
                 *
                 */
                //從ObjectFactory中獲取依賴對象
                Object object = objectFactory.getExtension(pt, property);
                if (object != null) {
                    //通過反射調用setter方法依賴
                    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;
}

執行 Object object = objectFactory.getExtension(pt, property)到行代碼,就是去獲取HitInformService hitInformService引用對應的對象,這裏獲取有兩種方式,一種是通過SpringExtensionFactory去通過getBean(name)走Spring加載bean的方式獲取對象,另一種是通過本文的Dubbo SPI方式,根據name去解析文件裏對應的接口實現類Class反射生成返回。

無論是通過哪種方式,最後都需獲取返回一個對象,然後通過method.invoke(instance, object)反射去執行對應的setXxx()方法,將對象進行屬性注入到前文SPI創建的對象cat裏。

到這裏,就完成了接口Animal對應cat這個實現類的創建了,這個過程,就是Dubbo SPI的底層實現細節。最後,將得到的org.apache.dubbo.test.Cat對象向上指向其接口Animal引用,通過接口就可以調用該實現類重寫的haveBehavior方法了。

Animal cat = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("cat");
cat.haveBehavior();

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