JDBC使用SPI機制解析


SPI機制為很多框架的擴展提供了可能,其實JDBC就應用到了這一機制。回憶一下JDBC獲取數據庫連接的過程。在早期版本中,需要先設置數據庫驅動的連接,再通過DriverManager.getConnection獲取一個Connection。

String url = "jdbc:mysql:///consult?serverTimezone=UTC"; String user = "root"; String password = "root"; Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager.getConnection(url, user, password); 

在較新版本中(具體哪個版本,筆者沒有驗證),設置數據庫驅動連接,這一步驟就不再需要,那么它是怎么分辨是哪種數據庫的呢?答案就在SPI。

1、加載

我們把目光回到DriverManager類,它在靜態代碼塊里面做了一件比較重要的事。很明顯,它已經通過SPI機制, 把數據庫驅動連接初始化了。

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

具體過程還得看loadInitialDrivers,它在里面查找的是Driver接口的服務類,所以它的文件路徑就是:META-INF/services/java.sql.Driver。

public class DriverManager { private static void loadInitialDrivers() { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { //很明顯,它要加載Driver接口的服務類,Driver接口的包為:java.sql.Driver //所以它要找的就是META-INF/services/java.sql.Driver文件 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator = loadedDrivers.iterator(); try{ //查到之后創建對象 while(driversIterator.hasNext()) { driversIterator.next(); } } catch(Throwable t) { // Do nothing } return null; } }); } } 

那么,這個文件哪里有呢?我們來看MySQL的jar包,就是這個文件,文件內容為:com.mysql.cj.jdbc.Driver

 
MySQL SPI文件

 

2、創建實例

上一步已經找到了MySQL中的com.mysql.cj.jdbc.Driver全限定類名,當調用next方法時,就會創建這個類的實例。它就完成了一件事,向DriverManager注冊自身的實例。

public class Driver extends NonRegisteringDriver implements java.sql.Driver { static { try { //注冊 //調用DriverManager類的注冊方法 //往registeredDrivers集合中加入實例 java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } public Driver() throws SQLException { // Required for Class.forName().newInstance() } } 

3、創建Connection

在DriverManager.getConnection()方法就是創建連接的地方,它通過循環已注冊的數據庫驅動程序,調用其connect方法,獲取連接並返回。

private static Connection getConnection( String url, java.util.Properties info, Class<?> caller) throws SQLException { //registeredDrivers中就包含com.mysql.cj.jdbc.Driver實例 for(DriverInfo aDriver : registeredDrivers) { if(isDriverAllowed(aDriver.driver, callerCL)) { try { //調用connect方法創建連接 Connection con = aDriver.driver.connect(url, info); if (con != null) { return (con); } }catch (SQLException ex) { if (reason == null) { reason = ex; } } } else { println(" skipping: " + aDriver.getClass().getName()); } } } 

4、再擴展

既然我們知道JDBC是這樣創建數據庫連接的,我們能不能再擴展一下呢?如果我們自己也創建一個java.sql.Driver文件,自定義實現類MyDriver,那么,在獲取連接的前后就可以動態修改一些信息。

還是先在項目ClassPath下創建文件,文件內容為自定義驅動類com.viewscenes.netsupervisor.spi.MyDriver

 
自定義數據庫驅動程序

我們的MyDriver實現類,繼承自MySQL中的NonRegisteringDriver,還要實現java.sql.Driver接口。這樣,在調用connect方法的時候,就會調用到此類,但實際創建的過程還靠MySQL完成。

package com.viewscenes.netsupervisor.spi public class MyDriver extends NonRegisteringDriver implements Driver{ static { try { java.sql.DriverManager.registerDriver(new MyDriver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } public MyDriver()throws SQLException {} public Connection connect(String url, Properties info) throws SQLException { System.out.println("准備創建數據庫連接.url:"+url); System.out.println("JDBC配置信息:"+info); info.setProperty("user", "root"); Connection connection = super.connect(url, info); System.out.println("數據庫連接創建完成!"+connection.toString()); return connection; } } --------------------輸出結果--------------------- 准備創建數據庫連接.url:jdbc:mysql:///consult?serverTimezone=UTC JDBC配置信息:{user=root, password=root} 數據庫連接創建完成!com.mysql.cj.jdbc.ConnectionImpl@7cf10a6f

原文鏈接:https://www.jianshu.com/p/3a3edbcd8f24


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM