Java 雙親委派模型
- ClassLoader 及 雙親委派模型
- 基礎知識介紹
2.1. SPI(Service Provider Interface)
2.2. ServiceLoader
2.3. Thread Context ClassLoader - JDBC Driver 註冊方式 區別和原理
Java中的ClassLoader
ClassLoader | 編程語言 | 加載內容 | Parent ClassLoader | |
---|---|---|---|---|
BootstrapClassLoader | C++ | jre/lib;jre/classes(包括 但不僅限於) | Null | Java虛擬機啓動後初始化 |
ExtClassLoader | Java | jre/lib/ext;java.ext.dirs(包括 但不僅限於) | BootstrapClassLoader | |
AppClassLoader | Java | classpath指定位置 | ExtClassLoader | ClassLoader.getSystemClassLoader 返回值 |
Java默認ClassLoader的基本模型:
基礎知識介紹
SPI(Service Provider Interface)
SPI機制簡介
SPI的全名爲Service Provider Interface,主要是應用於廠商自定義組件或插件中。在java.util.ServiceLoader的文檔裏有比較詳細的介紹。簡單的總結下java SPI機制的思想:我們系統裏抽象的各個模塊,往往有很多不同的實現方案,比如日誌模塊、xml解析模塊、jdbc模塊等方案。面向的對象的設計裏,我們一般推薦模塊之間基於接口編程,模塊之間不對實現類進行硬編碼。一旦代碼裏涉及具體的實現類,就違反了可拔插的原則,如果需要替換一種實現,就需要修改代碼。爲了實現在模塊裝配的時候能不在程序裏動態指明,這就需要一種服務發現機制。 Java SPI就是提供這樣的一個機制:爲某個接口尋找服務實現的機制。有點類似IOC的思想,就是將裝配的控制權移到程序之外,在模塊化設計中這個機制尤其重要。
SPI具體約定
Java SPI的具體約定爲:當服務的提供者提供了服務接口的一種實現之後,在jar包的META-INF/services/目錄裏同時創建一個以服務接口命名的文件。該文件裏就是實現該服務接口的具體實現類。而當外部程序裝配這個模塊的時候,就能通過該jar包META-INF/services/裏的配置文件找到具體的實現類名,並裝載實例化,完成模塊的注入。基於這樣一個約定就能很好的找到服務接口的實現類,而不需要再代碼裏制定。jdk提供服務實現查找的一個工具類:java.util.ServiceLoader。
載錄:https://blog.csdn.net/sigangjun/article/details/79071850
相關請查閱ServiceLoader
Thread Context ClassLoader
說明:獲取ClassLoader的方法
方法 | 返回值 | |
---|---|---|
類對象獲取 | this.getClass.getClassLoader | 加載該Class的ClassLoader |
ClassLoder的靜態方法 | ClassLoader.getSystemClassLoader | AppClassLoader |
提出問題:擴展類獲取底層ClassLoader,只需要通過獲取自身加載所用的ClassLoader,然後通過parent即可慢慢找到;但是底層對象(如java.lang.String)想要獲取外層ClassLoader(如AppClassLoader或者自定義ClassLoader)該如何實現?
對於AppClassLoader來說,可以通過ClassLoader.getSystemClassLoader來獲取,但是自定義ClassLoader就不能這樣獲取了。
爲了解決這個問題,JDK開發者們提供了一個方式,即在Thread中保存一個ClassLoader的引用 – contextClassLoader。即若底層對象想要獲取外層ClassLoader,只需在外層首先將ClassLoader設置至Thread中,然後底層對象在從中獲取出該ClassLoader即可(這種方式,被評價爲對“雙親委派模型”的破壞,不過 也是無奈之舉)。
Thread 中的 contextClassLoader,默認情況下爲AppClassLoader
在Thread 創建過程中會繼承父Thread的 contextClassLoaderprivate void init(....) { Thread parent = currentThread(); this.contextClassLoader = parent.getContextClassLoader(); }
(Thread 常常被作爲一種共享存儲|通信媒介)
JDBC Driver 註冊方式
- new com.mysql.jdbc.Driver();
- Class.forName(“com.mysql.jdbc.Driver”);
- java.sql.DriverManager.getConnection(“jdbc:mysql://…”);
從實踐可以發現,不論是採用第一種方式還是第二種方式,都不會在意對應的返回值。
那麼爲何還需要調用執行這段邏輯呢? – 用於執行com.mysql.jdbc.Driver中的靜態代碼塊
// from com.mysql.jdbc.Driver
static {
// 在com.mysql.jdbc包中直接調用java.sql包
// 兩個的ClassLoader是不同的
// com.mysql.jdbc -- AppClassLoader(正常情況)
// java.sql -- BootstrapClassLoader
java.sql.DriverManager.registerDriver(new Driver());
}
// from java.sql.DriverManager
public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da)
throws SQLException {
// 將以註冊driver信息進行管理
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
}
盡然第一種和第二種方式的目的都是爲了:執行com.mysql.jdbc.Driver中的靜態代碼塊,那麼第三種方式呢?沒有顯示調用方法來觸發com.mysql.jdbc.Driver的靜態初始邏輯,那能否成功獲取到mysql連接?
在JDBC4.0的升級之後,是可以無需進行顯示觸發 com.mysql.jdbc.Driver的靜態初始邏輯 的。
// from java.sql.DriverManager
static { loadInitialDrivers(); }
private static void loadInitialDrivers() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 通過ServiceLoader獲取配置實現類
// 通過迭代返回列表,來load配置實現類,以此來觸發Driver的靜態邏輯(註冊邏輯)
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
while(driversIterator.hasNext()) { driversIterator.next(); }
return null;
}
});
// 通過-D參數指定的jdbc.drivers屬性來load對應Driver,以此來觸發Driver的靜態邏輯
String drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() { return System.getProperty("jdbc.drivers"); }
});
for (String aDriver : drivers.split(":"))
{ Class.forName(aDriver, true, ClassLoader.getSystemClassLoader()); }
}
通過上面的代碼展示,可以發現,目前的以無需手動註冊Driver,這件事情已經由DriverManager統一處理了,但是 前提是這些Driver都按照規範註冊至META-INF/services/java.sql.Driver文件中了
注意實現
現在來說一下其中使用到的Thread ContextClassLoader:
簡單分析一下,在java.sql.DriverManager中,通過讀取META-INF/services/java.sql.Driver文件,來獲取註冊的Driver類全限定名稱,但是 它該如何去加載這個Driver?
使用自身的ClassLoader – BootstrapClassLoader?這顯然是行不通的,因爲雙親委派的設計,BootstrapClassLoader是獲取不到com.mysql.jdbc.Driver的。
這是就Thread 中的contextClassLoader,而這個ClassLoader通常情況下,就是AppClassLoader,正好可以load到com.mysql.jdbc.Driver。完美(雖然破壞了雙親委派模型),以後不需要顯示寫上Class.forName(“com.mysql.jdbc.Driver”),代碼將更加整潔。