Java的SPI机制——动态加载ServiceLoader
动态加载ServiceLoader
ServiceLoader 核心作用
ServiceLoader 是 Java 内置的服务提供者加载工具(属于 java.util 包),核心目标是实现 SPI(Service Provider Interface,服务提供者接口)机制:解耦服务接口与实现类,让程序能动态发现、加载和使用符合规范的第三方 / 模块化服务实现,无需硬编码依赖,支持可插拔的扩展。
ServiceLoader 使用示例
-
定义一个通用接口
Driverpublic interface Driver { void init(); } -
Driver的不同实现DriverImpl1和DriverImpl2public class DriverImpl1 implements Driver{ @Override public void init() { System.out.println("DriverImpl1 init"); } } public class DriverImpl2 implements Driver{ @Override public void init() { System.out.println("DriverImpl2 init"); } } -
在
src/main/resources/META-INF/services下新建SPI规范文件,文件名=接口的全类名,如com.xxx.Drivercom.xxx.DriverImpl1 com.xxx.DriverImpl2 -
使用
ServiceLoader加载Driver的不同实现DriverImpl1和DriverImpl2实例public class App { public static void main(String[] args) { ServiceLoader<Driver> serviceLoader = ServiceLoader.load(Driver.class); for (Driver driver : serviceLoader) { driver.init(); } } }输出
DriverImpl1 init DriverImpl2 init
ServiceLoader 典型使用场景
典型使用场景
- JDBC 驱动加载(最经典):
- Java 核心库只定义
java.sql.Driver接口,不提供具体数据库驱动; - MySQL/Oracle 等厂商实现
Driver接口,并在META-INF/services/java.sql.Driver文件中写入实现类名; - 程序通过
ServiceLoader.load(Driver.class)自动加载驱动,无需手动注册。
- Java 核心库只定义
- 日志框架扩展(如 SLF4J):
- SLF4J 定义日志接口,logback/log4j 作为实现;
- 引入 logback 依赖后,
ServiceLoader自动加载其实现类,程序无需修改日志调用代码。
- 框架扩展(如 Dubbo/SpringBoot):
- Dubbo 通过
ServiceLoader加载协议、序列化器等扩展; - SpringBoot 的自动配置部分也依赖 SPI 机制加载第三方 Starter。
- Dubbo 通过
JDBC驱动加载场景
在JDK中定义了数据库驱动的接口规范java.sql.Driver,常见的数据库驱动厂商分别有不同的实现,如Mysql的驱动常用的有com.mysql:mysql-connector-j或者org.mariadb.jdbc:mariadb-java-client分别实现了java.sql.Driver
并在他们的jar中提供了基于SPI规范的META-INF
|---mysql-connector-j
|---com.mysql...
|---META-INF
|---services
|---java.sql.Driver ## 内容为 com.mysql.cj.jdbc.Driver
|---mariadb-java-client
|---org.mariadb...
|---META-INF
|---services
|---java.sql.Driver ## 内容为 org.mariadb.jdbc.Driver
在实际使用中,一些驱动管理组件会调用ServiceLoader.load(Driver.class)去加载这些遵循SPI规范的驱动实例。如所有的java原生的java.sql.DriverManager中会调用确保数据库驱动加载,而Driver的实现类中都有静态代码块注册数据库驱动
/** MariaDB Driver */
public final class Driver implements java.sql.Driver {
// 使用静态代码自动注册驱动
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException e) {
// eat
}
}
}
public class DriverManager {
//...
// 获取数据库驱动前确保驱动已经被加载
private static void ensureDriversInitialized() {
//...
/**
* 基于SPI协议使用ServiceLoad加载所有的数据库驱动
* 适配java安全模式启动,此处加载数据库驱动需要使用AccessController临时提升权限
*/
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 加载所有的数据库驱动
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;
}
});
}
}
ServiceLoader 核心价值
-
解耦接口与实现,降低代码耦合
-
服务接口(如
java.sql.Driver)由核心框架 / 模块定义,实现类(如 MySQL/Oracle 驱动)由第三方提供; -
核心代码仅依赖接口,不直接引用实现类,避免了 “硬编码 new 实现类” 的耦合问题,修改 / 替换实现无需改动核心代码。
-
-
动态加载实现类(运行时扩展)
ServiceLoader会自动扫描类路径下的特定配置文件,加载所有符合规范的实现类,无需手动管理类加载:-
配置文件路径:
META-INF/services/[服务接口全限定名]; -
配置文件内容:每行写一个实现类的全限定名(支持多个实现)。
-
-
支持可插拔的扩展机制
这是
ServiceLoader最核心的价值:第三方只需遵循 SPI 规范实现接口、配置文件,无需修改原有程序,即可无缝接入扩展。
- 点赞
- 收藏
- 关注作者
评论(0)