SPI 可擴(kuò)展框架:是各種 rpc 框架用于實(shí)現(xiàn)高可擴(kuò)展性的手段。
本節(jié)實(shí)現(xiàn)最基本的 SPI 機(jī)制,包含四個(gè)基本部分:
@Extension
:該注解添加在可擴(kuò)展接口的實(shí)現(xiàn)
上,并且通常會(huì)添加一個(gè)alias
用于標(biāo)識一個(gè)擴(kuò)展實(shí)現(xiàn)類的 key(在 core-spi 中僅僅適用于標(biāo)識)ExtensionClass
:一個(gè)實(shí)現(xiàn)類
會(huì)最終被其 ExtensionLoader 加載為一個(gè)ExtensionClass
,存儲(chǔ)在其
ExtensionLoader 中,并且包含了實(shí)例化ExtensionClass
存儲(chǔ)的實(shí)現(xiàn)類
的方法ExtensionLoader
:每一個(gè)可擴(kuò)展接口
都有且僅有一個(gè)ExtensionLoader
,用于從相應(yīng)接口的 SPI 配置文件中讀取配置內(nèi)容并且將每一行解析成一個(gè)ExtensionClass
(每一個(gè)ExtensionClass
對應(yīng)一個(gè)實(shí)現(xiàn)诚些,SPI 配置文件中的每一行配置一個(gè)實(shí)現(xiàn)類),之后存儲(chǔ)<alias, ExtensionClass>
配置對到Map<String, ExtensionClass<T>>
容器中ExtensionLoaderFactory
:用來獲取或者創(chuàng)建 ExtensionLoader皇型,將創(chuàng)建好的 ExtensionLoader 放置在Map<Class, ExtensionLoader>
容器中
一诬烹、Extension
/**
* 擴(kuò)展接口實(shí)現(xiàn)類的標(biāo)識
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Extension {
/**
* 在 core-spi 中不起作用,僅用作 alias 標(biāo)識
*
* @return alias
*/
String value();
}
二弃鸦、ExtensionClass
/**
* 擴(kuò)展實(shí)現(xiàn)類類 Class 包裝類
*
* @param <T>
*/
public class ExtensionClass<T> {
/**
* 真實(shí)的擴(kuò)展實(shí)現(xiàn)類類 Class
*/
private Class<? extends T> clazz;
public ExtensionClass(Class<? extends T> clazz) {
this.clazz = clazz;
}
/**
* 調(diào)用無參構(gòu)造器創(chuàng)建擴(kuò)展實(shí)現(xiàn)類實(shí)例
*
* @return 擴(kuò)展實(shí)現(xiàn)類實(shí)例
*/
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);
}
}
/**
* 調(diào)用有參構(gòu)造器創(chuàng)建擴(kuò)展實(shí)現(xiàn)類實(shí)例
*
* @return 擴(kuò)展實(shí)現(xiàn)類實(shí)例
*/
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
/**
* 擴(kuò)展加載器
*
* @param <T>
*/
public class ExtensionLoader<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(ExtensionLoader.class);
/**
* 當(dāng)前擴(kuò)展加載器處理的擴(kuò)展接口名
*/
private String interfaceName;
/**
* interfaceName 擴(kuò)展接口下的所有實(shí)現(xiàn)
*/
private Map<String, ExtensionClass<T>> alias2ExtensionClass;
public ExtensionLoader(Class<T> interfaceClass) {
this.interfaceName = interfaceClass.getName();
this.alias2ExtensionClass = new ConcurrentHashMap<>();
// 此處只指定了一個(gè) spi 文件存儲(chǔ)的路徑
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 {
// 讀取多個(gè)spi文件
Enumeration<URL> urls = classLoader != null ? classLoader.getResources(spiFile) : ClassLoader.getSystemResources(spiFile);
if (urls == null) {
return;
}
while (urls.hasMoreElements()) {
// 每一個(gè) url 是一個(gè)文件
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 文件需要嚴(yán)格按照 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());
// 必須具有擴(kuò)展注解
Extension extension = clazz.getAnnotation(Extension.class);
if (extension == null) {
LOGGER.error("{} need @Extension", className);
return;
}
// 創(chuàng)建 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 規(guī)定了 spi 文件存儲(chǔ)的唯一路徑家破,且指定了 alias=className 這樣的唯一格式 - 由于這樣的強(qiáng)規(guī)定颜说,@Extension 注解中 value 屬性(即 alias)不再有用,只是作為一個(gè)標(biāo)識汰聋,可直接去掉
- SOFARPC 除了實(shí)現(xiàn)了基本的 spi 機(jī)制之外门粪,還實(shí)現(xiàn)了如下功能,具體見:SOFARPC 源碼分析2 - SPI 擴(kuò)展機(jī)制
- spi 文件路徑的可配置化
- spi 配置文件的名字的可配置化(默認(rèn)是可擴(kuò)展接口全類名)
- spi 配置的格式的多樣性
- 高 order 的實(shí)現(xiàn)類覆蓋低 order 實(shí)現(xiàn)的功能
- 排斥掉指定的低 order 的擴(kuò)展點(diǎn)的功能
- 實(shí)現(xiàn)類是否需要編碼
- 實(shí)現(xiàn)類是否需要單例
四烹困、ExtensionLoaderFactory
/**
* 創(chuàng)建 ExtensionLoader 的工廠
*/
public class ExtensionLoaderFactory {
/**
* key: 擴(kuò)展接口 Class
* value: 擴(kuò)展接口對應(yīng)的 ExtensionLoader(單例玄妈,每一個(gè)擴(kuò)展接口有一個(gè) ExtensionLoader)
*/
private static final Map<Class, ExtensionLoader> LOADER_MAP = new ConcurrentHashMap<>();
/**
* 獲取或創(chuàng)建 clazz 擴(kuò)展接口的 ExtensionLoader
*
* @param clazz 擴(kuò)展接口
* @param <T>
* @return clazz 擴(kuò)展接口的 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.png
1髓梅、可擴(kuò)展接口
package com.core;
public interface LoadBalancer {
String selectProvider();
}
2拟蜻、擴(kuò)展接口實(shí)現(xiàn)類
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. 根據(jù) alias 獲取具體的 Extension
LoadBalancer loadBalancer = loader.getExtension("random");
// 3. 使用具體的 loadBalancer
System.out.println(loadBalancer.selectProvider());
// 4. 根據(jù) alias 獲取具體的 Extension
LoadBalancer hasArgsLoadBalancer = loader.getExtension("hasArgs", new Class[]{String.class}, new Object[]{"haha"});
// 5. 使用具體的 loadBalancer
System.out.println(hasArgsLoadBalancer.selectProvider());
}
}
六瞭郑、通用的策略工廠
在實(shí)際業(yè)務(wù)開發(fā)中,我們通常會(huì)基于策略模式做一些事情鸭你,例如 上述的LoadBalancer接口,我們會(huì)做一個(gè)LoadBalancerFactory擒权,類似代碼如下
class LoadBalancerFactory {
private Map<LoadBalancerType, LoadBalancer> registry;
static {
// 讀取 LoadBalancer 實(shí)現(xiàn)類袱巨,存儲(chǔ)到registry中,后續(xù)通過registry.get(LoadBalancerType) 進(jìn)行調(diào)用
}
}
如果是另外一個(gè)其他需要策略化的接口碳抄,我們又會(huì)提供一個(gè)其他的 XxxFactory愉老,里邊的代碼模式幾乎與 LoadBalancerFactory 相同。其實(shí) ExtensionLoaderFactory
本身就可以作為一個(gè)通用工廠剖效,而策略實(shí)現(xiàn)類的數(shù)據(jù)的讀取可以通過注解掃描直接獲取嫉入,不需要通過 spi 文件進(jìn)行,簡化業(yè)務(wù)開發(fā)璧尸。核心變化如下咒林。完整代碼見:https://github.com/zhaojigang/x-toolsets
public class ExtensionLoader<T> {
public ExtensionLoader(Class<T> interfaceClass) {
this.interfaceName = interfaceClass.getName();
this.alias2ExtensionClass = new ConcurrentHashMap<>();
// 此處只指定了一個(gè) spi 文件存儲(chǔ)的路徑
loadFromClassLoader(interfaceClass, Lists.newArrayList("strategy.factory"));
}
private void loadFromClassLoader(Class<T> interfaceClass, List<String> scanPaths) {
Reflections reflections = new Reflections(scanPaths, this.getClass().getClassLoader());
Set<Class<? extends T>> classes = reflections.getSubTypesOf(interfaceClass);
for (Class<? extends T> clazz : classes) {
// 必須具有擴(kuò)展注解
Extension extension = clazz.getAnnotation(Extension.class);
if (extension == null || StringUtils.isBlank(extension.value())) {
throw new RuntimeException(clazz.getName() + " need @Extension");
}
// 創(chuàng)建 ExtensionClass
ExtensionClass<T> extensionClass = new ExtensionClass<>(clazz);
alias2ExtensionClass.putIfAbsent(extension.value(), extensionClass);
}
}
}
<!-- 簡化指定路徑進(jìn)行反射數(shù)據(jù)掃描的包 -->
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>