天天看点

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