衆所周知,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 動態加載擴展類的機制。
================================================
因爲本文做者水平有限,不免有些內容分析錯誤。
感謝你的理解與反饋!