線程上下文類加載器
通過名字可知,線程上下文類加載,就是當前線程所擁有的類加載器财边,可通過Thread.currentThread()獲取當前線程。
線程上下文類加載器(Thread Context ClassLoader)可以通過java.lang.Thread類的setContextClassLoader()方法設置点骑,創(chuàng)建線程時候未指定的話酣难,則默認從父線程中繼承。
那父線程中也沒指定呢黑滴?那么會默認為應用程序的類加載器憨募。例如:main方法的線程上下文類加載器就是sun.misc.Launcher$AppClassLoader。
前兩篇文章中袁辈,我們講解了類加載器的雙親委派模型菜谣,該模型的實現(xiàn)是通過類加載器中的parent屬性(父加載器)來完成的,默認統(tǒng)一交給最上層類加載器去嘗試加載。
那尾膊,這個線程上下文類加載器又是干啥的甘磨?
在介紹線程上下文類加載前,我們先了解下Java的SPI機制眯停。
線程上下文類加載實現(xiàn)
public class JVMTest6 {
public static void main(String[] agrs) throws ClassNotFoundException {
ClassLoader loader = JVMTest6.class.getClassLoader();
System.out.println(loader); //默認是應用類加載器
//此時獲得上下文類加載器:
ClassLoader loader2 = Thread.currentThread().getContextClassLoader();
System.out.println(loader2);//默認也是應用類加載器
//設置為自定義類加載器:
Thread.currentThread().setContextClassLoader(
new ClassLoaderTest("d:/"));
System.out.println(Thread.currentThread().getContextClassLoader());
//使用自定義類加載器加載:
Class c = Thread.currentThread().getContextClassLoader().loadClass("HelloWorld");
System.out.println(c.getClassLoader());//線程上下文類加載器
ClassLoader loader3 = String.class.getClassLoader();
System.out.println(loader3);//啟動類加載器 = null
}
}
測試結果:
sun.misc.Launcher$AppClassLoader@41dee0d7
sun.misc.Launcher$AppClassLoader@41dee0d7
ClassLoaderTest@516a4aef
ClassLoaderTest@516a4aef
null
JDBC加載案例分析
介紹完了spi济舆,下來我們來舉幾個例子進一步說明逆向類加載。
舉個簡單的例子莺债,mysql是如何獲取數(shù)據(jù)庫連接:
public class JVMTest7 {
public static void main(String[] agrs) {
try {
// 注冊驅動類
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/testdb";
//通過java庫獲取數(shù)據(jù)庫連接
Connection conn = java.sql.DriverManager.getConnection(url, "root", "123456");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
以上就是mysql注冊驅動及獲取connection的過程滋觉。在該流程中,java通過線程上線文類加載器實現(xiàn)了逆向類加載齐邦。
(1)通過系統(tǒng)類加載器椎侠,加載Driver類---Class.forName("com.mysql.jdbc.Driver");底層具體實現(xiàn):registerDriver()將driver實例注冊到java.sql.DriverManager類中措拇,其實就是將com.mysql.jdbc.Driver添加到java.sql.DriverManager類的靜態(tài)變量CopyOnWriteArrayList集合中我纪。
com.mysql.jdbc.Driver包中:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
}
(2)通過java.sql.DriverManager注冊數(shù)據(jù)庫驅動。首先丐吓,來看下DriverManager的靜態(tài)方法浅悉。需要明確的是java.sql.DriverManager位于rt.jar包目錄下,該目錄下的所有類均由Bootstrap啟動類加載器進行加載券犁。
java.sql.DriverManager包中:
static {
//初始化Driver驅動實現(xiàn)類:
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
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;
}
//通過spi來加載jdbc驅動實現(xiàn)類:
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//通過SPI方式术健,讀取META-INF/services下文件中的類:
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {}
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);
}
}
}
(3)spi具體實現(xiàn):
在下面代碼中,通過SPI方式來完成java.sql.Driver接口實現(xiàn)類的類加載操作粘衬。
java.sql.DriverManager包中:
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//通過SPI方式荞估,讀取META-INF/services下文件中的類名:
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {}
return null;
}
});
獲取到ServiceLoader對象后,進行遍歷操作稚新,遍歷出所有META-INF/services文件夾下的實現(xiàn)類名稱勘伺,之后再進行Class.forName("")類加載操作。類加載操作在driversIterator.next()中完成褂删。
java.util.ServiceLoader包中:
public static <S> ServiceLoader<S> load(Class<S> service) {
//獲取線程上下文類加載器:
ClassLoader cl = Thread.currentThread().getContextClassLoader();
//生成ServiceLoader對象:
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader){
return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = svc;
loader = cl;
reload();
}
在獲取ServiceLoader對象時飞醉,獲取了此時線程上下文中的類加載器,將此類加載賦值給ServiceLoader類中的loader成員變量笤妙。在后續(xù)類加載過程中冒掌,都是使用的此類加載來完成。這一步的操作蹲盘,直接打破了雙親委派模型股毫,實現(xiàn)了逆向類加載。
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {}
通過debug發(fā)現(xiàn)召衔,driversIterator.next()方法內部會調用Class c = Class.forName(cn, false, loader)方法進行類加載操作铃诬。而此時傳遞的loader就是之前獲取的線程上下文類加載器,傳遞的cn就是META-INF/services文件中的具體實現(xiàn)類。
由于筆者是通過本地的test進行測試趣席,所以上文中涉及到的類加載器都是AppClassLoader系統(tǒng)類加載器兵志。