Java的SPI服务发现机制详解

在学习SpringBoot时经常会听到SPI这个术语,那什么是SPI呢?SPI有什么作用呢?与SpringBoot有什么关联呢?下面我们一起来探究下。

什么是SPI

SPI全拼是Service Provider Interface,是Java提供的一种服务发现机制。它会加载所有jar包中classpath路径下的META-INF/services文件夹下的文件,自动加载文件里所定义的类。通过这种机制为许多框架提供了扩展的可能,比如JDBC中就使用了SPI机制。

下面是JDK定义了JDBC接口,不同的数据库厂商实现了JDBC的接口,不同的数据库在自己的实现jar包中META-INF/services文件夹下创建接口名称文件(接口名是需要实现的接口全类名),文件内容就是实现接口的具体类。

相当于java定义了规范,具体的由第三方实现,就是java和第三方提供商的一个协定。

下面先来看个案例,弄懂SPI的用法。

SPI接口定义

第一步定义SPI接口,相当于定义一个规范,实现交给第三方。

创建一个maven项目,不要创建成springboot项目,因为默认的springboot项目打的jar和普通的maven项目jar包的目录结构不一致,导致第三方引用接口失败。

把项目打一个Jar包。这就是一个接口定义包,很简单。

SPI接口实现

我们创建两个接口实现的项目,创建两个项目是为了说明接口可以有不同的第三方实现。

这里也是创建普通的maven项目,不要创建成功springboot项目,原因与上面一样。

创建第一个项目

创建一个mySqlDatabase项目。

实现Connection接口。

在classpath路径下创建META-INF/services目录,在目录中创建接口全类路径con.study.database.Connection的文件,文件中的内容就是实现Connection接口的实现类名称。

打成jar包,供应用程序调用。

创建第二个项目

创建项目MyOracleDatabase。

实现Connection接口。

在classpath路径下创建META-INF/services目录,在目录中创建接口全类路径con.study.database.Connection的文件,文件中的内容就是实现Connection接口的实现类名称com.study.MyOracleConnection。

打成jar包供应用程序调用。

应用程序使用SPI接口

应用程序首先导入上面两个jar包,真正使用的时候导入一个实现了Connection接口的jar包就可以了,这里是为了演示所以导入的两个实现的jar包。

<dependency>
            <groupId>org.example</groupId>
            <artifactId>mySqlDatabase</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>MyOracleDatabase</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

调用JDK的ServiceLoader.load接口加载lib库下面的所有jar包的classpath路径下META-INF/services目录下的com.study.database.Connection文件。

package com.example.importstudy.service;

import com.study.database.Connection;

import java.util.Iterator;
import java.util.ServiceLoader;

public class TestMyDataBaseConnect {
    public static void main(String[] args) {
        ServiceLoader<Connection> load = ServiceLoader.load(Connection.class);
        Iterator<Connection> iterator = load.iterator();
        while (iterator.hasNext()) {
            Connection connection = iterator.next();
            connection.connect();
        }
    }
}

测试结果

Connected to the target VM, address: '127.0.0.1:61535', transport: 'socket'
mysql connection
oracle connection
Disconnected from the target VM, address: '127.0.0.1:61535', transport: 'socket'

可以看到两个实现类都被加载了,但是我们在测试代码中并没使用任何实现类,只是使用了定义的Connection接口。这就是SPI的原理,加载META-INF/services下的接口名称文件,把文件中的实现类加载到JVM中。

总结

SPI是Java提供的一种服务发现机制,让框架只需要定义好接口,实现交给第三方,第三方只要在META-INF/service目录下添加接口全类名文件,文件中填写实现类的全类名即可,框架启动时会自动加载所有jar包中META-INF/service目录下以接口命名的文件。这样应用程序仅需要简单的导入实现类的Jar包就可以使用接口的功能了,用户可以很方便的替换不同的实现,而不需要改动业务调用代码。

springBoot自动装配就是借鉴了Java的SPI服务发现机制,springBoot在启动是会自动扫描所有jar包下的META-INF/spring.factories文件并加载其中的org.springframework.boot.autoconfigure.EnableAutoConfiguration关联的类。

举报
评论 0