天天看點

Soul 學習筆記之 ExtensionLoader(十五)

目錄

    • 總系列目錄位址
    • ExtentionLoader
    • 使用方式
      • LoadBalanceUtils
      • ExtensionLoader.getExtensionLoader(LoadBalance.class)
      • getJoin(algorithm)
      • 具體執行時序圖
    • 總結

總系列目錄位址

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/ 加載類
    Soul 學習筆記之 ExtensionLoader(十五)
    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 部分時序圖
  1. getJoin 部分時序圖

總結

實際上是“基于接口的程式設計+政策模式+配置檔案”組合實作的動态加載機制

接口類必須加上 @SPI, 實作類必須加上 @Join

ExtensionLoader 在多個 RPC 架構都有實作, 如: dubbo, sofaRpc

sofaRpc 有 @Extensible 和 @Extension, 對應 sofa @SPI 和 @Join