(四)Dubbo的SPI機制的底層是如何實現的?

(一)什麼是SPI機制?Java中的SPI機制是如何實現的?

(1)首先先說一下JavaSPI機制(Service Provider Interface)其實說白了就是定義一個接口,但是可以有多個實現該接口的實現類,其實也是一種服務發現機制。

  • 其實SPI機制的本質就是將接口實現類的全限定名配置在文件中,並由服務加載器讀取配置文件,加載實現類。這樣可以在運行時,動態爲接口替換實現類

(2)那麼Dubbo中的SPI機制和Java中的SPi機制又有什麼區別呢?

  • 首先Java中的SPI機制是基於接口的編程+策略模式+配置文件的組合方式實現的動態加載機制

  • 其實具體的JavaSPI的實現就是:

    • 1.你先定義一個A接口,比如是com.wangwei.A這個接口
    • 2.然後你需要在src/main/resources/ 下建立 /META-INF/services 目錄下定義和A接口相同的文件com.wangwei.A名字,然後需要在這個com.wangwei.A文件中 定義一些你需要實現這個接口的實現類這裏有多個實現類的
    • 3.然後你在使用的時候需要去遍歷這個接口的所有實現類,然後拿到你想要拿到的實現類,然後使用,是這樣的一個流程。

在這裏插入圖片描述
那麼其實JavaSPI機制的底層實現原理:

其實主要ServiceLoader這個類進行去加載的


public final class ServiceLoader<S>
    implements Iterable<S>
{

    private static final String PREFIX = "META-INF/services/";

    // The class or interface representing the service being loaded
    private final Class<S> service;

    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;

    // The access control context taken when the ServiceLoader is created
    private final AccessControlContext acc;

    // Cached providers, in instantiation order
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // The current lazy-lookup iterator
    private LazyIterator lookupIterator;

這裏中間就直接省略了。。。。。。

  private class LazyIterator  implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

JavaSP機制的缺點:

  • 雖然ServiceLoader也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實現類全部加載並實例化一遍。如果你並不想用某些實現類,它也被加載並實例化了,這就造成了浪費。獲取某個實現類的方式不夠靈活,只能通過Iterator形式獲取,不能根據某個參數來獲取對應的實現類。
  • 多個併發多線程使用ServiceLoader類的實例是不安全的。
  • 擴展如果依賴其他的擴展,做不到自動注入和裝配
  • 不提供類似於Spring的IOC和AOP功能
  • 擴展很難和其他的框架集成,比如擴展裏面依賴了一個Spring bean,原生的Java SPI不支持

(二)dubbo的SPI機制底層是如何實現的?

(1)dubbo中的SPI機制

  • 其實爲什麼在dubbo中又重新定義了一套dubbo的SPI機制,就是因爲java中的SPI機制的一些缺點,不能滿足dubbo的使用場景或者說效率沒有那麼高效
  • 所以dubbo自己封裝了一套更強大的SPI機制,直接封裝到了ExtensionLoader 類中,通過 ExtensionLoader,我們可以加載指定的實現類。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路徑,並且可以直接給實現類進行一個命名,然後在使用的時候,直接通過@SPI(“名字”)去找到具體的實現類,比如:random(名字)=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance(實現類)

(2)dubbo中SPI機制的使用流程:

  1. 首先如果你想給一個接口需要用dubbo的SPI機制實現,那麼首先你需要給這個接口上加上@SPI(“使用默認的實現類的名字”)這個註解
  2. 然後需要在你接口中的方法上加上一個@Adaptive註解,這個註解的核心功能就是:會爲該方法生成對應的代碼。方法內部會根據方法的參數,來決定使用哪個擴展,@Adaptive註解用在類上代表實現一個裝飾類,類似於設計模式中的裝飾模式
  3. 然後通過你在配置文件中配置好並實現你需要的實現類,然後供其進行調用完成,類似下面
random=com.alibaba.dubbo.rpc.cluster.loadbalance.RandomLoadBalance
roundrobin=com.alibaba.dubbo.rpc.cluster.loadbalance.RoundRobinLoadBalance
leastactive=com.alibaba.dubbo.rpc.cluster.loadbalance.LeastActiveLoadBalance
consistenthash=com.alibaba.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance

(3)dubbo是如何實現的這個增強版的SPI機制的?源碼級別

  1. 首先dubbo其實是通過 ExtensionLoader 的 getExtensionLoader 方法獲取一個ExtensionLoader 實例,然後再通過 ExtensionLoader 的 getExtension 方法獲取拓展類對象
  2. getExtension方法中的核心就是創建一個擴展對象
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);
    }
    Object instance = holder.get();
    // 雙重檢查
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                // 創建拓展實例
                instance = createExtension(name);
                // 設置實例到 holder 中
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}
  1. 然後到createExtension()這個方法中主要的流程是:
    • 通過 getExtensionClasses 獲取所有的拓展類
    • 通過反射創建拓展對象
    • 向拓展對象中注入依賴(這裏使用的是 Dubbo IOC 與 AOP 的具體實現)
    • 將拓展對象包裹在相應的 Wrapper 對象中(這裏使用的是 Dubbo IOC 與 AOP 的具體實現)
private T createExtension(String name) {
    // 從配置文件中加載所有的拓展類,可得到“配置項名稱”到“配置類”的映射關係表
    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);
        }
        // 向實例中注入依賴
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
            // 循環創建 Wrapper 實例
            for (Class<?> wrapperClass : wrapperClasses) {
                // 將當前 instance 作爲參數傳給 Wrapper 的構造方法,並通過反射創建 Wrapper 實例。
                // 然後向 Wrapper 實例中注入依賴,最後將 Wrapper 實例再次賦值給 instance 變量
                instance = injectExtension(
                    (T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("...");
    }
}
  1. 然後是getExtensionClasses()方法去加載,如果 classes 仍爲 null,則通過 loadExtensionClasses 加載拓展類
private Map<String, Class<?>> getExtensionClasses() {
    // 從緩存中獲取已加載的拓展類
    Map<String, Class<?>> classes = cachedClasses.get();
    // 雙重檢查
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // 加載拓展類
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}
  1. 然後是loadExtensionClasses()方法,主要做了兩件事:一是對 SPI 註解進行解析,二是調用 loadDirectory 方法加載指定文件夾配置文件
private Map<String, Class<?>> loadExtensionClasses() {
    // 獲取 SPI 註解,這裏的 type 變量是在調用 getExtensionLoader 方法時傳入的
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if (defaultAnnotation != null) {
        String value = defaultAnnotation.value();
        if ((value = value.trim()).length() > 0) {
            // 對 SPI 註解內容進行切分
            String[] names = NAME_SEPARATOR.split(value);
            // 檢測 SPI 註解內容是否合法,不合法則拋出異常
            if (names.length > 1) {
                throw new IllegalStateException("more than 1 default extension name on extension...");
            }

            // 設置默認名稱,參考 getDefaultExtension 方法
            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;
}

總結dubbo的SPI機制的特點:(符合Dubbo的微內核+插件的總體設計思想)

首先dubbo的核心都是基於Dubbo的微內核+插件的機制,而SPI機制更是dubbo中最核心之一的地方,他的設計符合開閉原則,有很好的擴展性。

  • 對Dubbo進行擴展,不需要改動Dubbo的源碼,自定義的Dubbo的擴展點實現,是一個普通的Java類,Dubbo沒有引入任何Dubbo特有的元素,對代碼侵入性幾乎爲零。
  • 將擴展註冊到Dubbo中,只需要在ClassPath中添加配置文件。使用簡單。而且不會對現有代碼造成影響。符合開閉原則
  • Dubbo的擴展機制支持IoC,AoP等高級功能
  • Dubbo的擴展機制能很好的支持第三方IoC容器,默認支持Spring Bean,可自己擴展來支持其他容器,比如Google的Guice
  • 切換擴展點的實現,只需要在配置文件中修改具體的實現,不需要改代碼。使用方便

(三)dubbo中的SPI機制和Java中的SPI機制有什麼區別?

看完上面的講解,想必你自己肯定也能猜到dubbo的SPI機制和Java中的SPI機制有什麼區別,不然我這篇博客不是白寫了嗎,哭唧唧。。。。歡迎探討

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