從jdbc4.0 不再需要再寫Class.forName("")到SPI技術
阿新 • • 發佈:2020-11-28
一開始學習JDBC的時候到現在一直認為連線資料庫之前首先需要
Class.forName("com.mysql.jdbc.Driver");
直到某一天至少是在寫這篇部落格之前。為什麼註釋掉了還是可以連線資料庫?這個問題先等一等看。
先回憶下例項化物件的幾種方式
//1.直接new Hello hello = new Hello(); hello.say(); //2.Class.forName.newInstance Hello hello2 = (Hello) Class.forName("com.mySpi.Hello").newInstance(); hello2.say(); //3.需實現Serializable, 運用反序列化手段,呼叫java.io.ObjectInputStream物件的 readObject()方法。
繼續我們問題,既然不需要顯示載入,那肯定是有其他的類替我們載入了,首先看DriverManager,因為除了main方法所在的類,最先調的只有他了,再找下該類下面有沒有靜態程式碼塊,因為它是最先被jvm載入的,然後我們會發現
/** * Load the initial JDBC drivers by checking the System property * jdbc.properties and then use the {@code ServiceLoader} mechanism */ static { loadInitialDrivers(); println("JDBC DriverManager initialized"); }
然後執行loadInitialDrivers();程式碼如下
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; } // If the driver is packaged as a Service Provider, load it. 如果驅動是作為服務提供,就去載入 // Get all the drivers through the classloader 通過classloader 獲取所有的驅動 // exposed as a java.sql.Driver.class service. Driver介面的有實現類作為服務 // ServiceLoader.load() replaces the sun.misc.Providers() 載入實現類 AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { 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; } }); 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); } } }
在loadInitialDrivers()方法中,我們發現通過ServiceLoader機制載入java.sql.Driver介面的實現類,然後對所有實現類進行遍歷,來完成了驅動類的載入。
那麼問題來了,jdk是如何知道java.sql.Driver有哪些實現類的呢?
主角登場:
SPI,官方話是JDK內建的一種服務提供發現機制。比如有一個介面,想在執行時動態地給它新增實現,只需要按照SPI的規矩來辦,那麼SPI機制在程式執行時就會發現該實現類;規則如下:
在classpath下META-INF/services 新建一以介面名(如java.sql.Driver)為名稱的檔案,檔案內容是介面實現類(如mysql驅動包下META-INF/services/java.sql.Driver裡的com.mysql.jdbc.Driver),mysql-connect--的示例檔案如下
所以不用顯示Class.forName的原因是:核心ServiceLoader類提供了一個靜態的load()方法,用於載入指定介面的所有實現類。呼叫該方法後,classpath下META-INF/services目錄的java.sql.Driver檔案中指定的所有實現類都會被載入。
現在我們知道spi機制的核心方法,可以寫一個demo 實現下這種情況
首先新建SayHello介面,然後新建它的兩個實現類Hello,Hi,程式碼如下
public interface SayHello {
void say();
}
public class Hello implements SayHello{
@Override
public void say() {
System.out.println("hello world--hello");
}
}
public class Hi implements SayHello {
@Override
public void say() {
System.out.println("hello world --hi");
}
}
首先在resource檔案下新建META-INF資料夾,然後META-INF下新建services資料夾,該資料夾下新建com.mySpi.SayHello檔案,內容如下
下面我們寫一個main 方法測試是否可以載入實現類,如果已經載入,就可以列印
public static void main(String[] args) {
ServiceLoader<SayHello> sayHellos = ServiceLoader.load(SayHello.class);
Iterator<SayHello> driversIterator = sayHellos.iterator();
while (driversIterator.hasNext()){
SayHello hello=driversIterator.next();
hello.say();
}
}
如果com.mySpi.SayHello裡內容,那麼將無法列印hello-world,SPI簡單入門就到這裡了。