一分钟 了解 Java SPI 机制

什么是 Java SPI

“Java SPI(Service Provider Interface),是JDK内置的一种动态加载扩展点的实现,是一种服务机制,SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载机器读取配置文件,加载实现类,这样就是可以在运行时动态的为接口替换实现类” ------ dubbo 官网

代码实现

首先在ClassPath的META-INF/services目录下放置一个与接口同名的文本文件,文件的内容为接口的实现类,多个实现类用换行符分隔。JDK中使用java.util.ServiceLoader来加载具体的实现。

1.首先定义一个RobotCar接口

/**
 * @Auther: corey
 * @Date: 2020/7/3 10:05
 * @Description: qq飞车
 */
public interface RobotCar {

    void hello();
}

2.提供RobotCar的实现, RobotCar有两个实现。BigPenguin和OldGodmother。

/**
 * @Auther: corey
 * @Date: 2020/7/3 10:06
 * @Description: 大企鹅
 */
public class BigPenguin implements RobotCar {

    @Override
    public void hello() {
        System.out.println("Hello  我是大企鹅。。。");
    }
}
/**
 * @Auther: corey
 * @Date: 2020/7/3 10:08
 * @Description: 老干妈
 */
public class OldGodmother implements RobotCar {
    @Override
    public void hello() {
        System.out.println("hello 我是老干ma。。。");
    }
}

3.添加配置文件 在META-INF/services目录添加一个文件,文件名和接口全名称相同,所以文件是META-INF/services/com.service.RobotCar文件内容为:

service.impl.BigPenguin
service.impl.OldGodmother

4.通过ServiceLoader加载RobotCar实现

/**
 * @Auther: corey
 * @Date: 2020/7/3 10:17
 * @Description:
 */
public class RobotCarTest {

    public static void main(String[] args) {
        ServiceLoader<RobotCar> serviceLoader = ServiceLoader.load(RobotCar.class);
        System.out.println("Java SPI....");
        serviceLoader.forEach(RobotCar::hello);
    }
}

运行结果为:
在这里插入图片描述
在上面的例子中,我们定义了一个扩展点和它的两个实现。在ClassPath中添加了扩展的配置文件,最后使用ServiceLoader来加载所有的扩展点。 最终的输出结

总结:

如图所示:

在这里插入图片描述
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。

Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦。

优点:
使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。

缺点:

  • 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类,的方式不够灵活

  • 多个并发多线程使用ServiceLoader类的实例是不安全的。

使用场景常见地方有

  • 数据库驱动加载接口实现类的加载
    JDBC加载不同类型数据库的驱动

  • 日志门面接口实现类加载,SLF4J加载不同提供商的日志实现类

  • Spring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等

  • Dubbo中也大量使用SPI的方式实现框架的扩展, 不过它对Java提供的原生SPI做了封装,允许用户扩展实现Filter接口

参考:
http://dubbo.apache.org/zh-cn/blog/introduction-to-dubbo-spi.html
https://developer.aliyun.com/article/640161

相关连接:
https://mp.weixin.qq.com/s/M0YIYOi9ucgnai1ILUVAvQ

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章