Dubbo SPI(一)

ExtensionLoader

這是構(gòu)成dubbo spi內(nèi)核的主要類,因此是閱讀dubbo源碼必須要先了解的類。

getExtensionLoader

ExtensionLoader的構(gòu)造方法是私有的,唯一得到實(shí)例的方法就是這個(gè)靜態(tài)方法。它確保了type是接口,并且通過(guò)withExtensionAnnotation方法確保接口上有SPI注解,然后構(gòu)造實(shí)例對(duì)象,并放入EXTENSION_LOADERS靜態(tài)域緩存。

getExtensionClasses

private Map<String, Class<?>> getExtensionClasses() {
    //先從類實(shí)例緩存中加載
    Map<String, Class<?>> classes = cachedClasses.get();
    //經(jīng)典的單例寫(xiě)法
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                //加載擴(kuò)展類并放入緩存
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

這是ExtensionLoader中一個(gè)無(wú)參的私有方法,用來(lái)加載spi,可以看到在類中的很多方法都會(huì)先調(diào)用該方法。為什么不在構(gòu)造方法中直接調(diào)用該方法呢?我覺(jué)得可能的原因是在真正使用時(shí)加載指定接口的擴(kuò)展,以節(jié)約資源。

Holder

注意上面的cachedClasses,他的類型是dubbo的Holder類。

public class Holder<T> {
    //volatile保證值多線程可見(jiàn)
    private volatile T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }

}

對(duì)于這個(gè)類的用途,不是很理解,并且和AtomicReference感覺(jué)有點(diǎn)相似。
偶然有一天看到一個(gè)場(chǎng)景,可能是其用途所在,僅個(gè)人理解。代碼如下:

public void test(boolean flag) throws Exception {
    Thread.sleep(5000);
    if (flag) {
        System.out.println("now is true");
    }
}

flag作為方法的入?yún)?,初始值肯定是?zhǔn)確的,但是在線程暫停的5秒內(nèi),很有可能外部實(shí)際的值已經(jīng)改變了,但是方法內(nèi)部判斷的依舊是舊值,此時(shí)就出現(xiàn)了錯(cuò)誤的結(jié)果。如果用Holder或者AtomicBoolean包起來(lái),那就可以得到當(dāng)前的準(zhǔn)確值。

  • 注意以上對(duì)Holder的理解是錯(cuò)誤的,查看Holder的實(shí)例是如何使用的就可發(fā)現(xiàn),其目的是在加鎖時(shí)有一個(gè)局部鎖!!

loadExtensionClasses

如果在緩存中沒(méi)有拿到擴(kuò)展類,那么就會(huì)調(diào)用該方法,加載所有的擴(kuò)展類。dubbo指定了三個(gè)資源目錄,分別為:META-INF/services/;META-INF/dubbo/;META-INF/dubbo/internal/。

private Map<String, Class<?>> loadExtensionClasses() {
    //得到spi注解
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if (defaultAnnotation != null) {
        //得到默認(rèn)的擴(kuò)展對(duì)象名字
        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));
            }
            //緩存默認(rèn)擴(kuò)展類名字
            if (names.length == 1) cachedDefaultName = names[0];
        }
    }
    //擴(kuò)展類名字和類對(duì)象的map
    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    //加載指定的三個(gè)目錄的文件名是接口全類名的文件
    //這里replace應(yīng)該是dubbo加入apache項(xiàng)目后做的適配
    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;
}

loadDirectory

加載指定目錄的擴(kuò)展類。

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
    //目錄+接口全類名
    String fileName = dir + type;
    try {
        Enumeration<java.net.URL> urls;
        //得到ExtensionLoader的類加載器
        ClassLoader classLoader = findClassLoader();
        if (classLoader != null) {
            //不是bootstrap加載器,直接使用類加載器加載資源
            urls = classLoader.getResources(fileName);
        } else {
            //是bootstrap加載器,調(diào)用系統(tǒng)加載器
            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

加載具體的某個(gè)資源文件。

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;
            //按行讀取文件內(nèi)容
            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;
                        //根據(jù)=得到名字和擴(kuò)展類的全類名
                        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) {
                        //緩存加載某個(gè)類出現(xiàn)的異常
                        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);
    }
}

這里將加載某個(gè)擴(kuò)展類出現(xiàn)的異常緩存了起來(lái),不直接拋出保證了可以繼續(xù)加載其他擴(kuò)展類,然后在使用該擴(kuò)展時(shí)從緩存中拿到該異常報(bào)錯(cuò)。

loadClass

加載資源文件內(nèi)的指定擴(kuò)展類。

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    //判斷是否是接口的實(shí)現(xiàn)類
    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注解,該注解表示該擴(kuò)展類是適配類
    //然后放入cachedAdaptiveClass緩存,如果有多個(gè)報(bào)異常
    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());
        }
        //判斷是否是包裝擴(kuò)展類,是就放入cachedWrapperClasses緩存
        //判斷是否有該接口類型入?yún)⒌臉?gòu)造方法,可以查看isWrapperClass方法
    } else if (isWrapperClass(clazz)) {
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    } else {
        //確保有無(wú)參構(gòu)造方法
        clazz.getConstructor();
        //如果沒(méi)有用=指定名字就先通過(guò)Extension注解拿(不過(guò)貌似已經(jīng)不推薦了)
        //再通過(guò)類名拿(類名后綴是接口類名的話會(huì)去除)
        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注解表明其是激活擴(kuò)展,以后會(huì)講到這個(gè)注解的作用
            Activate activate = clazz.getAnnotation(Activate.class);
            if (activate != null) {
                //緩存激活擴(kuò)展類
                cachedActivates.put(names[0], activate);
            } else {
                // support com.alibaba.dubbo.common.extension.Activate
                com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
                if (oldActivate != null) {
                    cachedActivates.put(names[0], oldActivate);
                }
            }
            for (String n : names) {
                if (!cachedNames.containsKey(clazz)) {
                    cachedNames.put(clazz, n);
                }
                //放入緩存
                Class<?> c = extensionClasses.get(n);
                if (c == null) {
                    extensionClasses.put(n, clazz);
                    //名字重復(fù)異常
                } else if (c != clazz) {
                    throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                }
            }
        }
    }
}

本文總結(jié)

SPI

接口上必須要有該注解,該注解的值指定了默認(rèn)的擴(kuò)展實(shí)現(xiàn)的名字。

dubbo加載目錄

  1. META-INF/services/
  2. META-INF/dubbo/
  3. META-INF/dubbo/internal/

dubbo類名來(lái)源

  1. 資源文件中通過(guò)=指定,可以通過(guò),分隔(,兩邊支持空格)
  2. 通過(guò)Extension注解指定(Deprecated)
  3. 通過(guò)類名拿到(截去接口類名)

之前我們看到exceptions緩存了加載某個(gè)擴(kuò)展類出現(xiàn)的異常,緩存的鍵是文件的一行(去掉了注釋),后續(xù)是通過(guò)擴(kuò)展類名去拿緩存的,所以第二種方式顯然不能匹配得到。

擴(kuò)展類分類

  1. 默認(rèn)擴(kuò)展,spi注解指定
  2. 適配擴(kuò)展,Adaptive注解指定,只能有一個(gè)
  3. 包裝擴(kuò)展,有接口類型入?yún)⒌臉?gòu)造方法
  4. 激活擴(kuò)展,Activate指定,可以通過(guò)該注解的幾個(gè)值指定是否生效,filter就通過(guò)該注解
  5. 一般擴(kuò)展,cachedClasses緩存了名字和類的關(guān)系,cachedNames緩存了類和首個(gè)名字的關(guān)系
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容