xkernel微內(nèi)核系統(tǒng)工具
簡介
xkernel是一個基于java SPI思想的類加載工具包,是構(gòu)建微內(nèi)核系統(tǒng)的基礎(chǔ),微內(nèi)核不與擴展點的具體實現(xiàn)產(chǎn)生交互,通過ExtensionLoader將擴展點與具體實現(xiàn)建立關(guān)聯(lián),微內(nèi)核只需要知道自己暴露的擴展點和ExtensionLoader即可,擴展千變化萬,內(nèi)核以不變應(yīng)萬變。采用本工具包可快速設(shè)計一個基于微內(nèi)核+插件式的擴展開發(fā)框架,不需要改動源碼就可以實現(xiàn)擴展,解耦,實現(xiàn)擴展對原來的代碼幾乎沒有侵入性,只需要添加配置就可以實現(xiàn)擴展,符合開閉原則。
背景
SPI全稱Service Provider Interface,是Java提供的一套用來被第三方實現(xiàn)或者擴展的接口,它可以用來啟用框架擴展和替換組件,SPI的作用就是為這些被擴展的API尋找服務(wù)實現(xiàn),從java spi的原理中可以了解到,java的spi機制有著如下的弊端:
- 只能遍歷所有的實現(xiàn),并全部實例化。
- 配置文件中只是簡單的列出了所有的擴展實現(xiàn),而沒有給他們命名。導(dǎo)致在程序中很難去準(zhǔn)確的引用它們。
軟件架構(gòu)
軟件架構(gòu)說明



微內(nèi)核不與擴展點的具體實現(xiàn)產(chǎn)生交互,通過ExtensionLoader將擴展點與具體實現(xiàn)建立關(guān)聯(lián),微內(nèi)核只需要知道自己暴露的擴展點和ExtensionLoader即可,擴展千變化萬,內(nèi)核以不變應(yīng)萬變。
l 術(shù)語說明:
1, SPI:Service Provider Interface 。
2, 擴展點:被@Spi注解的 Interface 為一個擴展點。
3, 擴展:被@Spi注解的Interface 的實現(xiàn)稱為這個擴展點的一個擴展。
l 擴展點約定:
1, 擴展點必須是Interface類型,必須被@Spi注解,滿足這兩點才是一個擴展點。
l 擴展定義約定:
1, 在META-INF/services/$擴展點接口的全類名
META-INF/ext/$擴展點接口的全類名 ,
META-INF/ext/internal/$擴展點接口的全類名 ,
這些路徑下定義的文件名稱為 $擴展點接口的全類名 , 文件中以鍵值對的方式配置擴展點的擴展實現(xiàn)。例如文件 META-INF/ext/internal/com.ximg.api.ImgHandler中定義的擴展 :
thumbnailator=com.ximg.impl.ThumbnailatorImgHandler
l 默認擴展:
1, 被@Spi("abc")注解的Interface,那么這個擴展點的缺省適應(yīng)擴展就是 SPI 配置文件中 key 為 "abc" 的擴展。
l Spi注解類,該注解作用于擴展點的接口上,表明該接口是一個擴展點,屬性 value 用來指定默認適配擴展點的名稱。定義如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Spi {
? String value() default "";
}
l ExtensionLoader:擴展點實現(xiàn)加載器。
1, 擴展加載器是本方案的核心組件,它控制內(nèi)部所有擴展點的初始化、加載擴展的過程。
2, 包含的靜態(tài)屬性:
EXTENSION_LOADERS:保存了內(nèi)核開放的擴展點對應(yīng)的 ExtensionLoader 實例對象。
EXTENSION_INSTANCES:保存了擴展類型 (Class) 和擴展類型的實例對象。
type : 被 @SPI 注解的 Interface , 也就是擴展點。
cachedNames : 保存不滿足裝飾模式(不存在只有一個參數(shù),并且參數(shù)是擴展點類型實例對象的構(gòu)造函數(shù))的擴展的名稱。
cachedClasses : 保存不滿足裝飾模式的擴展的 Class 實例 , 擴展的名稱作為 key , Class 實例作為 value。
cachedInstances : 保存擴展的名稱和實例對象 , 擴展名稱為 key , 擴展實例為 value。
cachedDefaultName : 擴展點上 @SPI 注解指定的缺省適配擴展。
cachedWrapperClasses : 滿足裝飾模式的擴展的 Class 實例。
exceptions : 保存在加載擴展點配置文件時,加載擴展點過程中拋出的異常 , key 是當(dāng)前讀取的擴展點配置文件的一行 , value 是拋出的異常。
3, 類加載過程:
首先通過 ExtensionLoader 的 getExtensionLoader 方法獲取一個 ExtensionLoader 實例,然后再通過 ExtensionLoader 的 getExtension 方法獲取拓展類對象。這其中,getExtensionLoader 方法用于從緩存中獲取與拓展類對應(yīng)的 ExtensionLoader,若緩存未命中,則創(chuàng)建一個新的實例,主要有以下步驟:
a. T getExtension(String name)方法:首先檢查緩存,緩存未命中則創(chuàng)建拓展對象
b. T createExtension(String name) 方法,包含了如下的步驟:
i. 通過 getExtensionClasses 獲取所有的拓展類
ii. 通過反射創(chuàng)建拓展對象
c. Map<String, Class<?>> getExtensionClasses():在通過名稱獲取拓展類之前,首先需要根據(jù)配置文件解析出拓展項名稱到拓展類的映射關(guān)系表(Map<名稱, 拓展類>),之后再根據(jù)拓展項名稱從映射關(guān)系表中取出相應(yīng)的拓展類即可,這里也是先檢查緩存,若緩存未命中,則通過 synchronized 加鎖。加鎖后再次檢查緩存,并判空。此時如果 classes 仍為 null,則通過 loadExtensionClasses 加載拓展類。
d. Map<String, Class<?>> loadExtensionClasses():loadExtensionClasses 方法總共做了兩件事情,一是對 SPI 注解進行解析,二是調(diào)用 loadDirectory 方法加載指定文件夾配置文件。
e. Void loadDirectory(Map<String, Class<?>> extensionClasses, String dir): loadDirectory 方法先通過 classLoader 獲取所有資源鏈接,然后再通過 loadResource 方法加載資源。
f. Void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL): loadResource 方法用于讀取和解析配置文件,并通過反射加載類,最后調(diào)用 loadClass 方法進行其他操作。
g. void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException:loadClass 方法用于主要用于操作緩存,如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等等。
采用本工具包可快速設(shè)計一個基于微內(nèi)核+插件式的擴展開發(fā)框架,不需要改動源碼就可以實現(xiàn)擴展,解耦,實現(xiàn)擴展對原來的代碼幾乎沒有侵入性,只需要添加配置就可以實現(xiàn)擴展,符合開閉原則。
安裝教程
引入微內(nèi)核系統(tǒng)核心包
<dependency>
<groupId>com.javacoo</groupId>
<artifactId>xKernel</artifactId>
<version>1.0.0</version>
</dependency>
使用說明
1,設(shè)計需要擴展的接口類,如:。
/**
* 數(shù)據(jù)處理
* <p>說明:</p>
* <li></li>
*
* @Author DuanYong
* @Since 2019/8/30 23:41
* @Version 1.0
*/
public interface DataHandler<T> {
/**
* 處理
* <p>說明:</p>
* <li></li>
* @Author DuanYong
* @Since 2019/8/30 23:42
* @Version 1.0
* @Params data
*/
void handle(final T data);
}
2,在接口上添加注解@Spi,如:
/**
* 數(shù)據(jù)處理
* <p>說明:</p>
* <li></li>
*
* @Author DuanYong
* @Since 2019/8/30 23:41
* @Version 1.0
*/
@Spi("default")
public interface DataHandler<T> {
/**
* 處理
* <p>說明:</p>
* <li></li>
* @Author DuanYong
* @Since 2019/8/30 23:42
* @Version 1.0
* @Params data
*/
void handle(final T data);
}
3,實現(xiàn)擴展接口類。
/**
* 抽象數(shù)據(jù)處理類
* <p>說明:</p>
* <li></li>
*
* @Author DuanYong
* @Since 2019/8/30 23:47
* @Version 1.0
*/
@Slf4j
public abstract class AbstractDataHandler<T> implements DataHandler<String> {
protected ExecutorService executorService = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors(),
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),Executors.defaultThreadFactory());
@Override
public final void handle(final String data){
executorService.submit(()->{
//解析
T t = parser(data);
if(null == t){
return;
}
//執(zhí)行處理
doHandle(t);
});
}
/**
* 執(zhí)行處理
* <p>說明:</p>
* <li></li>
* @Author DuanYong
* @Since 2019/8/30 23:57
* @Version 1.0
* @Params t
*/
protected abstract void doHandle(T t);
/**
* 解析數(shù)據(jù)
* <p>說明:</p>
* <li></li>
* @Author DuanYong
* @Since 2019/8/30 23:55
* @Version 1.0
* @Params rawData 原始數(shù)據(jù)
* @Return T
*/
protected abstract T parser(String rawData);
}
4,在項目或jar包的META-INF/services/或者META-INF/ext或者META-INF/ext/internal目錄下,創(chuàng)建一個文本文件:名稱為接口的“全限定名”,內(nèi)容格式為:實現(xiàn)名=實現(xiàn)類的全限定名。
文件:com.javacoo.swing.api.data.DataHandler
內(nèi)容:default=com.javacoo.swing.core.data.AliPayDataHandler
5,使用ExtensionLoader.getExtensionLoader(接口類型)方法,獲取對應(yīng)接口類型的ExtensionLoader<接口類型> 實例對象。
/**數(shù)據(jù)處理服務(wù)*/
private DataHandler dataHandler;
public MyNetworkDelegate(){
dataHandler = ExtensionLoader.getExtensionLoader(DataHandler.class).getDefaultExtension();
}
6,使用ExtensionLoader<接口類型> 實例對象,調(diào)用getExtension(擴展點名稱)方法,獲取對應(yīng)擴展點名稱的擴展實現(xiàn)。
項目信息
路漫漫其修遠兮,吾將上下而求索
碼云:https://gitee.com/javacoo
QQ:164863067
作者/微信:javacoo
郵箱:xihuady@126.com