聲明:本人寫的這些,只是當時學習這些知識的理解,不代表是正確的,只是為了鞏固記憶,好記憶不如爛筆頭,如果哪里有問題,請指出,不喜勿噴,謝謝。
1. 什么是全盤負責委托機制
每個類都有自己的類加載器,那么負責加載當前類的類加載器也會去加載當前類中引用的其他類,前提是引用的類沒有被加載過。
例如ClassA中有個變量 ClassB,那么加載ClassA的類加載器會去加載ClassB,如果找不到ClassB,則異常。
2. 為什么需要有線程上下文類加載器
jvm推薦我們使用雙親委托機制,主要是保證了相同的類不會被重復加載。但是,在jdk1.2之后,提出了線程上下文類加載器的概念,目的是為了打破雙親委托機制,因為在某些場景下(例如:JNDI,JDBC....)等等SPI場景中,關於什么是SPI(服務發現接口),可以參考之前寫的文檔資料(https://www.cnblogs.com/duguxiaobiao/p/12183135.html),使用雙親委托機制無法實現,那么為什么無法實現呢?
2.1 使用JDBC的例子,分析為什么雙親委托機制不能實現要求
原生的JDBC的使用,獲取數據庫連接使用的是 Connection conn = DriverManager.getConnection(xx,xx,xx);很明顯,Connection是jdk提供的接口,具體的實現是我們的廠商例如mysql 實現,加入到項目中,那么設想一下,DriverManager.getConnection(xx,xx,xx);該方法肯定是使用的mysql的jar包,返回了mysql實現的Connection對象,那么加載DriverManager類是由啟動類加載器加載,根據上面的全盤負責委托機制來說,啟動類加載器會去加載MySql的jar包,很明顯,找不到。所以使用雙親委托機制來說,無法實現該SPI場景的需求。
2.2 線程上下文類加載器的作用
雙親委托機制:子加載器對應的命名空間包含了父加載器,所以可以實現子容器訪問父容器
線程上下文類加載器:使用該類加載器,可以實現 父容器訪問子容器場景,主要設置好上下文類加載器即可。
3. 線程上下文類加載器的使用
3.1 線程上下文類加載器使用API
- 獲取當前線程的上下文類加載器:Thread.currentThread().getContextClassLoader();
- 設置當前線程的上下文類加載器:Thread.currentThread().setContextClassLoader(ClassLoader cl);
3.2 線程上下文類加載器的特征
- 如果沒有設置 setContextClassLoader(),那么線程將繼承父線程的上下文類加載器,這段可以通過Thread.init()方法中可以看出
- Java應用運行時初始上下文類加載器是系統類加載器,可以在源碼:Launcher類的構造方法中,在實例化系統類加載器后,將之設置為上下文類加載器。
3.3 線程上下文類加載器使用的通用寫法
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(null);
//其他執行事件
doSomthing();
} finally {
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
4. 借助JDBC源碼分析上下文類加載器的使用
4.1 示例代碼
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("xxx", "xx", "xx");
4.2 源碼分析
4.2.1 首先解析第一句 Class.forName("com.mysql.jdbc.Driver");
這里就不介紹了Class.forName()源碼了,這行代碼表示初始化 Driver類,對Driver類的主動使用,就會導致Driver的靜態代碼塊執行,那么我們進入到Driver類中,看是否有需要初始化調用的靜態代碼塊。
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
可以看到,存在靜態代碼塊,那么進入到靜態代碼塊中,我們來解析 DriverManager.registerDriver(new Driver());
4.2.2 DriverManager.registerDriver(new Driver());
可能有的朋友查看該代碼源碼時,直接就會進入到DriverManager類中查看registerDriver(),其實跟解析第一行一行,主動調用類的靜態方法,會導致累的初始化,執行 DriverManager中的靜態代碼塊。所以我們需要先看下面的源碼:
static {
//首先會添加初始化加載drivers,這里引入了ServiceLoader
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
//因為我們沒有設置 jdbc.drivers屬性,所以這里只展示關鍵代碼,對於其他不影響流程的代碼有所刪減,具體的可以看源碼
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;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//主要的引入各個廠家的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;
}
});
}
如果熟悉針對SPI場景的服務加載方式ServiceLoader使用的,當看到這經典的幾行的代碼時,就知道具體的加載方式了,如果有對ServiceLoader或者SPI不是很熟悉的,可以先閱讀或者百度下相關文檔(https://www.cnblogs.com/duguxiaobiao/p/12183135.html),那么通過下面這行代碼就可以將mysql依賴加載到內存中了。
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
那么是怎么在DriverManager對應的類加載器啟動類加載器中加載到mysql jar包的呢,下面來分析 ServiceLaoder.load()方法,go
4.2.3 ServiceLaoder.load()
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
//定義的內部類
private class LazyIterator implements Iterator<S>
{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
查閱源碼,我們可以看出當我們執行 ServiceLaoder.load()的時候,首先會獲取當前線程的上下文類加載器。而且在構造方法中也可以看到,如果獲取的上下文類加載器為空時,也會使用默認的系統類加載器,而默認設置當前線程的上下文類加載器的時候,默認運行時也是系統類加載器作為上下文類加載器,所以先肯定一點,后續加載類的類加載器肯定是 系統類加載器。
致此,通過 ServiceLoader
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
4.2.4 driversIterator.hasNext(); driversIterator.next();
從ServiceLoader.iterator()源碼中可以看出, driversIterator.hasNext()其實是調用了 load()時候創建的 LazyIterator.hasNext()方法
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
LazyIterator.hasNext() 最終調用了 LazyIterator.hasNextService()方法,致此,我們就可以看到ServiceLoader是如何在指定目錄下獲取到指定類名對應的實現類全類名信息的。有興趣的可以看看。
我們的重點是在 LazyIterator.next()方法,該方法最終調用了 LazyIterator.nextService()方法,在該方法中我們可以看到如何將mysql的Driver實現類使用上下文類加載器所加載到內存中。
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
//在遍歷的時候獲取的當前item的文件內容
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//是否初始化參數為false,表示這里只加載類,不初始化
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
//這里才將類初始化,觸發mysql Driver類的靜態代碼塊
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
上面代碼說簡單點,就是在 hasNext()中找到待實例化的全類名,而在 next()中實例化該類。
注意:這里有一個問題 我們在最之前的代碼中,使用了Class.forName()將mysql驅動的Driver類加載了,這里又重復了一次,那我們的第一步豈不是多余的操作。
這個疑問很好,也是正確的,所以在新版本的JDBC處理上,已經使用了SPI方式的ServiceLoader加載方式,不在需要第一步驟的手動加載初始化具體的驅動全類名了。
既然已經觸發了mysql Driver累的初始化,那么跟最開始一樣,不再重復,這回DriverManager靜態代碼塊已經執行完畢,可以真正的執行 DriverManager.registerDriver(new Driver());了,其實最終也是調用的 DriverManager.registerDriver()
4.2.5 DriverManager.registerDriver();
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
//加載到緩存中
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
從上述代碼上看,其實做的東西很簡單,就是判斷該Driver如果在緩存中沒有就添加到緩存中而已。
致此,第一行代碼解析完畢,后面的具體如何獲取數據庫連接的,跟當前文章想表達的偏離了,所以不再繼續了,over