SPI : Service Provider Interface

1.概述

jdk6中引入了一个新特性,使得我们可以根据一个指定的「接口」去找到并加载指定的「实现」。
本篇文章会详细介绍下SPI的用法及场景。

2.关键组成

java.sql.Driver为例

Service

一个众所周知的「接口」。比如说jdk包中的java.sql.Driver,,这就是JDBC的接口定义。它没有具体的实现,具体的实现由不同的厂商自己提供,例如MySQL的com.mysql.jdbc.Driver、Oracle的oracle.jdbc.driver.OracleDriver

Service Provider

SPI 的特定实现。服务提供者包含一个或多个实现或扩展服务类型的具体类。

服务提供者通过配置文件进行配置和标识,将该文件放在资源目录 META-INF/services 中。文件名是 SPI 的完全限定名,其内容是 SPI 实现的完全限定名。
mysql驱动

Service Provider 是一个 jar 文件,我们将其放置在应用程序类路径、 Java 扩展类路径或用户定义的类路径中。

来看下mysql-connector-java是怎么做的在这里插入图片描述
在这里插入图片描述
可以看到Driver继承了NonRegisteringDriver并且实现了jdk提供的java.sql.Driver接口。NonRegisteringDriver本身也实现了jdk提供的java.sql.Driver接口。

Driver中的静态域部分只做了一件事情,那就看下DriverManager是什么。

DriverManagerjava.sql.Driver接口一样,也是JDK提供的。从命名就可以看出,这个类的作用就是管理所有能加载到的java.sql.Driver接口实现。

DriverManager中有这么一段代码:

static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

loadInitialDrivers()的实现中比较长,截取一段我们关注的

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();

总结

  1. JDK提供了JDBCjava.sql.Driver接口
  2. mysql提供对java.sql.Driver的实现com.mysql.jdbc.Driver
  3. mysqlcom.mysql.jdbc.Driver被实例化时,使用JDK提供的DriverManager做注册
  4. DriverManager实例化时,通过JDKServiceLoader扫描、加载java.sql.Driver的实现类

ServiceLoader

SPI 的核心是 ServiceLoader 类。
它具有延迟发现和加载实现的作用。
它使用上下文类路径来定位Service Provider的实现并将它们放入内部缓存中。

其代码中定义了扫描包的路径:

private static final String PREFIX = "META-INF/services/";

3.打破双亲委派

我们知道jdk的核心API(e.g rt.jar)是BootstrapClassLoader加载的,三方提供的jar包是AppClassLoader加载的,那么ServiceLoader是rt.jar中的类,那么对应的加载器就是BootstrapClassLoader

那么问题来了。

如果一个类由类加载器加载,那么这个类依赖的类也是由相同的类加载器加载的。

很显然,ServiceLoader这里打破了双亲委派机制
涉及到源码实现较多,和本篇主题关系不大,留到下篇文章再研究。

下班!
在这里插入图片描述

### 服务提供者接口(SPI)插件机制详解 Service Provider InterfaceSPI)是一种用于服务发现的机制,允许在运行时动态加载实现特定接口的类。这种机制为框架或库的扩展点提供了实现方式,使得开发者能够在运行时动态插入或更换组件的实现[^1]。 Java 提供了内置的 `ServiceLoader` 类来支持 SPI 机制。通过定义一个接口,并由多个模块或库实现该接口,系统可以在运行时自动识别并加载这些实现。这种方式广泛应用于 JDBC 驱动、日志框架、网络协议栈等场景中。 #### SPI 的工作原理 - **接口定义**:首先定义一个服务接口,例如音频格式解析器接口。 - **实现类**:不同的库或模块可以提供该接口的具体实现,如 MP3 解码器、FLAC 解码器等。 - **配置文件声明**:在 `META-INF/services/` 目录下创建一个以接口全限定名为名称的文件,并在其中列出所有实现类的全限定名。 - **动态加载**:使用 `ServiceLoader.load()` 方法加载接口的所有实现,并按需调用。 示例代码如下: ```java public interface AudioDecoder { boolean canDecode(String format); void decode(InputStream input); } ``` 实现类: ```java public class Mp3Decoder implements AudioDecoder { @Override public boolean canDecode(String format) { return "mp3".equalsIgnoreCase(format); } @Override public void decode(InputStream input) { // 实现 MP3 解码逻辑 } } ``` 配置文件 `META-INF/services/com.example.AudioDecoder` 内容: ``` com.example.Mp3Decoder ``` 然后在程序中加载: ```java ServiceLoader<AudioDecoder> decoders = ServiceLoader.load(AudioDecoder.class); for (AudioDecoder decoder : decoders) { if (decoder.canDecode("mp3")) { decoder.decode(inputStream); } } ``` 此机制允许在不修改核心代码的前提下,动态添加新的音频解码器[^2]。 #### 常见问题与解决方案 1. **无法加载实现类** 可能原因包括配置文件路径错误、实现类未正确声明、类路径缺失等。应检查 `META-INF/services/` 下的文件是否完整,并确保类路径包含所有必要的 JAR 包。 2. **多个实现冲突** 当多个实现类同时满足条件时,可能会导致不可预知的行为。可以通过优先级注解、配置项或自定义排序策略来解决此类问题。 3. **性能问题** `ServiceLoader` 在首次加载时会扫描整个类路径,可能导致启动延迟。可以通过懒加载或缓存机制优化性能。 4. **兼容性限制** 不同版本的 JDK 对 SPI 的支持略有差异。某些框架如 Spring 和 Dubbo 提供了自己的 SPI 实现,以增强灵活性和可扩展性[^2]。 5. **安全限制** 在某些受限环境中(如 Applet 或 OSGi),SPI 加载可能受到安全管理器的限制。需要确保相应的权限被正确授予。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高级摸鱼工程师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值