SPI 可扩展框架:是各种 rpc 框架用于实现高可扩展性的手段。
本节实现最基本的 SPI 机制,包含四个基本部分:
@Extension
:该注解添加在可扩展接口的实现
上,并且通常会添加一个alias
用于标识一个扩展实现类的 key(在 core-spi 中仅仅适用于标识)ExtensionClass
:一个实现类
会最终被其 ExtensionLoader 加载为一个ExtensionClass
,存储在其
ExtensionLoader 中,并且包含了实例化ExtensionClass
存储的实现类
的方法ExtensionLoader
:每一个可扩展接口
都有且仅有一个ExtensionLoader
,用于从相应接口的 SPI 配置文件中读取配置内容并且将每一行解析成一个ExtensionClass
(每一个ExtensionClass
对应一个实现,SPI 配置文件中的每一行配置一个实现类),之后存储<alias, ExtensionClass>
配置对到Map<String, ExtensionClass<T>>
容器中ExtensionLoaderFactory
:用来获取或者创建 ExtensionLoader,将创建好的 ExtensionLoader 放置在Map<Class, ExtensionLoader>
容器中
一、Extension
/**
* 扩展接口实现类的标识
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Extension {
/**
* 在 core-spi 中不起作用,仅用作 alias 标识
*
* @return alias
*/
String value();
}
二、ExtensionClass
/**
* 扩展实现类类 Class 包装类
*
* @param <T>
*/
public class ExtensionClass<T> {
/**
* 真实的扩展实现类类 Class
*/
private Class<? extends T> clazz;
public ExtensionClass(Class<? extends T> clazz) {
this.clazz = clazz;
}
/**
* 调用无参构造器创建扩展实现类实例
*
* @return 扩展实现类实例
*/
public T getExtInstance() {
if (clazz == null) {
throw new RuntimeException("Class of ExtensionClass is null");
}
try {
return clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException("create " + clazz.getCanonicalName() + " instance error", e);
}
}
/**
* 调用有参构造器创建扩展实现类实例
*
* @return 扩展实现类实例
*/
public T getExtInstance(Class[] argTypes, Object[] args) {
if (clazz == null) {
throw new RuntimeException("Class of ExtensionClass is null");
}
try {
Constructor<? extends T> constructor = clazz.getDeclaredConstructor(argTypes);
return constructor.newInstance(args);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException("create " + clazz.getCanonicalName() + " instance error", e);
}
}
}
三、ExtensionLoader
/**
* 扩展加载器
*
* @param <T>
*/
public class ExtensionLoader<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(ExtensionLoader.class);
/**
* 当前扩展加载器处理的扩展接口名
*/
private String interfaceName;
/**
* interfaceName 扩展接口下的所有实现
*/
private Map<String, ExtensionClass<T>> alias2ExtensionClass;
public ExtensionLoader(Class<T> interfaceClass) {
this.interfaceName = interfaceClass.getName();
this.alias2ExtensionClass = new ConcurrentHashMap<>();
// 此处只指定了一个 spi 文件存储的路径
loadFromFile("META-INF/services/corespi/");
}
private void loadFromFile(String spiConfigPath) {
String spiFile = spiConfigPath + interfaceName;
try {
ClassLoader classLoader = this.getClass().getClassLoader();
loadFromClassLoader(classLoader, spiFile);
} catch (Exception e) {
LOGGER.error("load file {} error, ", spiFile, e);
}
}
private void loadFromClassLoader(ClassLoader classLoader, String spiFile) throws IOException {
// 读取多个spi文件
Enumeration<URL> urls = classLoader != null ? classLoader.getResources(spiFile) : ClassLoader.getSystemResources(spiFile);
if (urls == null) {
return;
}
while (urls.hasMoreElements()) {
// 每一个 url 是一个文件
URL url = urls.nextElement();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"))) {
String line;
while ((line = reader.readLine()) != null) {
// 读取文件中的每一行
readLine(line);
}
} catch (Exception e) { // 文件需要整体失败,不能单行失败
LOGGER.error("load {} fail,", spiFile, e);
}
}
}
private void readLine(String line) throws ClassNotFoundException {
// spi 文件需要严格按照 alias=className 格式编写
String[] aliasAndClassName = line.split("=");
// 任何不是 alias=className 格式的行都直接过滤掉
if (aliasAndClassName == null || aliasAndClassName.length != 2) {
return;
}
String alias = aliasAndClassName[0].trim();
String className = aliasAndClassName[1].trim();
Class<?> clazz = Class.forName(className, false, this.getClass().getClassLoader());
// 必须具有扩展注解
Extension extension = clazz.getAnnotation(Extension.class);
if (extension == null) {
LOGGER.error("{} need @Extension", className);
return;
}
// 创建 ExtensionClass
ExtensionClass<T> extensionClass = new ExtensionClass<>((Class<? extends T>) clazz);
alias2ExtensionClass.putIfAbsent(alias, extensionClass);
}
public T getExtension(String alias) {
ExtensionClass<T> extensionClass = alias2ExtensionClass.get(alias);
if (extensionClass == null) {
throw new RuntimeException("Not found extension of " + interfaceName + " named: \"" + alias + "\"!");
}
return extensionClass.getExtInstance();
}
public T getExtension(String alias, Class[] argTypes, Object[] args) {
ExtensionClass<T> extensionClass = alias2ExtensionClass.get(alias);
if (extensionClass == null) {
throw new RuntimeException("Not found extension of " + interfaceName + " named: \"" + alias + "\"!");
}
return extensionClass.getExtInstance(argTypes, args);
}
}
- core-spi 规定了 spi 文件存储的唯一路径,且指定了 alias=className 这样的唯一格式 - 由于这样的强规定,@Extension 注解中 value 属性(即 alias)不再有用,只是作为一个标识,可直接去掉
- SOFARPC 除了实现了基本的 spi 机制之外,还实现了如下功能,具体见:SOFARPC 源码分析2 - SPI 扩展机制
- spi 文件路径的可配置化
- spi 配置文件的名字的可配置化(默认是可扩展接口全类名)
- spi 配置的格式的多样性
- 高 order 的实现类覆盖低 order 实现的功能
- 排斥掉指定的低 order 的扩展点的功能
- 实现类是否需要编码
- 实现类是否需要单例
四、ExtensionLoaderFactory
/**
* 创建 ExtensionLoader 的工厂
*/
public class ExtensionLoaderFactory {
/**
* key: 扩展接口 Class
* value: 扩展接口对应的 ExtensionLoader(单例,每一个扩展接口有一个 ExtensionLoader)
*/
private static final Map<Class, ExtensionLoader> LOADER_MAP = new ConcurrentHashMap<>();
/**
* 获取或创建 clazz 扩展接口的 ExtensionLoader
*
* @param clazz 扩展接口
* @param <T>
* @return clazz 扩展接口的 ExtensionLoader
*/
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> clazz) {
ExtensionLoader<T> loader = LOADER_MAP.get(clazz);
if (loader == null) {
synchronized (ExtensionLoaderFactory.class) {
if (loader == null) {
loader = new ExtensionLoader<>(clazz);
LOADER_MAP.put(clazz, loader);
}
}
}
return loader;
}
}
五、测试
image.png1、可扩展接口
package com.core;
public interface LoadBalancer {
String selectProvider();
}
2、扩展接口实现类
package com.core;
@Extension("random")
public class RandomLoadBalancer implements LoadBalancer {
@Override
public String selectProvider() {
return "random: 10.211.55.10:8080";
}
}
@Extension("hasArgs")
public class HasArgsLoadBalancer implements LoadBalancer {
private String tag;
public HasArgsLoadBalancer(String tag){
this.tag = tag;
}
@Override
public String selectProvider() {
return "hasArgs: 10.211.55.11:8080 - " + tag;
}
}
3、spi 配置文件
文件位置:META-INF/services/corespi/com.core.LoadBalancer
random = com.core.RandomLoadBalancer
hasArgs = com.core.HasArgsLoadBalancer
4、测试 SPI
public class TestSPI {
@Test
public void testMainFunc() {
// 1. 获取 LoadBalancer 的 ExtensionLoader
ExtensionLoader<LoadBalancer> loader = ExtensionLoaderFactory.getExtensionLoader(LoadBalancer.class);
// 2. 根据 alias 获取具体的 Extension
LoadBalancer loadBalancer = loader.getExtension("random");
// 3. 使用具体的 loadBalancer
System.out.println(loadBalancer.selectProvider());
// 4. 根据 alias 获取具体的 Extension
LoadBalancer hasArgsLoadBalancer = loader.getExtension("hasArgs", new Class[]{String.class}, new Object[]{"haha"});
// 5. 使用具体的 loadBalancer
System.out.println(hasArgsLoadBalancer.selectProvider());
}
}
网友评论