框架源码专题-JDK-1、SPI
1. 使用方法
1.1. 约定
Java SPI 就是这样做的,约定在 Classpath 下的 META-INF/services/ 目录里创建一个 以服务接口命名的文件,然后 **文件里面记录的是此 jar 包提供的具体实现类的全限定名**。
这样当我们引用了某个 jar 包的时候就可以去找这个 jar 包的 META-INF/services/ 目录,再根据接口名找到文件,然后读取文件里面的内容去进行实现类的加载与实例化。
1.2. 示例
1.2.1. DriverManager-MySQL
比如我们看下 MySQL 是怎么做的。
再来看一下文件里面的内容。
1.2.2. SpringMVC
Spring-4、SpringMVC2. 源码原理
以下源码分析基于 JDK8
2.1. Java SPI 入口-ServiceLoader
ServiceLoader.load()
2.2. 运行逻辑
在 ServiceLoader.load() 方法中,首先会尝试获取当前使用的 ClassLoader(获取当前线程绑定的 ClassLoader,查找失败后使用 SystemClassLoader),然后调用 reload() 方法,调用关系如下图所示:
在 reload() 方法中,首先会清理 providers 缓存(LinkedHashMap 类型的集合),==该缓存用来记录 ServiceLoader 创建的实现对象==,其中 Key 为实现类的完整类名,Value 为实现类的对象。之后创建 LazyIterator
迭代器,用于读取 SPI 配置文件并实例化实现类对象。
在前面的示例中,main() 方法中使用的迭代器底层就是调用了 ServiceLoader.LazyIterator 实现的。Iterator 接口有两个关键方法:hasNext() 方法和 next() 方法。这里的 LazyIterator 中的 next() 方法最终调用的是其 nextService() 方法,hasNext() 方法最终调用的是 hasNextService() 方法,调用关系如下图所示:
2.2.1. hasNextService- 查找
首先来看 LazyIterator.hasNextService() 方法,该方法主要 负责查找 META-INF/services 目录下的 SPI 配置文件,并进行遍历。
2.2.2. nextService- 实例化
在 hasNextService() 方法中完成 SPI 配置文件的解析之后,再来看 LazyIterator.nextService() 方法,该方法 负责实例化 hasNextService() 方法读取到的实现类,其中会将实例化的对象放到 providers 集合中缓存起来
2.3. 触发逻辑
2.3.1. DriverManager
静态代码块中的 loadInitialDrivers
方法中或者新版本的 ensureDriversInitialized
方法中,调用 ServiceLoader.load(Driver.class)
3. 存在问题
相信大家一眼就能看出来,Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且 将实现类全部实例化,假设一个实现类初始化过程比较消耗资源且耗时,但是你的代码里面又用不上它,这就产生了资源的浪费。
所以说 Java SPI 无法按需加载实现类。
4. Java 中的 SPI
4.1. SpringMVC
Spring-4、SpringMVC4.2. DubboSPI
分布式专题-14、Dubbo-SPI4.3. SpringBoot
SpringBoot-1、基本原理5. 实战经验
6. 参考与感谢
6.1. 源码原理
[[03 Dubbo SPI 精析,接口实现两极反转(上).md]]
https://juejin.cn/post/6872138926216511501#heading-2