詳解Java實(shí)現(xiàn)簡單SPI流程
參考dubbo和shenyu網(wǎng)關(guān)實(shí)現(xiàn)自定義的SPI
SPI標(biāo)注注解
標(biāo)注提供SPI能力接口的注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SPI {
/**
* value
* @return value
*/
String value() default "";
}
標(biāo)準(zhǔn)SPI實(shí)現(xiàn)的注解@Join
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Join {
}
SPI核心實(shí)現(xiàn)
SPI的一些Class和擴(kuò)展對(duì)象緩存
SPI實(shí)現(xiàn)是一個(gè)懶加載的過程,只有當(dāng)通過get方法獲取擴(kuò)展的實(shí)例時(shí)才會(huì)加載擴(kuò)展,并創(chuàng)建擴(kuò)展實(shí)例,這里我們定義一個(gè)集合用于緩存擴(kuò)展類,擴(kuò)展對(duì)象等,代碼如下:
@Slf4j
@SuppressWarnings("all")
public class ExtensionLoader<T> {
/**
* SPI配置擴(kuò)展的文件位置
* 擴(kuò)展文件命名格式為 SPI接口的全路徑名,如:com.redick.spi.test.TestSPI
*/
private static final String DEFAULT_DIRECTORY = "META-INF/log-helper/";
/**
* 擴(kuò)展接口 {@link Class}
*/
private final Class<T> tClass;
/**
* 擴(kuò)展接口 和 擴(kuò)展加載器 {@link ExtensionLoader} 的緩存
*/
private static final Map<Class<?>, ExtensionLoader<?>> MAP = new ConcurrentHashMap<>();
/**
* 保存 "擴(kuò)展" 實(shí)現(xiàn)的 {@link Class}
*/
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
/**
* "擴(kuò)展名" 對(duì)應(yīng)的 保存擴(kuò)展對(duì)象的Holder的緩存
*/
private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
/**
* 擴(kuò)展class 和 擴(kuò)展點(diǎn)的實(shí)現(xiàn)對(duì)象的緩存
*/
private final Map<Class<?>, Object> joinInstances = new ConcurrentHashMap<>();
/**
* 擴(kuò)展點(diǎn)默認(rèn)的 "名稱" 緩存
*/
private String cacheDefaultName;
// 省略代碼后面介紹
}
獲取擴(kuò)展器ExtensionLoader
public static<T> ExtensionLoader<T> getExtensionLoader(final Class<T> tClass) {
// 參數(shù)非空校驗(yàn)
if (null == tClass) {
throw new NullPointerException("tClass is null !");
}
// 參數(shù)應(yīng)該是接口
if (!tClass.isInterface()) {
throw new IllegalArgumentException("tClass :" + tClass + " is not interface !");
}
// 參數(shù)要包含@SPI注解
if (!tClass.isAnnotationPresent(SPI.class)) {
throw new IllegalArgumentException("tClass " + tClass + "without @" + SPI.class + " Annotation !");
}
// 從緩存中獲取擴(kuò)展加載器,如果存在直接返回,如果不存在就創(chuàng)建一個(gè)擴(kuò)展加載器并放到緩存中
ExtensionLoader<T> extensionLoader = (ExtensionLoader<T>) MAP.get(tClass);
if (null != extensionLoader) {
return extensionLoader;
}
MAP.putIfAbsent(tClass, new ExtensionLoader<>(tClass));
return (ExtensionLoader<T>) MAP.get(tClass);
}
擴(kuò)展加載器構(gòu)造方法
public ExtensionLoader(final Class<T> tClass) {
this.tClass = tClass;
}
獲取SPI擴(kuò)展對(duì)象
獲取SPI擴(kuò)展對(duì)象是懶加載過程,第一次去獲取的時(shí)候是沒有的,會(huì)觸發(fā)從問家中加載資源,通過反射創(chuàng)建對(duì)象,并緩存起來。
public T getJoin(String cacheDefaultName) {
// 擴(kuò)展名 文件中的key
if (StringUtils.isBlank(cacheDefaultName)) {
throw new IllegalArgumentException("join name is null");
}
// 擴(kuò)展對(duì)象存儲(chǔ)緩存
Holder<Object> objectHolder = cachedInstances.get(cacheDefaultName);
// 如果擴(kuò)展對(duì)象的存儲(chǔ)是空的,創(chuàng)建一個(gè)擴(kuò)展對(duì)象存儲(chǔ)并緩存
if (null == objectHolder) {
cachedInstances.putIfAbsent(cacheDefaultName, new Holder<>());
objectHolder = cachedInstances.get(cacheDefaultName);
}
// 從擴(kuò)展對(duì)象的存儲(chǔ)中獲取擴(kuò)展對(duì)象
Object value = objectHolder.getT();
// 如果對(duì)象是空的,就觸發(fā)創(chuàng)建擴(kuò)展,否則直接返回?cái)U(kuò)展對(duì)象
if (null == value) {
synchronized (cacheDefaultName) {
value = objectHolder.getT();
if (null == value) {
// 創(chuàng)建擴(kuò)展對(duì)象
value = createExtension(cacheDefaultName);
objectHolder.setT(value);
}
}
}
return (T) value;
}
創(chuàng)建擴(kuò)展對(duì)象
反射方式創(chuàng)建擴(kuò)展對(duì)象的實(shí)例
private Object createExtension(String cacheDefaultName) {
// 根據(jù)擴(kuò)展名字獲取擴(kuò)展的Class,從Holder中獲取 key-value緩存,然后根據(jù)名字從Map中獲取擴(kuò)展實(shí)現(xiàn)Class
Class<?> aClass = getExtensionClasses().get(cacheDefaultName);
if (null == aClass) {
throw new IllegalArgumentException("extension class is null");
}
Object o = joinInstances.get(aClass);
if (null == o) {
try {
// 創(chuàng)建擴(kuò)展對(duì)象并放到緩存中
joinInstances.putIfAbsent(aClass, aClass.newInstance());
o = joinInstances.get(aClass);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return o;
}
從Holder中獲取獲取擴(kuò)展實(shí)現(xiàn)的Class集合
public Map<String, Class<?>> getExtensionClasses() {
// 擴(kuò)區(qū)SPI擴(kuò)展實(shí)現(xiàn)的緩存,對(duì)應(yīng)的就是擴(kuò)展文件中的 key - value
Map<String, Class<?>> classes = cachedClasses.getT();
if (null == classes) {
synchronized (cachedClasses) {
classes = cachedClasses.getT();
if (null == classes) {
// 加載擴(kuò)展
classes = loadExtensionClass();
// 緩存擴(kuò)展實(shí)現(xiàn)集合
cachedClasses.setT(classes);
}
}
}
return classes;
}
加載擴(kuò)展實(shí)現(xiàn)Class
加載擴(kuò)展實(shí)現(xiàn)Class,就是從文件中獲取擴(kuò)展實(shí)現(xiàn)的Class,然后緩存起來
public Map<String, Class<?>> loadExtensionClass() {
// 擴(kuò)展接口tClass,必須包含SPI注解
SPI annotation = tClass.getAnnotation(SPI.class);
if (null != annotation) {
String v = annotation.value();
if (StringUtils.isNotBlank(v)) {
// 如果有默認(rèn)的擴(kuò)展實(shí)現(xiàn)名,用默認(rèn)的
cacheDefaultName = v;
}
}
Map<String, Class<?>> classes = new HashMap<>(16);
// 從文件加載
loadDirectory(classes);
return classes;
}
private void loadDirectory(final Map<String, Class<?>> classes) {
// 文件名
String fileName = DEFAULT_DIRECTORY + tClass.getName();
try {
ClassLoader classLoader = ExtensionLoader.class.getClassLoader();
// 讀取配置文件
Enumeration<URL> urls = classLoader != null ? classLoader.getResources(fileName)
: ClassLoader.getSystemResources(fileName);
if (urls != null) {
// 獲取所有的配置文件
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 加載資源
loadResources(classes, url);
}
}
} catch (IOException e) {
log.error("load directory error {}", fileName, e);
}
}
private void loadResources(Map<String, Class<?>> classes, URL url) {
// 讀取文件到Properties,遍歷Properties,得到配置文件key(名字)和value(擴(kuò)展實(shí)現(xiàn)的Class)
try (InputStream inputStream = url.openStream()) {
Properties properties = new Properties();
properties.load(inputStream);
properties.forEach((k, v) -> {
// 擴(kuò)展實(shí)現(xiàn)的名字
String name = (String) k;
// 擴(kuò)展實(shí)現(xiàn)的Class的全路徑
String classPath = (String) v;
if (StringUtils.isNotBlank(name) && StringUtils.isNotBlank(classPath)) {
try {
// 加載擴(kuò)展實(shí)現(xiàn)Class,就是想其緩存起來,緩存到集合中
loadClass(classes, name, classPath);
} catch (ClassNotFoundException e) {
log.error("load class not found", e);
}
}
});
} catch (IOException e) {
log.error("load resouces error", e);
}
}
private void loadClass(Map<String, Class<?>> classes, String name, String classPath) throws ClassNotFoundException {
// 反射創(chuàng)建擴(kuò)展實(shí)現(xiàn)的Class
Class<?> subClass = Class.forName(classPath);
// 擴(kuò)展實(shí)現(xiàn)的Class要是擴(kuò)展接口的實(shí)現(xiàn)類
if (!tClass.isAssignableFrom(subClass)) {
throw new IllegalArgumentException("load extension class error " + subClass + " not sub type of " + tClass);
}
// 擴(kuò)展實(shí)現(xiàn)要有Join注解
Join annotation = subClass.getAnnotation(Join.class);
if (null == annotation) {
throw new IllegalArgumentException("load extension class error " + subClass + " without @Join" +
"Annotation");
}
// 緩存擴(kuò)展實(shí)現(xiàn)Class
Class<?> oldClass = classes.get(name);
if (oldClass == null) {
classes.put(name, subClass);
} else if (oldClass != subClass) {
log.error("load extension class error, Duplicate class oldClass is " + oldClass + "subClass is" + subClass);
}
}存儲(chǔ)Holder
public static class Holder<T> {
private volatile T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
測(cè)試SPI
定義SPI接口
@SPI
public interface TestSPI {
void test();
}
擴(kuò)展實(shí)現(xiàn)1和2
@Join
public class TestSPI1Impl implements TestSPI {
@Override
public void test() {
System.out.println("test1");
}
}
@Join
public class TestSPI2Impl implements TestSPI {
@Override
public void test() {
System.out.println("test2");
}
}
在resources文件夾下創(chuàng)建META-INF/log-helper文件夾,并創(chuàng)建擴(kuò)展文件
文件名稱(接口全路徑名):com.redick.spi.test.TestSPI
文件內(nèi)容
testSPI1=com.redick.spi.test.TestSPI1Impl
testSPI2=com.redick.spi.test.TestSPI2Impl
動(dòng)態(tài)使用測(cè)試
public class SpiExtensionFactoryTest {
@Test
public void getExtensionTest() {
TestSPI testSPI = ExtensionLoader.getExtensionLoader(TestSPI.class).getJoin("testSPI1");
testSPI.test();
}
}
測(cè)試結(jié)果:
test1
public class SpiExtensionFactoryTest {
@Test
public void getExtensionTest() {
TestSPI testSPI = ExtensionLoader.getExtensionLoader(TestSPI.class).getJoin("testSPI2");
testSPI.test();
}
}
測(cè)試結(jié)果:
test2
總結(jié)
實(shí)現(xiàn)一個(gè)自定義的SPI機(jī)制其核心的邏輯就是擴(kuò)展的加載,本篇是參考Dubbo等開源項(xiàng)目簡單實(shí)現(xiàn)了一個(gè)SPI機(jī)制的核心代碼,核心邏輯就是從SPI擴(kuò)展的配置文件中加載擴(kuò)展實(shí)現(xiàn)的流程,通常情況下,SPI的應(yīng)用場(chǎng)景出現(xiàn)在高度可擴(kuò)展組件,并且在使用過程中有需求能夠靈活切換不同的實(shí)現(xiàn)的時(shí)候。比如程序使用限流組件,使用“令牌桶算法”和“漏桶算法”分別實(shí)現(xiàn)了限流邏輯,在業(yè)務(wù)使用限流算法的過程中,就可以通過SPI機(jī)制在程序啟動(dòng)過程中將兩種算法實(shí)現(xiàn)的組件加載好,然后通過參數(shù)指定具體使用的限流算法。此外,SPI機(jī)制能夠?qū)U(kuò)展開放,常用于開源軟件,用戶可以實(shí)現(xiàn)自己的擴(kuò)展。
到此這篇關(guān)于詳解Java實(shí)現(xiàn)簡單SPI流程的文章就介紹到這了,更多相關(guān)Java實(shí)現(xiàn)SPI內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot中的ImportSelector類動(dòng)態(tài)加載bean詳解
這篇文章主要介紹了SpringBoot中的ImportSelector類動(dòng)態(tài)加載bean詳解,ImportSelector接口是spring中導(dǎo)入外部配置的核心接口,根據(jù)給定的條件(通常是一個(gè)或多個(gè)注釋屬性)判定要導(dǎo)入那個(gè)配置類,在spring自動(dòng)化配置和@EnableXXX中都有它的存在,需要的朋友可以參考下2024-01-01
maven引入kabeja依賴的實(shí)現(xiàn)步驟
本文主要介紹了maven引入kabeja依賴的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-09-09
Springboot @WebFilter無法注入其他Bean的示例問題
這篇文章主要介紹了Springboot @WebFilter無法注入其他Bean的示例問題,本文通過示例代碼給大家分享解決方法,需要的朋友可以參考下2021-09-09
Mybatis中傳遞多個(gè)參數(shù)的4種方法總結(jié)
這篇文章主要給大家介紹了關(guān)于Mybatis中傳遞多個(gè)參數(shù)的4種方法,并且介紹了關(guān)于使用Mapper接口時(shí)參數(shù)傳遞方式,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2018-04-04
使用springmvc的controller層獲取到請(qǐng)求的數(shù)據(jù)方式
這篇文章主要介紹了使用springmvc的controller層獲取到請(qǐng)求的數(shù)據(jù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
詳解SpringBoot集成消息隊(duì)列的案例應(yīng)用
Message?Queue又名消息隊(duì)列,是一種異步通訊的中間件??梢岳斫鉃猷]局,發(fā)送者將消息投遞到郵局,然后郵局幫我們發(fā)送給具體的接收者,具體發(fā)送過程和時(shí)間與我們無關(guān)。?消息隊(duì)列是分布式系統(tǒng)中重要的組件,消息隊(duì)列主要解決了應(yīng)用耦合、異步處理、流量削鋒等問題2022-04-04
Scheduled如何會(huì)在上次任務(wù)執(zhí)行完才會(huì)執(zhí)行下次任務(wù)
這篇文章主要介紹了Scheduled如何會(huì)在上次任務(wù)執(zhí)行完才會(huì)執(zhí)行下次任務(wù)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
Spring注解實(shí)現(xiàn)自動(dòng)裝配過程解析
這篇文章主要介紹了Spring注解實(shí)現(xiàn)自動(dòng)裝配過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03

