前言
我们平时使用Spring时,由Spring来管理类,Seata没有依赖Spring,因此它自己做了一套SPI机制,用于类的管理。
由于Seata里面一个接口有多个实现类,如配置中心就支持consul、etcd3、nacos等。这些实现类都继承了同一个接口,在运行时获取某一个实现类,就需要使用到SPI扩展点了。
使用方式
EnhancedServiceLoader
类中有load
方法和loadFile
方法,load
方法是包装loadFile
方法实现的,如:
public static <S> S load(Class<S> service, ClassLoader loader) throws EnhancedServiceNotFoundException {
return loadFile(service, null, loader);
}
private static <S> S loadFile(Class<S> service, String activateName, ClassLoader loader) {
//不同参数的load方法,都是包装方法,最终调用的都是loadFile方法
return loadFile(service, activateName, loader, null, null);
}
在每个接口的实现类上,都会有一个@LoadLevel
注解,以FrenchHello
实现类为例:
//定义一个hello接口
public interface Hello {
String say();
}
//实现类使用LoadLevel注解,有两个参数,name标识实现类的名称,order表示实现类的顺序
@LoadLevel(name = "FrenchHello", order = 2)
public class FrenchHello implements Hello {
@Override
public String say() {
return "Bonjour";
}
}
我们可以这样使用:
Hello load = EnhancedServiceLoader.load(Hello.class, Hello.class.getClassLoader());
load.say()
源码解析
整个源码的总体流程可以总结为:
1.第一次初始化时,解析默认目录下的SPI文件,并加载Class、根据注解中的order排序,这些Class是全量的,会被缓存。
如果指定了activateName
,则加载activateName
目录下的SPI文件中配置的类。这些Class不是全量的,也不会被缓存。
2.如果有多个符合的扩展点,并且没有指定要取哪一个,则默认取order最大的那个。
3.初始化对应的扩展点Class。
1基础属性:
//SPI的默认加载路径
private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String SEATA_DIRECTORY = "META-INF/seata/";
//用于缓存SPI类的ConcurrentHashMap
@SuppressWarnings("rawtypes")
private static Map<Class, List<Class>> providers = new ConcurrentHashMap<Class, List<Class>>();
2指定加载某个扩展点:
load方法们,参数不同,但都是调用了loadFile方法。
public static <S> S load(Class<S> service, ClassLoader loader) throws EnhancedServiceNotFoundException {
//activateName参数默认为null,用户指定ClassLoader
return loadFile(service, null, loader);
}
public static <S> S load(Class<S> service) throws EnhancedServiceNotFoundException {
//用户不需要指定ClassLoader,直接通过EnhancedServiceLoader.class.getClassLoader()获取
return loadFile(service, null, findClassLoader());
}
public static <S> S lo