Dubbo源碼分析(一)dubbo SPI擴展點加載之擴展點自動包裝

dubbo最核心的思想就是就是SPI服務發現,可以實現動態加載實現類。http://dubbo.apache.org/zh-cn/docs/dev/SPI.html 在dubbo的官方文檔中用擴展點的介紹。我們可以看到,dubbo擴展點主要有四大特性

  1. 擴展點自動包裝。
  2. 擴展點自動裝配.
  3. 擴展點自適應.
  4. 擴展點自動激活
    接下來我會以Dubbo的Protocol 接口爲例來說明一下dubbo的四大特性。
@SPI("dubbo")
public interface Protocol {

    int getDefaultPort();
    
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
    
    void destroy();

}

我們首先看第一條,擴展點自動包裝,自動包裝擴展點的Wrapper類。ExtensionLoader在加載擴展點時,如果加載到的擴展點有拷貝構造函數,則判定爲擴展點Wrapper類。以Protocol爲例。我們可以看一下Protocol的繼承關係。
在這裏插入圖片描述
我們可以看到,其中有兩個是以Wrapper結尾,點進去看一下這兩個類,我們可以看到,他們都是有一個構造函數,參數是Protocol,也就是拷貝構造函數。那麼,當dubbo掃描到這兩個類時,就會把他們當作Protocol的包裝類。
在這裏插入圖片描述
在這裏插入圖片描述
接下來再看官方文檔,下面有介紹:“Wrapper 類同樣實現了擴展點接口,但是 Wrapper 不是擴展點的真正實現。它的用途主要是用於從 ExtensionLoader 返回擴展點時,包裝在真正的擴展點實現外。即從 ExtensionLoader 中返回的實際上是 Wrapper 類的實例,Wrapper 持有了實際的擴展點實現類。”
也就是我們拿到的擴展點實例其實都是經過了Wrapper包裝。
接下來我們可以再去源碼看看他是怎麼實現的。我們看一下Dubbo ExtensionLoader中的源碼

    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, (T) clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
            injectExtension(instance);
            
            // 重點來了,這裏會拿到dubbo掃描的wrapper類,並通過循環
            // 調用每一個wrapper類的構造參數爲擴展點類的構造函數,
            // 所以我們最後拿到的實例是經過了一層又一層的包裝後的實例,
            // 有點類似於aop
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (wrapperClasses != null && wrapperClasses.size() > 0) {
                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);
        }
    }

接下來再看一下dubbo是如何尋找wrapper類的,繼續看源碼

        private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
        String fileName = dir + type.getName();
        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 url = urls.nextElement();
                    try {
                        BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
               ..............,
                省略讀配置文件的代碼
               .................
               // 此處是關鍵,我們通過讀配置文件拿到了class類,通過反射調用,判斷
               // 是否有拷貝構造函數,有的話說明該類是包裝類,就會加入到緩存中,
               // 沒有就會執行catch中的代碼
                         try {
                           clazz.getConstructor(type);
                            Set<Class<?>> wrappers = cachedWrapperClasses;
                          if (wrappers == null) {
                              cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                               wrappers = cachedWrapperClasses;
                           }
                            wrappers.add(clazz);
                        } catch (NoSuchMethodException e) {
                           clazz.getConstructor();
                    

接下來的兩大特性,在下次會繼續介紹

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