xkernel微内核系统核心包

xkernel微内核系统工具

简介

xkernel是一个基于java SPI思想的类加载工具包,是构建微内核系统的基础,微内核不与扩展点的具体实现产生交互,通过ExtensionLoader将扩展点与具体实现建立关联,微内核只需要知道自己暴露的扩展点和ExtensionLoader即可,扩展千变化万,内核以不变应万变。采用本工具包可快速设计一个基于微内核+插件式的扩展开发框架,不需要改动源码就可以实现扩展,解耦,实现扩展对原来的代码几乎没有侵入性,只需要添加配置就可以实现扩展,符合开闭原则。

背景

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件,SPI的作用就是为这些被扩展的API寻找服务实现,从java spi的原理中可以了解到,java的spi机制有着如下的弊端:

  1. 只能遍历所有的实现,并全部实例化。
  2. 配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。

软件架构

软件架构说明


类结构图
微内核系统设计简图
ExtensionLoader类加载过程流程图

微内核不与扩展点的具体实现产生交互,通过ExtensionLoader将扩展点与具体实现建立关联,微内核只需要知道自己暴露的扩展点和ExtensionLoader即可,扩展千变化万,内核以不变应万变。

l 术语说明:

1, SPI:Service Provider Interface 。

2, 扩展点:被@Spi注解的 Interface 为一个扩展点。

3, 扩展:被@Spi注解的Interface 的实现称为这个扩展点的一个扩展。

l 扩展点约定:

1, 扩展点必须是Interface类型,必须被@Spi注解,满足这两点才是一个扩展点。

l 扩展定义约定:

1, 在META-INF/services/$扩展点接口的全类名

META-INF/ext/$扩展点接口的全类名 ,

META-INF/ext/internal/$扩展点接口的全类名 ,

这些路径下定义的文件名称为 $扩展点接口的全类名 , 文件中以键值对的方式配置扩展点的扩展实现。例如文件 META-INF/ext/internal/com.ximg.api.ImgHandler中定义的扩展 :

thumbnailator=com.ximg.impl.ThumbnailatorImgHandler

l 默认扩展:

1, 被@Spi("abc")注解的Interface,那么这个扩展点的缺省适应扩展就是 SPI 配置文件中 key 为 "abc" 的扩展。

l Spi注解类,该注解作用于扩展点的接口上,表明该接口是一个扩展点,属性 value 用来指定默认适配扩展点的名称。定义如下:

@Documented

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.TYPE)

public @interface Spi {

​ String value() default "";

}

l ExtensionLoader:扩展点实现加载器。

1, 扩展加载器是本方案的核心组件,它控制内部所有扩展点的初始化、加载扩展的过程。

2, 包含的静态属性:

EXTENSION_LOADERS:保存了内核开放的扩展点对应的 ExtensionLoader 实例对象。

EXTENSION_INSTANCES:保存了扩展类型 (Class) 和扩展类型的实例对象。

type : 被 @SPI 注解的 Interface , 也就是扩展点。

cachedNames : 保存不满足装饰模式(不存在只有一个参数,并且参数是扩展点类型实例对象的构造函数)的扩展的名称。

cachedClasses : 保存不满足装饰模式的扩展的 Class 实例 , 扩展的名称作为 key , Class 实例作为 value。

cachedInstances : 保存扩展的名称和实例对象 , 扩展名称为 key , 扩展实例为 value。

cachedDefaultName : 扩展点上 @SPI 注解指定的缺省适配扩展。

cachedWrapperClasses : 满足装饰模式的扩展的 Class 实例。

exceptions : 保存在加载扩展点配置文件时,加载扩展点过程中抛出的异常 , key 是当前读取的扩展点配置文件的一行 , value 是抛出的异常。

3, 类加载过程:

首先通过 ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。这其中,getExtensionLoader 方法用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则创建一个新的实例,主要有以下步骤:

a. T getExtension(String name)方法:首先检查缓存,缓存未命中则创建拓展对象

b. T createExtension(String name) 方法,包含了如下的步骤:

i. 通过 getExtensionClasses 获取所有的拓展类

ii. 通过反射创建拓展对象

c. Map<String, Class<?>> getExtensionClasses():在通过名称获取拓展类之前,首先需要根据配置文件解析出拓展项名称到拓展类的映射关系表(Map<名称, 拓展类>),之后再根据拓展项名称从映射关系表中取出相应的拓展类即可,这里也是先检查缓存,若缓存未命中,则通过 synchronized 加锁。加锁后再次检查缓存,并判空。此时如果 classes 仍为 null,则通过 loadExtensionClasses 加载拓展类。

d. Map<String, Class<?>> loadExtensionClasses():loadExtensionClasses 方法总共做了两件事情,一是对 SPI 注解进行解析,二是调用 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 方法用于读取和解析配置文件,并通过反射加载类,最后调用 loadClass 方法进行其他操作。

g. void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException:loadClass 方法用于主要用于操作缓存,如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等等。

采用本工具包可快速设计一个基于微内核+插件式的扩展开发框架,不需要改动源码就可以实现扩展,解耦,实现扩展对原来的代码几乎没有侵入性,只需要添加配置就可以实现扩展,符合开闭原则。

安装教程

引入微内核系统核心包

      <dependency>
    <groupId>com.javacoo</groupId>
    <artifactId>xKernel</artifactId>
    <version>1.0.0</version>
      </dependency>

使用说明

1,设计需要扩展的接口类,如:。

/**
 * 数据处理
 * <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,如:

/**
 * 数据处理
 * <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,实现扩展接口类。

/**
 * 抽象数据处理类
 * <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;
            }
            //执行处理
            doHandle(t);
        });
    }
    /**
     * 执行处理
     * <p>说明:</p>
     * <li></li>
     * @Author DuanYong
     * @Since 2019/8/30 23:57
     * @Version 1.0
     * @Params t
     */
    protected abstract void doHandle(T t);

    /**
     * 解析数据
     * <p>说明:</p>
     * <li></li>
     * @Author DuanYong
     * @Since 2019/8/30 23:55
     * @Version 1.0
     * @Params rawData 原始数据
     * @Return T
     */
    protected abstract T parser(String rawData);


}

4,在项目或jar包的META-INF/services/或者META-INF/ext或者META-INF/ext/internal目录下,创建一个文本文件:名称为接口的“全限定名”,内容格式为:实现名=实现类的全限定名。

文件:com.javacoo.swing.api.data.DataHandler
内容:default=com.javacoo.swing.core.data.AliPayDataHandler

5,使用ExtensionLoader.getExtensionLoader(接口类型)方法,获取对应接口类型的ExtensionLoader<接口类型> 实例对象。

 /**数据处理服务*/
    private DataHandler dataHandler;
    public MyNetworkDelegate(){
        dataHandler = ExtensionLoader.getExtensionLoader(DataHandler.class).getDefaultExtension();
    }

6,使用ExtensionLoader<接口类型> 实例对象,调用getExtension(扩展点名称)方法,获取对应扩展点名称的扩展实现。

项目信息
路漫漫其修远兮,吾将上下而求索
码云:https://gitee.com/javacoo
QQ:164863067
作者/微信:javacoo
邮箱:xihuady@126.com

源码下载地址

https://gitee.com/javacoo/xkernel

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350