Dubbo 源碼解讀——自定義 Classloader 之 ExtensionLoader

    衆所周知,Dubbo 是阿里巴巴公司自主研發開源的一個高性能的服務框架(現已捐獻給 Apache 基金會組織),應用之間能夠經過 RPC 的方式來互相調用並返回結果。主要基於 Java 語言開發,它提供了三大核心能力:java

        1. 面向接口的遠程方法調用;git

        2. 智能容錯和負載均衡;github

        3. 以及服務自動註冊和發現;apache

   (圖來自 dubbo 官網)緩存

 對於 Dubbo 的特性,我這裏不作過多的介紹。接下來,進入正題。併發

1. 首先,下載源碼,源代碼地址爲:app

git clone https://github.com/apache/incubator-dubbo.git負載均衡

,若是網速很差的話,耗費時間會稍長一些,請耐心等待。( 本人版本爲:2.7.0-SNAPSHOT)框架

 

2. 下載成功後,用 IDEA 打開,以下圖:ide

3. 下面咱們進入正題, 直接看  dubbo-common 模塊中的 org.apache.dubbo.common.extension 包,也就是本文重點解讀的自定義 ClassLoader 位置。Dubbo 擴展點主要都是從 ExtensionFactory 接口進行的,咱們從這個接口類開始進行分析。

4.  ExtensionFactory 接口很簡單,裏面只有一個方法  getExtension, 而這個 接口一共有三個實現類。分別爲:SpiExtensionFactory (SPI 的方式類動態加載)、AdaptiveExtensionFactory(Adaptive 的方式類動態加載)、SpringExtensionFactory(Spring 的方式類動態加載), 這裏咱們重點以 SpiExtensionFactory 的方式進行分析。

/**
 * ExtensionFactory
 */
@SPI
public interface ExtensionFactory {

    /**
     * Get extension.
     *
     * @param type object type.
     * @param name object name.
     * @return object instance.
     */
    <T> T getExtension(Class<T> type, String name);

}

5.  進行  SpiExtensionFactory 類中,只有一個 ExtensionFactory 的實現方法。首先,判斷該 type 是否爲接口類型,而且具備 SPI  註解的標識,則調用 自定義擴張類加載器,對 type 類進行動態加載,若是加載成功,則返回該  type 自適應的類的對象實例。

public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}

6. 接下來,咱們進入  ExtensionLoader.getExtensionLoader(type); 進行分析。對 type 進行類型檢查判斷,若是不符合要求拋出異常,最後由 EXTENSION_LOADERS.get(type) 獲取該類的擴展的 ClassLoader 。

    EXTENSION_LOADERS  爲  ConcurrentMap, 其中 Key 爲  Class<?> 類型,Value 爲  ExtensionLoader<?> 類型。也就是說,對應的類有本身的自定義類加載器。

    在這裏獲取的時候爲 Null , 接下來 new ExtensionLoader 對象,構造方法中,傳入 type ,並回放到 ConcurrentMap 中。

        最終,返回  type 對應的自定義  ExtensionLoader 對象。

@SuppressWarnings("unchecked")
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 interface!");
    }
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type(" + type +
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }

    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;
}

 

7. 進入到  new ExtensionLoader<T>(type)  中,繼續閱讀源碼。 這裏須要注意,該類的構造方法爲私有! type 賦值給了全局變量 type 。

private ExtensionLoader(Class<?> type) {
    this.type = type;
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

 

8.  接下來是  ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()) 方法。

這裏是連着兩個調用, getExtensionLoader 咱們在第 6 步的時候已經閱讀過,返回的對象是參數 type 的類加載器。這裏返回的是 ExtensionFactory 類加載器,進入到  getAdaptiveExtension() 方法繼續閱讀。

 

9.  從 cachedAdaptiveInstance 獲取實例, cachedAdaptiveInstance 是一個泛型類,其中只有兩個方法  get 、set 兩個方法。 還有一個被  volatile 表示的屬性 value 。主要是爲了將動態加載類對象  instance 臨時緩存。

    接下來,對 createAdaptiveInstanceError 進行判斷。若是不爲空,直接拋出 IllegalStateException 信息。

   再往下走,對 cachedAdaptiveInstance 進行加鎖,再進行一次 非空判斷(你應該可以想到了——單例模式),結合上面的  private 構造函數、volatile 和 雙重檢查判斷,能夠說種種跡象都是爲了不併發形成多個對象建立出現問題。

      下面進入真正的建立實例方法 createAdaptiveExtension();

@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        if (createAdaptiveInstanceError == null) {
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        instance = createAdaptiveExtension();
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        createAdaptiveInstanceError = t;
                        throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                    }
                }
            }
        } else {
            throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
        }
    }

    return (T) instance;
}

10.  在該方法中,首先是獲取 class ,最後利用反射成該對象。

@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
}

 

11.  調用  getExtensionClasses() ,加載可擴展類。接着判斷  cachedAdaptiveClass 是否爲空,若是爲空,說明該  cachedAdaptive 類還麼有初始化,則對其進行初始化。以後,返回該 class 對象。

private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

 

12.  從緩存 Holder 裏面獲取該類 class ,第一次初始化時爲空,對其加鎖,同步的方式進行加載;

    並對其進行雙重檢查;執行 loadExtensionClasses() 加載改 class ,  並將該 class 類型存入到 cachedClasses 中。這樣作有一個好處就是,只須要初始化一次,後期若是須要加載,直接從內存中獲取便可。 

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;
}

 

13.  因爲在在上一步的時候,咱們有看到 synchronized ,因此,這裏的 反射加載爲 同步的方式。

    獲取該擴展類上的 SPI 註解信息(關於 SPI 註解,該類比較簡單,只有 value 屬性,讀者能夠自行查看),獲取其 value 值,以該值做爲 cacheName  的 Key。若是 value 值爲多個值,以 「,」 分割,取第一個爲 cacheName 。

    緊接着,分別加載  classpath  下  【META-INF/services/ 】、【META-INF/services/internal/

】和 【META-INF/dubbo/ 】文件夾中的 SPI 配置。

    type.name 將 com.alibaba 替換爲  org.apache 是爲了確保,須要加載的類包老版本兼容問題。統一成  org.apache 版本。

// synchronized in getExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if (defaultAnnotation != null) {
        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));
            }
            if (names.length == 1) cachedDefaultName = names[0];
        }
    }

    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    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;
}

 

14.    獲取 ExtensionLoader 類的  classloader, 若是獲取到,使用該 classloader 加載 type 類;未獲取到,則使用 java.lang.ClassLoader  從全部的搜索路徑中查找 type 名稱的資源。正常獲取到了,則進行加載 資源。

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
    String fileName = dir + type;
    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 resourceURL = urls.nextElement();
                loadResource(extensionClasses, classLoader, resourceURL);
            }
        }
    } catch (Throwable t) {
        logger.error("Exception when load extension class(interface: " +
                type + ", description file: " + fileName + ").", t);
    }
}

15.  使用流的方式加載資源(也就是上面提到的  META-INF 目錄下的文件),按行讀取,並進行加載。這裏有一個小細節,須要說明一下,按行讀取後,使用了 「=」 分隔符,前面爲 name 後面爲須要加載的class 類。(爲了節省篇幅,這裏略去部分代碼)        

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
        ......
        BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
        ......
            while ((line = reader.readLine()) != null) {
        ......
                            name = line.substring(0, i).trim();
                            line = line.substring(i + 1).trim();

                            loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
        ......
                      }
                }
            }
        ......


}

        也就是爲何咱們可以看到在相似下圖文件中所看到的內容(JDK 自帶的 SPI 方式,是不能解析  xx=xxx.xxx ),dubbo 經過擴展的方式,進行了支持,同時這個等號左邊的 name, 也就是咱們在動態配置時,須要指定的名稱。能夠說,具備一箭雙鵰的功效。

16. 到 loadClass 方式中,開始了真正的類加載工做。

    這個方法主要是對 clazz 進行加載,並對該動態類上的不一樣類型分別作不一樣的初始化工做,將加載後的 clazz 對象放入到  cachedNames 、cachedActivates、cachedAdaptiveClass 或 cachedWrapperClasses 中,進行了緩存。方便下一次獲取時不用再次加載。

    源碼,讀者可自行查看。

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
    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.");
    }
    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());
        }
    } else if (isWrapperClass(clazz)) {
        Set<Class<?>> wrappers = cachedWrapperClasses;
        if (wrappers == null) {
            cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
            wrappers = cachedWrapperClasses;
        }
        wrappers.add(clazz);
    } else {
        clazz.getConstructor();
        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 activate = clazz.getAnnotation(Activate.class);
            if (activate != null) {
                cachedActivates.put(names[0], activate);
            }
            for (String n : names) {
                if (!cachedNames.containsKey(clazz)) {
                    cachedNames.put(clazz, n);
                }
                Class<?> c = extensionClasses.get(n);
                if (c == null) {
                    extensionClasses.put(n, clazz);
                } else if (c != clazz) {
                    throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                }
            }
        }
    }
}

 

以上內容爲 Dubbo 動態加載擴展類的機制。

================================================

因爲本文做者水平有限,不免有些內容分析錯誤。

感謝你的理解與反饋!

相關文章
相關標籤/搜索