作用

SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。

使用

开发接口模块

public interface PayService {

    String pay();
}

开发实现模块

public class AliPayService implements PayService {
    @Override
    public String pay() {
        return "ali";
    }
}

添加service

  • 在resouces下创建目录META-INF/services
  • 在services添加文件com.xxx.PayService,即接口的fullname,内容为实现类,即AliPayService的fullname
  • 当多个实现类时,换行添加多个

测试

public static void main(String []args) {
    Iterator<PayService> iterator = ServiceLoader.load(PayService.class).iterator();
    while (iterator.hasNext()) {
        PayService payService = iterator.next();
        System.out.println(payService.pay());
    }
}

services可以在接口包,实现包或测试包均可,执行结果为所有所下com.xxx.PayService文件中的全集

源码解析

  • ServiceLoader为Iterable的实现类
  • 在ServiceLoader的load方法中,实际为通过接口Class和ClassLoader生成ServiceLoader实例
  • 在ServiceLoader实例的hasNext中,会读取resources下的文件内容,并保存下一个实例name nextName
  • 在next方法中,根据nextName 通过Class.forName(),生成实例并返回
  • 将object通过class.cast转换为泛型后返回

Dubbo中的spi

dubbo框架是对spi使用的一个很好的例子,dubbo框架本身就是基于SPI规范做了更进一步的封装,从上面的优缺点分析中,我妈了解了原生的SPI在客户端选择服务的时候需要遍历所有的接口实现,比较浪费资源,而dubbo在此基础上有了更好的封装和实现,下面来了解下dubbo的SPI使用吧

Dubbo 的 SPI 规范是:

  • 接口名:可随意定义
  • 实现类名:在接口名前添加一个用于表示自身功能的“标识前辍”字符串
  • 提供者配置文件路径:在依次查找的目录为
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
  • 提供者配置文件名称:接口的全限定性类名,无需扩展名
  • 提供者配置文件内容:文件的内容为 key=value 形式,value 为该接口的实现类的全限类性类名,key 可以随意,但一般为该实现类的“标识前辍”(首字母小写)。一个类名占
    一行
  • 提供者加载:ExtensionLoader 类相当于 JDK SPI 中的 ServiceLoader 类,用于加载提供者配置文件中所有的实现类,并创建相应的实例

具体实现略

在Dubbo源码中,很多地方会存在下面这样的三种代码,分别是自适应扩展点、指定名称的扩展点、激活扩展点,dubbo通过这些扩展的spi接口实现众多的插拔式功能

ExtensionLoader.getExtensionLoader(xxx.class).getAdaptiveExtension();
ExtensionLoader.getExtensionLoader(xxx.class).getExtension(name);
ExtensionLoader.getExtensionLoader(xxx.class).getActivateExtension(url, key);

以dubbo源码中的Protocol 为例,对应dubbo源码中的rpc模块

@SPI("dubbo")  
public interface Protocol {  
      
    int getDefaultPort();  
  
    @Adaptive  
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;  
  
    @Adaptive  
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;  

    void destroy();  
 
} 

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
  • Protocol接口,在运行的时候dubbo会判断一下应该选用这个Protocol接口的哪个实现类来实例化对象
  • 如果你配置了Protocol,则会将你配置的Protocol实现类加载到JVM中来,然后实例化对象时,就用你配置的那个Protocol实现类就可以了

上面那行代码就是dubbo里面大量使用的,就是对很多组件,都是保留一个接口和多个实现,然后在系统运行的时候动态的根据配置去找到对应的实现类。如果你没有配置,那就走默认的实现类,即dubbo

————————————————
版权声明:本文为CSDN博主「小码农叔叔」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhangcongyi420/article/details/121883515

spring中的spi

springboot怎么知道启动时需要加载 DataSource这个数据库连接的bean对象呢?怎么知道要使用JdbcTemplate 还是Druid的连接呢?

在spingboot工程启动过程中,有很重要的一个工作,就是完成bean的自动装配过程,自动装配装配的是什么东西呢?简单来说就是:

  • 扫描classpath(工程目录下)下所有依赖的jar包装中指定目录中以特定的全限定名称的文件,进行解析并装配成bean
  • 扫描xml文件,解析xml配置并装配成bean
  • 解析那些被认为是需要装配的配置类,如@configuration,@service等

其中第一步中的那些文件是什么呢?其实就是和dubbo或原生的spi规范中的那些 /META-INF 文件,只不过在springboot工程中,命名的格式和规范稍有不同

下面通过源码来看看springboot启动过程中是如何加载这些spi文件的吧

然后来到下面这里,重点关注setInitializers 这个方法,顾名思义,表示在启动过程中要做的一些初始化设置,那么要设置哪些东西呢?

在这个方法中,有一个方法getSpringFactoriesInstances,紧接着这个方法看进去

在该方法中需要重点关注这句代码,通过这句代码,将依赖包下的那些待装配的文件进行加载,说白了,就是加载classpath下的那些 spring.factory的文件里面的name

SpringFactoriesLoader.loadFactoryNames(type, classLoader)

那么问题是具体加载的是什么样的文件呢?不妨继续点进去看看,在SpringFactoriesLoader类的开头,有一个这样的路径,想必大家就猜到是什么了吧

也就是说,会去找以这样的名字结尾的文件,比如我们在依赖的jar包中,看到下面这一幕,在这个spring.factories中,会看到更多我们熟悉的配置

这样问题就很明白了,通过找到spring.factories的文件,然后解析出具体的类的完整的名称,然后再在:createSpringFactoriesInstances 这个方法中完成对这些 扩展的SPI接口实现类的初始化加载,即完成配的过程

参考