博客 / 詳情

返回

dubbo spi機制

框架設計的有彈性擴展性好會給使用者帶來很大的方便,如果業務有新的變化只要使用新的實現去替換之前的實現,且框架如何去加載實現我們也不用太關注,顯然這對我們開發者來説是非常友好的。java裏的SPI是這個思想,dubbo裏的SPI同樣是這個思想,關於java裏的spi因為會一次性將擴展點邏輯都執行而顯得不夠靈活,所以這裏不再展開(可以參考java spi),這裏重點説下dubbo裏的SPI.

使用方式
1,在META-INF/services目錄下根據接口的全路徑名創建一個文件,文件內容為key=value的形式
2,ExtensionLoader.getExtensionLoader(xxx.class).getExtension("key")這樣就可以獲取到我們自定義的實現了。

實現分析
下面我們對ExtensionLoader.getExtensionLoader(xxx.class).getExtension(name)這行代碼展開下。

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {

        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        //判斷給定type是否為接口
        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!");
        }
        //先從map裏獲取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:
1,對給定的type進行判斷,一是判斷是否是接口類型;二是判斷接口上是否有SPI註解
2,根據給定的type先從Map獲取對應的ExtensionLoader,要是沒有進行創建。

接下來我們再看看getExtension(name)方法:

public T getExtension(String name) {

        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        //先獲取包裝類Holder
        Holder<Object> holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder<Object>());
            holder = cachedInstances.get(name);
        }
        //再從包裝類裏獲取實現類的實例,需要重點看下createExtension
        Object instance = holder.get();
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    instance = createExtension(name);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

getExtension(name)方法邏輯清晰明瞭:
1,先獲取包裝類Holder
2,從包裝類Holder裏獲取具體實現類的實例。
實現類的實例是如何創建的呢?再看下createExtension(name)

private T createExtension(String name) {

        /**
         * getExtensionClasses會加載META-INF/services/,META-INF/dubbo/,META-INF/dubbo/internal/這三
         * 個目錄下的所有類
         */
        Class<?> clazz = getExtensionClasses().get(name);
        if (clazz == null) {
            throw findException(name);
        }
        try {
            //通過反射的方式創建name對應的實例,並放到map裏,下次可以直接從map裏取
            T instance = (T) EXTENSION_INSTANCES.get(clazz);
            if (instance == null) {
                EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            /**
             * 如果實現類對象裏有set方法,則框架還會根據依賴對象的實際情況進行自動注入的工作
             * 説明,只有滿足下面兩個條件才會進行注入:
             * 1,被依賴的對象也是在services,dubbo,internal這三個目錄下
             * 2,set方法上沒有使用了DisableInject註解
             */
            injectExtension(instance);
            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);
        }
    }

這方法的主要邏輯:
1,通過Class.forName加載META-INF/services/,META-INF/dubbo/,META-INF/dubbo/internal/這三個目錄下的所有類
2,通過反射創建type對應的實例
3,如果符合注入條件,框架還會進行自動注入。

最後用一張圖總結下整個過程:

創建完對象後都會將對象放入緩存方便下次直接獲取。

user avatar lankerens 頭像 mokeywie 頭像 huzilachadedanche 頭像
3 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.