Java基础-SPI机制

97 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情

什么是SPI?

SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver数据库驱动接口,日志接口,不同第三方可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现.Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是 解耦.

与API的区别

在结构上最简单的区别就是SPI机制的接口定义在服务方, 而API的接口定义在服务实现方.

image.png

SPI原理

在服务调用方定义好标准接口, 由第三方来实现, 在第三方实现接口后, 需要在服务提供jar包内的classpath下的META-INF/services/ 目录内提供一个文件名为接口的全限定名称的文件, 例如: java.sql.Driver . 文件内容为接口实现类的全限定名, 例如: com.mysql.cj.jdbc.Driver mysql-connection-java中的文件如图: image.png 在调用方需要使用到这个实现类时, 就会从jar包中的这个目录中去找到实现类的信息, 然后加载实现类并实例化, 就可以使用了. 加载实现类使用的是java中的java.util.ServiceLoader类中的load方法.

自定义一个SPI的Demo

  • 定义一个支付接口:
public interface Pay {
    Boolean settle(BigDecimal payAmount);
}
  • 实现类AliPay
public class AliPay implements Pay {
    @Override
    public Boolean settle(BigDecimal payAmount) {
        System.out.println("alipay amount: " + payAmount.toString());
        return true;
    }
}
  • 实现类WeChatPay
public class WeChatPay implements Pay {
    @Override
    public Boolean settle(BigDecimal payAmount) {
        System.out.println("WeChat pay amount: "+ payAmount.toString());
        return true;
    }
}
  • 配置文件配置

image.png

  • 调用方调用
public class SpiTest {
    public static void main(String[] args) {
        var serviceLoader = ServiceLoader.load(Pay.class);
        var iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            var next = iterator.next();
            next.settle(BigDecimal.TEN);
        }
    }
}

最后的输出结果是: WeChat pay amount: 10 alipay amount: 10

实现解析

在测试类中的ServiceLoader的load方法传递的参数是接口的Class对象, 该方法会去指定目录: META-INF/services 目录中寻找与当前接口同名的配置文件, 然后读取其内容, 如果有多个会全部读取, 然后执行类加载并初始化实现类, 最后实现类以列表的形式返回, 通过迭代列表即可访问配置文件中指定的第三方提供的服务类实例方法.

在示例工程中配置的实现类是两个, 在最终的打印结果也是两个. 在遇到多个时具体如何操作取决于业务需要, 可能是都执行, 也可能是随机选一个, 或者有优先级的选择, 要依据具体需求为准.