三种SPI机制的了解及使用
文章目录
1.SPI机制概念
SPI全程Service Provider Interface,是一种服务发现机制。
SPI的本质就是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类型。这样就可以运行时,动态为接口替换实现类。
正因此特性,我们可以很容易的通过SPI机制为我们的程序提供拓展功能。
2.Java SPI
2.1 创建一个项目,并创建如下模块
注意模块之间需要对应引用依赖,比如:db-api需要被其他三个模块引入,main-project要引入两个实现模块依赖。
2.2 db-api模块

public interface DBApi {
void getDBImpl();
}
2.3 mysql-impl模块

实现DBApi接口,并且在resources下创建META-INF/services/全限定接口名称文件,这里如上图所示,然后文件中写实现类的全限定类名。
com.linging.impl.MysqlImpl
2.4 oracle-impl模块

同上,文件中写oracle的实现类全限定类名:
com.linging.impl.OracleImpl
2.5 main-project模块

引入依赖:
<dependencies>
<dependency>
<groupId>com.linging</groupId>
<artifactId>db-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.linging</groupId>
<artifactId>mysql-impl</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.linging</groupId>
<artifactId>oracle-impl</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
public class Application {
public static void main(String[] args) {
ServiceLoader<DBApi> serviceLoader = ServiceLoader.load(DBApi.class);
Iterator<DBApi> it = serviceLoader.iterator();
while(it.hasNext()){
DBApi next = it.next();
// 判断具体实现类,进行处理...
if(next.getClass().equals(MysqlImpl.class)){
System.out.println("找到特定实现类");
}
next.getDBImpl();
}
}
}
通过上面的解析,可以发现,使用JavaSPI机制存在一些缺陷:
- 不能按需加载,需要遍历所有实现,并实例化,然后在循环中才能找到我们需要的实现类,当某个实现类实例化很耗时,它也被载入并实例化,这就造成加载耗时过长。
- 多个并发多线程使用ServiceLoader类的实例不安全。
3.Spring SPI
Spring SPI沿用了Java SPI的设计思想,采用spring.factories方式实现SPI机制,可以在不修改源码的前提下,提供Spring框架的扩展性。
引入spring依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
db-api模块:
public interface DBApi {
void getDBImpl();
}
xxx-impl实现模块的不同:
spring,factories中的内容:
com.linging.api.DBApi=com.linging.impl.MysqlImpl
main-project模块:
public class Application {
public static void main(String[] args) {
List<DBApi> loadFactories =
SpringFactoriesLoader.loadFactories(DBApi.class, Thread.currentThread().getContextClassLoader());
Iterator<DBApi> it = loadFactories.iterator();
while(it.hasNext()){
DBApi next = it.next();
next.getDBImpl();
}
}
}
Spring SPI机制和JavaSPI机制很类似,但是还有一些差异:
- JavaSPI机制是一个服务提供接口对应一个配置文件,配置文件中存放当前接口的所有实现类,所有配置文件放在
META-INF/services目录下。 - SpringSPI是仅有一个
META-INF/spring.factories配置文件存放多个接口及对应的实现类,以接口限定类名作为key,实现类作为value,多个实现类用逗号隔开。 - 和JavaSPI不一样的是,SpringSPI也会遍历所有的类,也会全都实例化,但是Spring加入了@Conditional注解,配置这个注解可以实现满足条件的类才会进行实例化,如果没有注解其实跟JDK SPI一样了,这也是SpringSPI与JDKSPI得区别。
4.Dubbo SPI

引入dubbo依赖:
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.12</version>
</dependency>
db-api模块:
@SPI
public interface DBApi {
void getDBImpl();
}
xxx-impl模块:
配置文件内容:
mysql=com.linging.impl.MysqlImpl
main-project模块:
public class Application {
public static void main(String[] args) {
ExtensionLoader<DBApi> extensionLoader = ExtensionLoader.getExtensionLoader(DBApi.class);
// 按需加载
DBApi mysql = extensionLoader.getExtension("mysql");
mysql.getDBImpl();
}
}
- 基于JavaSPI的缺陷无法支持接口按需加载接口实现类,Dubbo并未使用JavaSPI,而是重新实现了一套功能更强的SPI机制。
- Dubbo SPI的相关逻辑被封装在ExtensionLoader类中,通过它可以加载指定的实现类。
- Dubbo SPI所需的配置文件放在
META-INF/dubbo路径下,配置内容如下:文件名为接口全限定类名,内容为key=value的键值对,key为实现类的标识别名,value为实现类的全限定类名,这样就可以实现按需加载了。 - 在使用时,需要在接口上加
@SPI注解。
JDKSPI、SpringSPI、DubboSPI得区别:
| 特性 | JDK SPI | Spring SPI | Dubbo SPI |
|---|---|---|---|
| 加载内容 | 全部实现类,直接实例化 | 只加载类名,不实例化 | key-value 配置,按需懒加载 |
| 按需加载 | ❌ 不行,全量加载 | ✅ 靠 @Conditional 控制 | ✅ 极强,按名称获取 |
| 按名获取实现 | ❌ 只能遍历 | ❌ 不支持 | ✅ 支持(getExtension (“key”)) |
| IOC/AOP | ❌ 无 | ✅ 依赖 Spring 容器 | ✅ 自带 IOC/AOP/Wrapper 包装 |
| 动态性 | ❌ 静态 | ❌ 启动固定 | ✅ @Adaptive 运行时动态选择 |
| 缓存 | ❌ 无 | ✅ 类名缓存 | ✅ 实例级强缓存 |
| 定位 | 通用底层服务发现 | Spring Boot 自动配置 | RPC 高性能、可插拔、动态扩展 |
- JDK SPI:最原始,加载即全部实例化,啥也没有
- Spring SPI:只加载类名,靠条件注解控制实例化,面向自动配置
- Dubbo SPI:最强最复杂,支持按需、按名、IOC/AOP、运行时动态适配,面向 RPC 扩展
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)