Java的SPI机制实践

发布时间 2023-07-18 22:52:47作者: nuccch

Java SPI机制概述

先给出结论:“Java的SPI是一种服务发现机制,用于约定接口和动态发现实现类,体现了分层解耦的思想”。
Java的SPI机制常用于框架扩展或组件替换,最常见的Java SPI应用就是JDBC Driver,JDK提供了java.sql.Driver接口,却将具体的实现交给了相应的数据库驱动,比如:在mysql-connector-java-6.0.6.jar文件中可以看到一个遵循Java SPI机制的文件META-INF/services/java.sql.Driver,并且在该文件中定义了具体的驱动实现类完整限定名称:com.mysql.cj.jdbc.Driver
MySQL驱动SPI机制

驱动实现类com.mysql.cj.jdbc.Driver实现了JDK提供的java.sql.Driver接口。
MySQL驱动实现类

关于Java原生SPI机制的说明详见:Introduction to the Service Provider Interfaces

如何实践Java SPI机制

应用Java SPI机制分为四步:

第一步: 约定接口。

package org.chench.extra.java.spi;
public interface HelloSPI {
    void sayHello();
}

第二步: 编写接口实现类。

package org.chench.extra.java.spi;
public class ImageHello implements HelloSPI {
    @Override
    public void sayHello() {
        System.out.println("Image Hello");
    }
}

第三步: 在实现类所在的jar包路径META-INF/services下创建一个以“接口完整限定名”命名的描述文件,文件内容为“实现类的完整限定名”(可以是包含多行,每一行是一个实现类的完整限定名)。
如:创建文件META-INF/services/org.chench.extra.java.spi.HelloSPI,内容为:org.chench.extra.java.spi.ImageHello
SPI描述文件

第四步: 使用Java SPI机制动态加载实现类。

public class SPISample {
    public static void main(String[] args) {
        // Java SPI机制使用ServiceLoader动态加载实现类
        ServiceLoader<HelloSPI> loader = ServiceLoader.load(HelloSPI.class);
        Iterator<HelloSPI> iterator = loader.iterator();
        while (iterator.hasNext()) {
            iterator.next().sayHello();
        }
    }
}

输出:

Image Hello

如上所示,在Java SPI机制的应用中接口实现类是完全分开的(在不用的jar文件中),所以需要在实现类所在的jar文件中包含一个描述文件。

通常来讲,Java的SPI机制常用于框架中实现功能扩展或替换,即:接口定义和使用ServiceLoader动态加载实现类是在框架代码中,而接口实现类以及描述文件是在扩展代码中,它们分布在不同的jar文件。
实际上,在分布式服务框架Dubbo中也提供了类似于Java原生SPI的扩展机制,详见:自定义扩展

Java原生SPI机制的不足

ServiceLoader类的应用及实现来看,存在一些缺点和不足:

  1. 原生的Java SPI机制只能通过迭代器访问实现类,这样会加载所有在描述文件中的实现类,无法按需加载将造成内存资源浪费。
  2. 多个并发线程使用ServiceLoader示例时不是线程安全的。

为了弥补Java原生SPI机制的不足,Dubbo框架提供了自己的SPI扩展机制,具体使用详见:自定义扩展

【参考】
深入理解 Java 中 SPI 机制
搞懂dubbo的SPI扩展机制
Dubbo 扩展设计理念