目录
-
- 总系列目录地址
- 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