Java 雙親委派模型

Java 雙親委派模型

  1. ClassLoader 及 雙親委派模型
  2. 基礎知識介紹
    2.1. SPI(Service Provider Interface)
    2.2. ServiceLoader
    2.3. Thread Context ClassLoader
  3. 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的基本模型:

Java默認ClassLoader委派模型
載錄:https://www.cnblogs.com/doit8791/p/5820037.html


基礎知識介紹

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的 contextClassLoader

private void init(....) {
	Thread parent = currentThread();
	this.contextClassLoader = parent.getContextClassLoader();
}

(Thread 常常被作爲一種共享存儲|通信媒介)


JDBC Driver 註冊方式

  1. new com.mysql.jdbc.Driver();
  2. Class.forName(“com.mysql.jdbc.Driver”);
  3. 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”),代碼將更加整潔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章