1. 程式人生 > 其它 >Soul 學習筆記之 ExtensionLoader(十五)

Soul 學習筆記之 ExtensionLoader(十五)

技術標籤:java閘道器

目錄

總系列目錄地址

ExtentionLoader

SPI 全稱 Service Provider Interface,實際上是“基於介面的程式設計+策略模式+配置檔案”組合實現的動態載入機制。
使用 SPI 方式可以讓具體實現類在程式外部單獨程式設計,實現可插拔,達到解耦的目的

Soud 中的 ExtentionLoader 起源於 apache dubbo, 是 SPI 方式的擴充套件使用。

使用方式

LoadBalanceUtils

使用不同的負載均衡策略
LoadBalanceUtils.selector

public static DivideUpstream selector(final List<DivideUpstream> upstreamList, final String algorithm, final String ip) {
	LoadBalance loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getJoin(algorithm);
    return
loadBalance.select(upstreamList, ip); }

ExtensionLoader.getExtensionLoader(LoadBalance.class)

  1. Map<Class<?>, ExtensionLoader<?>> LOADERS 中讀取 key 為 LoadBalance.class 的 ExtensionLoader 實現。

  2. Map<Class<?>, ExtensionLoader<?>> LOADERS 有一個根節點 key 為 ExtensionFactory.class

  3. Holder<Map<String, Class<?>>> cachedClasses 中儲存所有 META-INF/soul/ 中的 class 資訊。

  4. 載入外部類的具體程式碼 返回的類儲存到 cachedClasses 中。

    private Map<String, Class<?>> loadExtensionClass() {
    	// SPI 註解加在介面類中, 如: interface LoadBalance 
        SPI annotation = clazz.getAnnotation(SPI.class);
        ...
        Map<String, Class<?>> classes = new HashMap<>(16);
        loadDirectory(classes);
        return classes;
    }
    
  5. 通過 META-INF/soul/ 載入類
    meta-inf

    private void loadDirectory(final Map<String, Class<?>> classes) {
    	// clazz.getName = org.dromara.soul.plugin.divide.balance.LoadBalance
        String fileName = SOUL_DIRECTORY + clazz.getName();
        try {
            ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
            // 載入檔案裡所有的元素
            // random=org.dromara.soul.plugin.divide.balance.spi.RandomLoadBalance
    		// roundRobin=org.dromara.soul.plugin.divide.balance.spi.RoundRobinLoadBalance
    		// hash=org.dromara.soul.plugin.divide.balance.spi.HashLoadBalance
            Enumeration<URL> urls = classLoader != null ? classLoader.getResources(fileName)
                    : ClassLoader.getSystemResources(fileName);
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    loadResources(classes, url);
                }
            }
        }
    }
    
  6. 通過 METE-INFO 找到的實現類需要加上 @Join 才會被載入,否則會報錯。

    private void loadClass(final Map<String, Class<?>> classes,final String name, final String classPath) {
    	....
    	Join annotation = subClass.getAnnotation(Join.class);
        if (annotation == null) {
    	        throw new IllegalStateException("load extension resources error," + subClass + " with Join annotation");
        }
    }
    

getJoin(algorithm)

public T getJoin(final String name) {
   	 ...
   	 // cachedInstances 在初始化時已經
     Holder<Object> objectHolder = cachedInstances.get(name);
     if (objectHolder == null) {
         cachedInstances.putIfAbsent(name, new Holder<>());
         objectHolder = cachedInstances.get(name);
     }
     Object value = objectHolder.getValue();
     if (value == null) {
         synchronized (cachedInstances) {
             value = objectHolder.getValue();
             if (value == null) {
                 value = createExtension(name);
                 objectHolder.setValue(value);
             }
         }
     }
     return (T) value;
 }
 private T createExtension(final String name) {
    ... 
   	// 已經使用過 getJoin 的會有一個快取
    Object o = joinInstances.get(aClass);
    if (o == null) {
        try {
            joinInstances.putIfAbsent(aClass, aClass.newInstance());
            o = joinInstances.get(aClass);
        }...
    }
    return (T) o;
}

具體執行時序圖

  1. getExtensionLoader 部分時序圖
Class LoadBalanceUtils ExtensionLoader getExtensionClasses loadExtensionClass loadDirectory loadResources loadClass selector getExtensionLoader 從 Map:LOADERS 中獲取ExtensionLoader 不存在則建立 從 cachedClasses 獲取 Map 找到註解 SPI 的介面類,並新建 Map 獲取 META-INF/soul 下的檔案 讀取 properties 的內容 找到註解 Join 的實現類, 放到新建立的 Map void void void Map Map ExtensionLoader doSomeThing Class LoadBalanceUtils ExtensionLoader getExtensionClasses loadExtensionClass loadDirectory loadResources loadClass ExtentionLoader getExtensionLoader
  1. getJoin 部分時序圖
Class LoadBalanceUtils ExtensionLoader createExtension getExtensionClasses selector getJoin 從 cachedInstances 中獲取ExtensionLoader 不存在,則新建 在 cachedClasses 查詢是否存在 還沒被載入過的類,會執行 loadExtensionClass Map.get 在 joinInstances 中查詢,不存在則 newInstance T T doSomeThing Class LoadBalanceUtils ExtensionLoader createExtension getExtensionClasses ExtentionLoader getJoin

總結

實際上是“基於介面的程式設計+策略模式+配置檔案”組合實現的動態載入機制
介面類必須加上 @SPI, 實現類必須加上 @Join
ExtensionLoader 在多個 RPC 框架都有實現, 如: dubbo, sofaRpc
sofaRpc 有 @Extensible 和 @Extension, 對應 sofa @SPI 和 @Join