详解Java实现简单SPI流程

目录
  • SPI标注注解
  • SPI核心实现
    • SPI的一些Class和扩展对象缓存
    • 获取扩展器ExtensionLoader
    • 扩展加载器构造方法
    • 获取SPI扩展对象
    • 创建扩展对象
    • 从Holder中获取获取扩展实现的Class集合
    • 加载扩展实现Class
    • 存储Holder
    • 测试SPI
  • 总结

参考dubboshenyu网关实现自定义的SPI

SPI标注注解

标注提供SPI能力接口的注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SPI {
    /**
     * value
     * @return value
     */
    String value() default "";
}

标准SPI实现的注解@Join

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Join {
}

SPI核心实现

SPI的一些Class和扩展对象缓存

SPI实现是一个懒加载的过程,只有当通过get方法获取扩展的实例时才会加载扩展,并创建扩展实例,这里我们定义一个集合用于缓存扩展类,扩展对象等,代码如下:

@Slf4j
@SuppressWarnings("all")
public class ExtensionLoader<T> {
    /**
     * SPI配置扩展的文件位置
     * 扩展文件命名格式为 SPI接口的全路径名,如:com.redick.spi.test.TestSPI
     */
    private static final String DEFAULT_DIRECTORY = "META-INF/log-helper/";
    /**
     * 扩展接口 {@link Class}
     */
    private final Class<T> tClass;
    /**
     * 扩展接口 和 扩展加载器 {@link ExtensionLoader} 的缓存
     */
    private static final Map<Class<?>, ExtensionLoader<?>> MAP = new ConcurrentHashMap<>();
    /**
     * 保存 "扩展" 实现的 {@link Class}
     */
    private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();
    /**
     * "扩展名" 对应的 保存扩展对象的Holder的缓存
     */
    private final Map<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();
    /**
     * 扩展class 和 扩展点的实现对象的缓存
     */
    private final Map<Class<?>, Object> joinInstances = new ConcurrentHashMap<>();
    /**
     * 扩展点默认的 "名称" 缓存
     */
    private String cacheDefaultName;
    // 省略代码后面介绍
}

获取扩展器ExtensionLoader

    public static<T> ExtensionLoader<T> getExtensionLoader(final Class<T> tClass) {
        // 参数非空校验
        if (null == tClass) {
            throw new NullPointerException("tClass is null !");
        }
        // 参数应该是接口
        if (!tClass.isInterface()) {
            throw new IllegalArgumentException("tClass :" + tClass + " is not interface !");
        }
        // 参数要包含@SPI注解
        if (!tClass.isAnnotationPresent(SPI.class)) {
            throw new IllegalArgumentException("tClass " + tClass + "without @" + SPI.class + " Annotation !");
        }
        // 从缓存中获取扩展加载器,如果存在直接返回,如果不存在就创建一个扩展加载器并放到缓存中
        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);
    }

扩展加载器构造方法

    public ExtensionLoader(final Class<T> tClass) {
        this.tClass = tClass;
    }

获取SPI扩展对象

获取SPI扩展对象是懒加载过程,第一次去获取的时候是没有的,会触发从问家中加载资源,通过反射创建对象,并缓存起来。

    public T getJoin(String cacheDefaultName) {
        // 扩展名 文件中的key
        if (StringUtils.isBlank(cacheDefaultName)) {
            throw new IllegalArgumentException("join name is null");
        }
        // 扩展对象存储缓存
        Holder<Object> objectHolder = cachedInstances.get(cacheDefaultName);
        // 如果扩展对象的存储是空的,创建一个扩展对象存储并缓存
        if (null == objectHolder) {
            cachedInstances.putIfAbsent(cacheDefaultName, new Holder<>());
            objectHolder = cachedInstances.get(cacheDefaultName);
        }
        // 从扩展对象的存储中获取扩展对象
        Object value = objectHolder.getT();
        // 如果对象是空的,就触发创建扩展,否则直接返回扩展对象
        if (null == value) {
            synchronized (cacheDefaultName) {
                value = objectHolder.getT();
                if (null == value) {
                    // 创建扩展对象
                    value = createExtension(cacheDefaultName);
                    objectHolder.setT(value);
                }
            }
        }
        return (T) value;
    }

创建扩展对象

反射方式创建扩展对象的实例

    private Object createExtension(String cacheDefaultName) {
        // 根据扩展名字获取扩展的Class,从Holder中获取 key-value缓存,然后根据名字从Map中获取扩展实现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 {
                // 创建扩展对象并放到缓存中
                joinInstances.putIfAbsent(aClass, aClass.newInstance());
                o = joinInstances.get(aClass);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return o;
    }

从Holder中获取获取扩展实现的Class集合

    public Map<String, Class<?>> getExtensionClasses() {
        // 扩区SPI扩展实现的缓存,对应的就是扩展文件中的 key - value
        Map<String, Class<?>> classes = cachedClasses.getT();
        if (null == classes) {
            synchronized (cachedClasses) {
                classes = cachedClasses.getT();
                if (null == classes) {
                    // 加载扩展
                    classes = loadExtensionClass();
                    // 缓存扩展实现集合
                    cachedClasses.setT(classes);
                }
            }
        }
        return classes;
    }

加载扩展实现Class

加载扩展实现Class,就是从文件中获取扩展实现的Class,然后缓存起来

    public Map<String, Class<?>> loadExtensionClass() {
        // 扩展接口tClass,必须包含SPI注解
        SPI annotation = tClass.getAnnotation(SPI.class);
        if (null != annotation) {
            String v = annotation.value();
            if (StringUtils.isNotBlank(v)) {
                // 如果有默认的扩展实现名,用默认的
                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(扩展实现的Class)
        try (InputStream inputStream = url.openStream()) {
            Properties properties = new Properties();
            properties.load(inputStream);
            properties.forEach((k, v) -> {
                // 扩展实现的名字
                String name = (String) k;
                // 扩展实现的Class的全路径
                String classPath = (String) v;
                if (StringUtils.isNotBlank(name) && StringUtils.isNotBlank(classPath)) {
                    try {
                        // 加载扩展实现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 {
        // 反射创建扩展实现的Class
        Class<?> subClass = Class.forName(classPath);
        // 扩展实现的Class要是扩展接口的实现类
        if (!tClass.isAssignableFrom(subClass)) {
            throw new IllegalArgumentException("load extension class error " + subClass + " not sub type of " + tClass);
        }
        // 扩展实现要有Join注解
        Join annotation = subClass.getAnnotation(Join.class);
        if (null == annotation) {
            throw new IllegalArgumentException("load extension class error " + subClass + " without @Join" +
                    "Annotation");
        }
        // 缓存扩展实现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);
        }
    }

存储Holder

    public static class Holder<T> {
        private volatile T t;
        public T getT() {
            return t;
        }
        public void setT(T t) {
            this.t = t;
        }
    }

测试SPI

定义SPI接口

@SPI
public interface TestSPI {
    void test();
}

扩展实现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文件夹下创建META-INF/log-helper文件夹,并创建扩展文件

文件名称(接口全路径名):com.redick.spi.test.TestSPI

文件内容

testSPI1=com.redick.spi.test.TestSPI1Impl
testSPI2=com.redick.spi.test.TestSPI2Impl

动态使用测试

public class SpiExtensionFactoryTest {
    @Test
    public void getExtensionTest() {
        TestSPI testSPI = ExtensionLoader.getExtensionLoader(TestSPI.class).getJoin("testSPI1");
        testSPI.test();
    }
}

测试结果:

test1

public class SpiExtensionFactoryTest {
    @Test
    public void getExtensionTest() {
        TestSPI testSPI = ExtensionLoader.getExtensionLoader(TestSPI.class).getJoin("testSPI2");
        testSPI.test();
    }
}

测试结果:

test2

总结

实现一个自定义的SPI机制其核心的逻辑就是扩展的加载,本篇是参考Dubbo等开源项目简单实现了一个SPI机制的核心代码,核心逻辑就是从SPI扩展的配置文件中加载扩展实现的流程,通常情况下,SPI的应用场景出现在高度可扩展组件,并且在使用过程中有需求能够灵活切换不同的实现的时候。比如程序使用限流组件,使用“令牌桶算法”和“漏桶算法”分别实现了限流逻辑,在业务使用限流算法的过程中,就可以通过SPI机制在程序启动过程中将两种算法实现的组件加载好,然后通过参数指定具体使用的限流算法。此外,SPI机制能够对扩展开放,常用于开源软件,用户可以实现自己的扩展。

到此这篇关于详解Java实现简单SPI流程的文章就介绍到这了,更多相关Java实现SPI内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java中的SPI机制案例分享

    目录 1 简单介绍 2 SPI 案例 3 SPI 的原理剖析 1 简单介绍 当我们封装了一套接口,其它项目想要调用我们的接口只需要引入我们写好的包,但是其它项目如果想要对我们的接口进行扩展,由于接口是被封装在依赖包中的,想要扩展并不容易,这时就需要依赖于Java为我们提供的SPI机制. SPI的全称是Service Provider Interface,服务提供者接口,而与之最接近的概念就是API,全称Application Programming Interface,应用程序编程接口.那么这两

  • JAVA中的SPI思想介绍

    目录 1. SPI介绍 2. SPI规则 3. SPI案例 3.1 组件的定义 3.2 组件的实现 3.3 组件的选用 4. SPI原理 5. SPI要求 6. SPI应用 总结 1. SPI介绍 SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口,其意义在于为某个接口寻找服务的实现,主要应用在框架中用来寻找组件,提高扩展性. 汽车制造是一个比较繁琐的过程,通常的手段是先规定汽车各个零部件的生产规格,各个零部件厂商按照这种规则去生产

  • 一文带你了解Java中的SPI机制

    目录 1: SPI机制简介 2: SPI原理 3: 使用场景 4: 源码论证 5: 实战 6: 优缺点 6.1 优点 6.2 缺点 1: SPI机制简介 SPI 全称是 Service Provider Interface,是一种 JDK 内置的动态加载实现扩展点的机制,通过 SPI 技术我们可以动态获取接口的实现类,不用自己来创建.这个不是什么特别的技术,只是 一种设计理念. 2: SPI原理 Java SPI 实际上是基于接口的编程+策略模式+配置文件组合实现的动态加载机制. 系统设计的各个

  • JAVA SPI机制详解使用方法

    目录 写在前面 什么是SPI 使用场景 实现约定 四种角色 基于JAVA原生特性实现的JAVA SPI机制的DEMO 1. 主要角色 2. 示例代码 3. 说明 基于SPRING BOOT实现的JAVA SPI机制的DEMO 写在前面 Java SPI提供了一种为某个接口寻找服务实现的机制.有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,SPI的核心思想就是解耦. 什么是SPI SPI全称Service Provider Interface,是Java提供的

  • 一文搞懂Java SPI机制的原理与使用

    目录 SPI 概念 举个栗子 第一步 第二步 第三步 第四步 原理 常用的框架 优缺点 优点 缺点 Java 程序员在日常工作中经常会听到 SPI,而且很多框架都使用了 SPI 的技术,那么问题来了,到底什么是 SPI 呢?今天阿粉就带大家好好了解一下 SPI. SPI 概念 SPI 全称是 Service Provider Interface,是一种 JDK 内置的动态加载实现扩展点的机制,通过 SPI 技术我们可以动态获取接口的实现类,不用自己来创建. 这里提到了接口和实现类,那么 SPI 

  • 深入探讨Java SPI机制及其应用场景

    目录 一.什么是SPI 二.使用场景 三.使用步骤示例 四.原理解析 1.SPI的核心就是ServiceLoader.load()方法 2.ServiceLoader核心代码介绍 一.什么是SPI SPI全称Service Provider Interface,是Java提供的一种服务发现机制.实现服务接口和服务实现的解耦. Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,实现不修改任何代码的情况下切换不同的实现. 二.使用场景 很多开源第三方jar包都有

  • 浅析Java中的SPI原理

    在面向对象的程序设计中,模块之间交互采用接口编程,通常情况下调用方不需要知道被调用方的内部实现细节,因为一旦涉及到了具体实现,如果需要换一种实现就需要修改代码,这违反了程序设计的"开闭原则".所以我们一般有两种选择:一种是使用API(Application Programming Interface),另一种是SPI(Service Provider Interface),API通常被应用程序开发人员使用,而SPI通常被框架扩展人员使用. 在进入下面学习之前,我们先来再加深一下API和

  • Java深入讲解SPI的使用

    目录 什么是Java SPI Java SPI使用demo SPI在JDBC中的应用 SPI在sharding-jdbc中的应用 扩展 什么是Java SPI     SPI的全名为:Service Provider Interface.在java.util.ServiceLoader的文档里有比较详细的介绍.简单的总结下 Java SPI 机制的思想.我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块.jdbc模块的方案等.面向的对象的设计里,我们一般推荐模

  • 详解java实现简单扫码登录功能(模仿微信网页版扫码)

    java实现简单扫码登录功能 模仿微信pc网页版扫码登录 使用js代码生成qrcode二维码减轻服务器压力 js循环请求服务端,判断是否qrcode被扫 二维码超时失效功能 二维码被扫成功登录,服务端产生sessionId,传到页面使用js保存cookie 多线程 生成qrcode相关js jquery.qrcode.js 代码 页面div <div class="pc_qr_code"> <input type="hidden" id="

  • 详解java实践SPI机制及浅析源码

    1.概念 正式步入今天的核心内容之前,溪源先给大家介绍一下关于SPI机制的相关概念,最后会提供实践源代码. SPI即Service Provider Interface,属于JDK内置的一种动态的服务提供发现机制,可以理解为运行时动态加载接口的实现类.更甚至,大家可以将SPI机制与设计模式中的策略模式建立联系. SPI机制: 从上图中理解SPI机制:标准化接口+策略模式+配置文件: SPI机制核心思想:系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编

  • 一文详解Java中流程控制语句

    目录 概述 判断语句 if if...else if..else if...else if语句和三元运算符的互换 选择语句 switch case的穿透性 循环语句 for while do...while for 和 while 的小区别 跳出语句 break continue 死循环 嵌套循环 概述 在一个程序执行的过程中,各条语句的执行顺序对程序的结果是有直接影响的.也就是说,程序的流程对运行结果有直接的影响.所以,我们必须清楚每条语句的执行流程.而且,很多时候我们要通过控制语句的执行顺序

  • 详解Java中的流程控制

    1.分支结构的概念 当需要进行条件判断并做出选择时,使用分支结构 2.if分支结构 格式: if(条件表达式){ 语句块; } package com.lagou.Day04; import java.util.Scanner; /** * 编程使用if分支结构模拟网吧上网的过程 */ public class Demo01 { public static void main(String[] args) { //1.提示用户输入年龄信息并使用变量记录 System.out.println("请

  • 详解Java中ThreadLocal类型及简单用法

    目录 1 基本概念 2 简单使用 3 应用场景 4 底层原理 4.1 set(Object) 4.2 get() 4.3 remove() 4.4 ThreadLocalMap 5 内存泄漏隐患和防止策略 5.1 为什么会发生内存泄漏? 5.2 怎样防止内存泄漏? 1 基本概念 ThreadLocal类提供了线程局部变量.这些变量与普通变量的不同之处在于,每个访问一个变量(通过其get或set方法)的线程都有自己的.独立初始化的变量副本.ThreadLocal实例通常是希望将状态与线程关联起来的

  • 详解Java内部类与对象的打印概念和流程

    目录 一.内部类的概念 二.内部类的分类 三.成员内部类 1.普通内部类 2.静态内部类 四.局部内部类 五.对象的打印 一.内部类的概念 在 Java 中,可以将一个类定义在另一个类或者一个方法的内部,前者称为内部类,后者称为外部类.内部类也是封装的一种体现. public class OutClass {//外部类 class InnerClass{//内部类 } } 注意事项: 1.内部类一定是定义在class 类名{}之中的类,定义在class 类名{}之外的,哪怕是在一份文件中,也并不

  • 详解Java中的三种流程控制语句

    目录 顺序语句 选择语句 if else的嵌套 switch case default 循环语句 for for in while do while break continue 顺序语句 顺序顾名思义就是程序自上而下执行 public class User { public static void main(String[] args) { String name = "hacker"; int age = 18; String happy = "学习Java";

  • 详解Java类加载器与双亲委派机制

    目录 引子 了解.class文件 类加载的过程 类加载器 与 双亲委派机制 ClassLoader 自定义类加载器 编写一个自定义的类加载器 为什么我们这边要打破双亲委派机制 自定义类加载器时,如何打破双亲委派机制 SPI机制 与 线程上下文类加载器 JDBC Tomcat SpringBoot Starter 尾语 引子 大家想必都有过平时开发springboot 项目的时候稍微改动一点代码,就得重启,就很烦 网上一般介绍 2种方式 spring-boot-devtools,或者通过JRebe

  • 详解Java线程池和Executor原理的分析

    详解Java线程池和Executor原理的分析 线程池作用与基本知识 在开始之前,我们先来讨论下"线程池"这个概念."线程池",顾名思义就是一个线程缓存.它是一个或者多个线程的集合,用户可以把需要执行的任务简单地扔给线程池,而不用过多的纠结与执行的细节.那么线程池有哪些作用?或者说与直接用Thread相比,有什么优势?我简单总结了以下几点: 减小线程创建和销毁带来的消耗 对于Java Thread的实现,我在前面的一篇blog中进行了分析.Java Thread与内

  • 详解Java面试官最爱问的volatile关键字

    本文向大家分享的主要内容是Java面试中一个常见的知识点:volatile关键字.本文详细介绍了volatile关键字的方方面面,希望大家在阅读过本文之后,能完美解决volatile关键字的相关问题.  在Java相关的岗位面试中,很多面试官都喜欢考察面试者对Java并发的了解程度,而以volatile关键字作为一个小的切入点,往往可以一问到底,把Java内存模型(JMM),Java并发编程的一些特性都牵扯出来,深入地话还可以考察JVM底层实现以及操作系统的相关知识. 下面我们以一次假想的面试过

随机推荐