Java的SPI机制
一、简介
SPI 机制,英文全称为Service Provider Interface ,字面意思为服务提供者接口,它是JDK提供给"服务提供厂商"或者"插件开发者"使用的接口。
SPI的设计思想:在系统的各个模块中,往往有着多个实现,例如日志,数据库驱动...,其实就是为了在使用某个接口的时候不用指定实现类,可以通过SPI机制来发现和加载这些的实现类,Spring的自动装配也是参考了Java的SPI机制所实现的。
二、SPI实现流程
-
定义SPI顶级接口
-
创建SPI实现类,可以是一个或者多个
-
在resources目录下创建
META-INF
文件夹- 在
META-INF
下创建SPI顶级接口全路径为名称的文件。例:cn.yufire.demo.spi.CarEngine
- 在
-
将所有SPI实现类的全路径写入此文件中,多个换行分割
-
使用
java.util
包下的ServiceLoader
加载所有的API实现类- 调用实现类中实现好的方法
三、SPI的应用场景
目前很多主流的软件都在使用SPI进行开发,例如JDBC
的数据库驱动,在各家数据库厂商的提供的驱动中,基本都采用了SPI机制来加载自家的数据库驱动。
- 例如
MySQL
的驱动包
- 在DriverManager中就能看到加载SPI的地方和代码
scss
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
.....
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//使用ServiceLoader动态加载具体的驱动实现类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
.....
}
2.例如日志框架,log4j
和logback
都是通过SPI
机制加载的
3.例如dubbo
的负载均衡策略
类似SPI机制的技术例如,Spring Boot
的自动装配,其思想也是借鉴与SPI,也是在META-INF
包下创建一个名为spring.factories
的文件,将需要自动装配的类的全地址写入到这个文件内,就可以实现,自动装配所需要的Bean
四、SPI实现简单实现
1. 首先定义一个服务的顶级通用接口
csharp
package cn.yufire.spi;
/**
* @author Yu
* @date 2022/5/9 14:55
* 汽车引擎顶级接口,用于定义顶级SPI接口实现
*/
public interface CarEngine {
/**
* 装载引擎
*/
void loadingEngine();
}
2. 创建SPI顶级接口实现类
为了测试多实现类,所以创建2个SPI接口实现类
- 创建V8引擎实现类
java
package cn.yufire.spi.impl;
import cn.yufire.spi.CarEngine;
/**
* @author Yu
* @date 2022/5/9 14:58
* V8发动机SPI实现
*/
public class V8_Engine implements CarEngine {
@Override
public void loadingEngine() {
System.out.println("v8发动机引擎加载中...");
}
}
- 创建V12引擎实现类
java
package cn.yufire.spi.impl;
import cn.yufire.spi.CarEngine;
/**
* @author Yu
* @date 2022/5/9 14:58
* v12发动机SPI实现
*/
public class V12_Engine implements CarEngine {
@Override
public void loadingEngine() {
System.out.println("v12发动机引擎加载中...");
System.out.println("v12发动机引擎加载完毕! 我是最强的!");
}
}
3. 创建存放SPI实现类的文件
- 在
resources
目录下创建META-INF文件夹
- 在
META-INF
下创建SPI顶级接口类全地址为名称文件
4. 将SPI实现类写入此文件中
- 多个实现换行隔开即可
rust
cn.yufire.spi.impl.V12_Engine
cn.yufire.spi.impl.V8_Engine
5. 创建测试类
php
/**
* @author Yu
* @date 2022/5/9 15:00
* spi测试
*/
public class Tests {
public static void main(String[] args) {
// 获取所有的CarEngine实现类
ServiceLoader<CarEngine> serviceLoader = ServiceLoader.load(CarEngine.class);
// 执行所有实现类的loadingEngine方法
serviceLoader.forEach(CarEngine::loadingEngine);
}
}
- 执行后输出
erlang
v12发动机引擎加载中...
v12发动机引擎加载完毕! 我是最强的!
v8发动机引擎加载中...