DriverManager探祕

語言: CN / TW / HK

最近在使用Flink sql gateway和Flink jdbc driver開發類似Zeppelin的開發平臺,在使用flink jdbc driver的時候,對DriverManager的用法產生了疑問。

為什麼輸入url就能找到對應的Driver實現類呢?

先來看看官方的示例程式碼:

Connection connection = DriverManager.getConnection("jdbc:flink://localhost:8083?planner=blink");
Statement statement = connection.createStatement();
ResultSet rs = statement.executeQuery("SELECT * FROM T");
while (rs.next()) {
System.out.println(rs.getInt(1) + ", " + rs.getString(2));
}
statement.close();
connection.close();

在註冊Driver的時候,只需要新增對應的依賴jar,無需任何註冊就可以直接使用Driver。這其中到底是如何做到的呢?

1 總覽

先放一張圖便於理解,後面的描述都會按照這個圖展開。

2 詳細流程

先來看看DriverManager的內部,有一段靜態程式碼塊。

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

這裡有個知識點,就是static的執行順序,其實是在構造方法之前。

 1. 初始化static變數
2. 執行static程式碼塊
3. 初始化非static變數
4. 父類無參建構函式
5. 子類建構函式

這樣只要我們第一次使用DriverManager類,就會觸發loadInitialDrivers。

在這個方法裡面:

private static void loadInitialDrivers() {
...
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 使用SPI載入類
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
...
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
}
...

上面的方法就是DriverManager的核心,使用到的是Java裡面一種叫做SPI的技術,即Service Provider Interface 服務發現機制。這種方法通常都用在第三方服務擴充套件上,比如我們開發了一個主程式,並定義對應的介面。那麼第三方可以以外掛的形式,直接提供實現類的jar包,主程式就可以自動的發現這些實現類。

具體的使用方法如下:

  • 在resources下的META-INF/services建立介面描述檔案,檔名是介面的包+類名,裡面的內容是實現類的包+類名

  • 使用ServiceLoader.load(介面類),得到的迭代器就包含具體的實現類。

比如Flink的jdbc driver包下,就有這樣的描述檔案:

mysql中也有這樣的檔案:

知道了服務發現,就可以看看具體的Driver裡面都做了什麼。

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}


static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}

原來是在程式碼塊中把Driver物件,註冊到了DriverManager中。經過註冊後,DriverManager的registeredDrivers物件就包含了需要的Driver。因此當執行

Connection connection = DriverManager.getConnection("jdbc:flink://172.16.3.11:8083?planner=blink");

時,會觸發DriverManager內部的getConnection方法。在getConnection中,在connect()方法中通過對比註冊的Driver物件和url字首匹配,從而返回對應的連線物件。

for(DriverInfo aDriver : registeredDrivers) {
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
return (con);
}
}
}
}

3 總結

總結來說,整體流程如下:

1 通過使用DriverManager觸發靜態程式碼塊,從而觸發初始化

2 通過SPI服務發現,掃描到所有的Driver介面實現類,即各個驅動

3 遍歷可用的驅動類,觸發驅動類靜態程式碼塊執行,完成註冊。

4 呼叫getConnection獲取連線,內部遍歷所有可用的驅動類

5 校驗驅動合法性(url與驅動匹配),建立並返回Connection物件

4 往期推薦