Soul 學習筆記之 ExtensionLoader(十五)
阿新 • • 發佈:2021-02-19
目錄
總系列目錄地址
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)
-
Map<Class<?>, ExtensionLoader<?>> LOADERS 中讀取 key 為 LoadBalance.class 的 ExtensionLoader 實現。
-
Map<Class<?>, ExtensionLoader<?>> LOADERS 有一個根節點 key 為 ExtensionFactory.class
-
Holder<Map<String, Class<?>>> cachedClasses 中儲存所有 META-INF/soul/ 中的 class 資訊。
-
載入外部類的具體程式碼 返回的類儲存到 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; }
-
通過 META-INF/soul/ 載入類
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); } } } }
-
通過 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;
}
具體執行時序圖
- getExtensionLoader 部分時序圖
- getJoin 部分時序圖
總結
實際上是“基於介面的程式設計+策略模式+配置檔案”組合實現的動態載入機制
介面類必須加上 @SPI, 實現類必須加上 @Join
ExtensionLoader 在多個 RPC 框架都有實現, 如: dubbo, sofaRpc
sofaRpc 有 @Extensible 和 @Extension, 對應 sofa @SPI 和 @Join