目錄
-
- 總系列目錄位址
- 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)
- 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