Java 里面的SPI

今天在看web.xml为什么逐渐淡出我们的视野,重温了下JDK的SPI机制(Service Provider Interface)。

SPI 是什么?

面向接口编程+配置文件!
对,我总结的就是一句话。看看我们日常使用不同数据库驱动,不同日志框架,是不是都在使用SPI机制呢?下面我用数据库驱动来举例。

JDBC驱动使用SPI

一、概述

我们都知道数据库驱动 java.sql.Driver,针对不同数据库厂商有不同的实现,翻翻我们mysql驱动jar包,依次找到META-INF/services/java.sql.Driver,看看里面有啥?

1
2
3
# mysql 驱动实现类
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

我们看看Driver类的static方法

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
// 注册
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {
Class.forName().newInstance()
}
}

二、加载驱动,获取连接

注册Driver到DriverManager -> DriverManager.getConnection(…) 获取连接

新(SPI机制)、老(反射)2种方式

1、新方式,SPI机制,省略Class.forName(“xxx.xxx.xxx”)方式(jar包下所需文件如概述描述)

驱动从 5.x 开始jar包下必须要有 META-INF/services/java.sql.Driver。

1
2
3
// 会调用DriverManager的static方法,里面会寻找驱动jar包下面META-INF/services/java.sql.Driver 文件
// 初始化Driver,当然也就调用了Driver的static方法
Connection connection = DriverManager.getConnection(URL, username, pwd);
2、老方式,通过Class.forName(“xxx.xxx.xxx”)反射

驱动4.x一般这样来写

1
2
3
4
// 反射,会调用Driver里static方法,注册Driver到DriverManager
Class.forName("com.mysql.jdbc.Driver");
// 从DriverManager 里面获取连接
Connection connection = DriverManager.getConnection(URL, username, pwd);

2种方式讲解

  • 新的
1
2
3
4
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}

ServiceLoader实现spi机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//查找mysql驱动jar下META-INF/services/java.sql.Driver
//初始化它,调用 static方法,注册驱动(同下分析)
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}

后续流程同下面老的讲解

  • 老的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//Driver里static方法
static {
try {
//向DriverManager 注册Driver
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
// registerDriver 方法
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {

registerDriver(driver, null);
}

// 最终方法,注册Driver到DriverManager
public static synchronized void registerDriver(java.sql.Driver driver,DriverAction da)
throws SQLException {
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
// 获取连接
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
//最终调用
return (getConnection(url, info, Reflection.getCallerClass()));
}

private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
SQLException reason = null;
// 从registeredDrivers获取连接信息
for(DriverInfo aDriver : registeredDrivers) {
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
// 获取连接
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}

我们可以看到SPI机制核心在于:ServiceLoader、META-INF/services/需要实现接口全定限名 文件。ServiceLoader位于java.util包下,其load方法就是实现SPI的关键。有兴趣可以自己实现个SPI,试验下!