SPI 机制

297 阅读2分钟

SPI (Service Provider Interface)是 JDK 内置的一种服务发现机制,可以用来启动框架扩展和替换组件,主要是写框架和扩展框架的开发人员使用。

SPI 实现

比如我们要调用打车接口,实际可能下单的平台包括滴滴、曹操出行、出租车平台等等,我们可以先定义一个接口

  • 打车接口定义
public interface CallCar {

    /**
     * 叫车接口
     */
    void call();
}
  • 滴滴约车实现
public class DidiCall implements CallCar {

    /**
     * 叫车接口
     */
    @Override
    public void call() {
        System.out.println("滴滴约车中。。。");
    }
}
  • 曹操出行约车实现
public class CaoCaoCall implements CallCar {

    /**
     * 叫车接口
     */
    @Override
    public void call() {
        System.out.println("曹操出行约车中。。。");
    }
}
  • 出租车约车实现
public class TaxiCall implements CallCar {

    /**
     * 叫车接口
     */
    @Override
    public void call() {
        System.out.println("出租车约车中。。。");
    }
}

接下来、还需要在 resources目录下新建 META-INF/services 目录,然后新建一个与约车接口全称命名相同的文件,文件的位置及内容如下

image.png

com.knowledge.spi.car.CaoCaoCall
com.knowledge.spi.car.DidiCall
  • 编写测试用例
public class CaseDemo {

    public static void main(String[] args) {
        ServiceLoader<CallCar> serviceLoader = ServiceLoader.load(CallCar.class);
        final Iterator<CallCar> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            final CallCar callCar = iterator.next();
            callCar.call();
        }
    }
}
  • 运行结果
曹操出行约车中。。。
滴滴约车中。。。

Process finished with exit code 0

实际上我们调用 ServiceLoader.load() 方法加载某接口时,会去 META-INF/services 下找接口的全称命名文件,再根据文件内定义的实现类路径加载所有的类。这就是 SPI 的思想,接口的实现由 provider 实现,我们在扩展框架的功能时、也可以用这种思想添加我们自己的扩展实现类。

SPI 在JDBC中的应用

在 JDBC4.0 之前我们要想连接数据库,通常会调用Class.forName("com.mysql.jdbc.Driver") 来加载驱动,但是在 JDBC4.0 之后,不需要手动加载驱动,直接获取连接就可以了,因为新版本的 JDBC 使用了 SPI 扩展机制来实现。

  • 接口定义

在 jdk 中定义了接口java.sql.Driver,但是并没有具体实现

image.png

  • mysql 实现

当我们引入mysql-connector-java-6.0.6.jar后,可以发现在 jar 包中有一个 META-INF/services 目录,可以发现有一个 java.sql.Driver 的文件,里边的内容就是com.mysql.cj.jdbc.Driver。根据 SPI 的服务发现机制,数据库驱动就会加载。

  • postgresql 实现

同样的,当我们引入postgresql-42.0.0.jar后,也可以发现在 jar 包中有一个 META-INF/services 目录,可以发现有一个 org.postgresql.Driver 的文件

  • 使用方法

因为有了 SPI 的服务发现机制,我们在写代码连接数据库的时候,不需要再写Class.forName("com.mysql.jdbc.Driver"),直接这样写即可

String url = "jdbc:xxxx://xxxx:xxxx/xxxx"; 
Connection conn = DriverManager.getConnection(url,username,password);
  • 源码实现

在java.sql.DriverManager中,有个loadInitialDrivers方法

static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

image.png 当DriverManager中加载的时候就会调用下边这行,使用 SPI 机制加载我们 jar 包中或者我们自己扩展的驱动实现。当然、实现可以有多个。

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

image.png