SPI机制
SPI机制
SPI (Service Provider Interface),主要适用于扩展作用,SPI可以很灵活的让接口和实现分离,让api提供者只提供接口,第三方来实现,然后可以使用配置文件的方式来实现替换或者扩展,在框架中比较常见,提高框架的可扩展性。
JDK的SPI机制-ServiceLoader
ServiceLoader是Java提供的一种简单的SPI机制的实现,Java的SPI实现约定了以下两件事:
- 文件必须放在
META-INF/services/
目录底下 - 文件名必须为接口的全限定名,内容为接口实现的全限定名
案例实现:
- 定义接口
public interface SpiInterface {
String spi()
}
- 实现接口
public class SpiInterfaceTest1 implements SpiInterface {
@Override
public String spi() {
return "SPI实现类test1";
}
}
public class SpiInterfaceTest2 implements SpiInterface {
@Override
public String spi() {
return "SPI实现类TEST2";
}
}
- 在resources目录下,建立文件夹
META-INF/services
,创建一个以接口的全限定名为名称的文件
,内容是该实现类的全限定名
- 使用ServiceLoader.load()方法来加载实现类
public static void main(String[] args) {
/*JDK SPI*/
ServiceLoader<SpiInterface> load = ServiceLoader.load(SpiInterface.class);
Iterator<SpiInterface> iterator = load.iterator();
while (iterator.hasNext()) {
SpiInterface spiInterface = iterator.next();
System.out.println(spiInterface.spi());
}
}
测试结果:
SPI实现类test1
SPI实现类TEST2
java中最常见的spi机制应用就是数据库驱动的加载,java其实就是定义了java语言跟数据库交互的接口,但是具体的实现得交给各大数据库厂商来实现,那么java怎么知道你的数据库厂商的实现了?这时就需要spi机制了,java好约了定在 Classpath 路径下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,然后内容是该数据库厂商的实现的接口的全限定名,这样数据库厂商只要按照这个规则去配置,java就能找到。
缺点:
- JDK的SPI会把所有的对象都实例化到内存中,不管你用不用,可能会导致资源的浪费
- 无法区分使用者应该使用哪一个实现类,需要从接口设计上入手决定使用那个实现类,不够灵活
Spring的SPI机制-SpringFactoriesLoader
Spring的SPI机制的约定如下:
- 配置文件必须在
META-INF/
目录下,文件名必须为spring.factories
- 文件内容为键值对,一个键可以有多个值,只需要用逗号分割就行,同时键值都需要是类的全限定名,键和值可以没有任何类与类之间的关系,当然也可以有实现的关系。
案例实现:
- 配置spring.factories
在Spring中提供的SPI机制,我们只需要在 META-INF/spring.factories
中配置接口实现类名,即可通过服务发现机制,在运行时加载接口的实现类:
- 使用SpringFactoriesLoader.loadFactories()来加载
List<SpiInterface> spiInterfaces = SpringFactoriesLoader.loadFactories(SpiInterface.class, Demo.class.getClassLoader());
for (SpiInterface spiInterface : spiInterfaces) {
System.out.println(spiInterface.spi());
}
测试结果:
SPI实现类test1
SPI实现类TEST2
- 自动配置原理
在我们日常定义starter的时候,一般在spring.factories
里会定义成org.springframework.boot.autoconfigure.EnableAutoConfiguration
作为key,这样是为了SpringBoot启动的时候,根据@EnableAutoConfiguration
注解来自动注入配置
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
在引入EnableAutoConfiguration
的时候,会执行AutoConfigurationImportSelector
里的逻辑,而真正去扫描各类starter的是其中的一个继承类ImportAutoConfigurationImportSelector
protected Collection<String> loadFactoryNames(Class<?> source) {
List<String> factoryNames = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(source, getBeanClassLoader()));
ImportCandidates.load(source, getBeanClassLoader()).forEach(factoryNames::add);
return factoryNames;
}
SpringFactoriesLoader.loadFactoryNames:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
这里就用到了SpringFactoriesLoader.loadFactoryNames(source, getBeanClassLoader())
去扫描各starter下的META-INF/spring.factories
配置文件,然后通过反射等操作把文件里的value值变成Spring里的Bean对象,完成自动配置;
但是SpringBoot3.0之后不再使用SpringFactoriesLoader,而是Spring重新从META-INF/spring/
目录下的org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中读取了。
优点:
- 不需要像JDK的SPI,一个接口就要定义一个文件,而是有统一的
spring.factories
配置文件,文件内容由k-v组成 - Spring的SPI机制提供了获取类限定名的方法
loadFactoryNames
,而Java的SPI机制是没有的。通过这个方法获取到类限定名之后就可以将这些类注入到Spring容器中,用Spring容器加载这些Bean,而不仅仅是通过反射
缺点:
- 没有实现获取指定某个指定实现类的功能,所以要想能够找到具体的某个实现类,还得依靠具体接口的设计
Dubbo的SPI机制-ExtensionLoader
Dubbo的SPI机制也做了以下几点约定:
- 接口必须要加@SPI注解
- 配置文件可以放在
META-INF/services/
、META-INF/dubbo/internal/
、META-INF/dubbo/
、META-INF/dubbo/external/
这四个目录底下,文件名也是接口的全限定名,一般使用META-INF/dubbo/
- 内容为键值对,键为短名称(可以理解为spring中Bean的名称),值为实现类的全限定名
案例实现:
测试:
ExtensionLoader<SpiInterface> extensionLoader =
ApplicationModel.defaultModel()
.getExtensionDirector()
.getExtensionLoader(SpiInterface.class);
SpiInterface test1 = extensionLoader.getExtension("test1");
System.out.println(test1.spi());
Dubbo的SPI是可以根据指定的key来选择使用哪个实现类的,这样比较灵活
dubbo核心机制
- 自适应机制
自适应机制说的是在代码运行过程中,可以基于参数动态的选择到具体的实现类来执行
每个接口有且只有一个自适应类,可以通过extensionLoader.getAdaptiveExtension();
得到这个自适应类
自适应类有两种方式产生,第一种就是自己指定,就是上面测试的那种写法,执行要执行的key
另外一种是在接口的实现类上加@Adaptive
注解,那么这个这个实现类就是自适应实现类。
@Adaptive
public class SpiInterfaceTest1 implements SpiInterface {
@Override
public String spi() {
return "SPI实现类test1";
}
}
ExtensionLoader<SpiInterface> extensionLoader =
ApplicationModel.defaultModel()
.getExtensionDirector()
.getExtensionLoader(SpiInterface.class);
SpiInterface adaptiveExtension = extensionLoader.getAdaptiveExtension();
System.out.println(adaptiveExtension.spi());
// SPI实现类test1
- 自动激活
自动激活,就是根据你的参数,动态的选择一批实现类返回给你,最常见的使用方式就是dubbo里Filter,可以动态的选择是提供者还是消费者
自动激活的实现类上需要加上Activate
注解:
@Activate(group = {CommonConstants.PROVIDER})
public class DubboRequestFilter implements Filter {
这就说明该DubboRequestFilter
在dubbo的提供方会被激活,提供自定义拦截器作用