作用
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接口实现类的初始化加载,即完成配的过程