介绍
SPI是一种服务发现机制,允许jdk从ClassPath:META-INF/services查找接口实现类实例化到内存中。
非SPI的加载方式则需要用户在代码中指定接口实现类并进行实例化。
为何要用SPI?
实际上传统的服务加载方式从代码量来说并不会比SPI多上多少,但从java设计上考虑,传统的加载方式是一种硬编码的编程方式,而SPI设计的初衷就是将服务的加载从代码中剥离出去使得我们编程的时候,不需要考虑服务的选择,直接面向接口编程。
我们以mysql驱动的加载来演示加载方式的区别
//传统加载方式 1
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:33061/xxx?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull", "root", "123456");
//传统加载方式 2
System.setProperty("jdbc.drivers","com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:33061/xxx?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull", "root", "123456");
//SPI加载方式
Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:33061/xxx?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull", "root", "123456");
强调
两种加载方式没有谁对谁错,编程思想仁者见仁智者见智,如果不能理解设计者的想法,请记住它。
SPI配置文件加载函数介绍
举个例子:
//摘自源码
//大体看了下load方法对ServiceLoader类的类加载器、上下文等变量进行初始化,并且校验了Driver.class,实例化了一个私有迭代器LazyIterator对象保存需要加载的服务接口,并未做任何查找操作或者实例化操作
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
while(driversIterator.hasNext()) {
driversIterator.next();
}
这段代码会在classpath路径下的META-INF/services目录找到文件名为java.sql.Driver的文本并读取其中的内容(每一行都是一个实现类的权限命名),挨个实例化其实现类 如图:
(lib目录在classpath下)
浅析SPI机制下数据库驱动加载过程(以mysql为范例)
DriverManager有一个初始化的静态块,这会在执行getConnection前运行
static {
//初始化驱动
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
查看loadInitialDrivers()
//以下代码删除源码注释减少篇幅
private static void loadInitialDrivers() {
//这里drivers的获取对应我提到的 传统加载方式 2
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drive