Java 类加载篇(三)SPI 动态加载机制总结
Java SPI
SPI 全称 Service Provider Interface,Java 1.6 引入,是 Java 在语言层面为我们提供了一种方便地创建可扩展应用的途径。SPI 提供了一种 JVM 级别的服务发现机制,我们只需要按照 SPI 的要求,在 jar 包中进行适当的配置,JVM 就会在运行时通过懒加载,帮我们找到所需的服务并加载。如果我们一直不使用某个服务,那么它不会被加载,一定程度上避免了资源的浪费。
整体机制图如下:
使用例子
以 JDBC 为例,标准服务接口为 com.mysql.jdbc.Driver
。
MySQL 作为服务提供方,以 mysql-connector-java 5.1.44 为例,按规范要求其 META-INF/services/java.sql.Driver
配置文件中声明了两个实现类,如下:
1 | com.mysql.jdbc.Driver |
当类加载器载入 java.sql.DriverManager
类时,会执行其静态代码块,从而执行其中的 SPI 代码加载 JDBC Driver 实现,源码如下,详见:《Java 数据持久化系列(一)JDBC Driver 驱动程序总结》
1 | ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); |
流程如下:
源码解析
ServiceLoader
的结构如下:
其成员变量如下:
1 | public final class ServiceLoader<S> |
SPI 的核心在于内部类 LazyIterator
,承担了以下职责:
- 加载配置文件,解析、验证其内容
- 加载类
- 反射构造实例
核心源码及注释如下:
1 | // Private inner class implementing fully-lazy provider lookup |
使用场景
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。这种动态加载机制的使用场景如下:
- JDBC Driver 驱动程序管理类
java.sql.DriverManager
。详见:JDBC Driver 驱动程序总结 - JSR-303 Bean Validation 的
javax.validation.Validation
- 日志门面接口实现类加载,SLF4J 加载不同提供商的日志实现类。
- Spring
- 对 servlet3.0 规范对
ServletContainerInitializer
的实现 - 自动类型转换 Type Conversion SPI (Converter SPI、Formatter SPI) 等
- Spring Factories 机制(
SpringFactoriesLoader
)
- 对 servlet3.0 规范对
- Dubbo 通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。详见:http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html
对比总结
下面总结下这几个加载类:
- Java
java.util.ServiceLoader
- Spring
org.springframework.core.io.support.SpringFactoriesLoader
- Dubbo
com.alibaba.dubbo.common.extension.ExtensionLoader
Java SPI | Spring Factories | Dubbo SPI | |
---|---|---|---|
加载类 | ServiceLoader |
SpringFactoriesLoader |
ExtensionLoader |
加载文件 | META-INF/services/接口全限定名 |
META-INF/spring.factories |
META-INF/dubbo |
文件内容 | 接口实现类,多值以换行分隔 | 通过键值对方式(key=value)配置,多值以逗号分隔 | 通过键值对方式(key=value)配置,支持按需加载接口实现类 |
接口注解 | / | / | @SPI |