自定義類加載器驗證類加載機制
全盤委托機制
當一個ClassLoader裝載一個類時,除非顯示地使用另一個ClassLoader,則該類所依賴及引用的類也由這個CladdLoader載入。
雙親委派機制
子類加載器如果沒有加載過該目標類,就先委托父類加載器加載該目標類,只有在父類加載器找不到字節碼文件的情況下才從自己的類路徑中查找並裝載目標類。
幾個重要的函數
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
1. loadClass()
可以看出loadClass()方法中實現了雙親委派的機制,即找父加載器,如果找到,則調用父加載器的loadClass(),如果父加載器為NUll,則調用啟動類加載器,如果啟動類加載器與父加載器都無法加載類,則調用自己的findClass()方法。
2. findClass()
很明顯,如果要不改變雙親委派機制的話,只需要重寫findClass()方法,實現類加載的邏輯。
自定義類加載器
各級的類加載器關系如下
自定義加載器MyLoaderA
public class MyLoaderA extends ClassLoader{
public MyLoaderA(ClassLoader parent) {
super(parent);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
if(!name.endsWith("A")){
// A加載器只能加載A類
throw new ClassNotFoundException();
}
System.out.println("A加載器正在進行加載");
// 讀取類路徑下的class文件
String className = name.substring(name.lastIndexOf(".") + 1)+".class";
InputStream inputStream = getClass().getResourceAsStream(className);
if (inputStream == null){
return super.loadClass(name, false);
}
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
throw new ClassNotFoundException();
}
}
}
MyLoaderB和MyLoaderC與MyLoaderA類似,只是在if判斷處將A改成B和C
自定義需要被加載的類
public class A {
private B b = new B();
}
public class B {
private C c = new C();
}
public class C {
}
類A的內部引用了類B,類B的內部引用了類C。
測試類
public class Test {
public static void main(String[] args) throws Exception {
MyLoaderC myLoaderC = new MyLoaderC(null);
MyLoaderB myLoaderB = new MyLoaderB(myLoaderC);
MyLoaderA myLoaderA = new MyLoaderA(myLoaderB);
Object o = myLoaderA.loadClass("com.available.A").newInstance();
}
}
MyLoaderC的父加載器是null,也就是啟動類加載器,MyLoaderB的父加載器是MyLoaderC,MyLoaderA的父加載器是MyLoaderB。
猜想
首先進入myLoaderA的loadClass()方法,去尋找myLoaderA的父加載器,接着進入myLoaderB的loadClass(),尋找myLoaderB的父加載器,myLoaderC無法加載類A,拋出異常給myLoaderB,myLoaderB也拋出異常給myLoaderA,隨后進入myLoaderA的findClass()方法中。
其次要加載類B,還是上面一套方法,只不過在myLoaderC拋出異常后,myLoaderB的findClass()加載了類B。
最后要加載類C,按照全盤委托機制,類B引用了類C,那么類C將不會進入myLoaderA的loadClass()方法,而是進入myLoaderB的loadClass()方法,開啟雙親委派機制加載類。
debug驗證
- 尋找到了myLoaderA的父加載器
- 尋找到MyLoaderB的父加載器
- MyLoaderC無法加載拋異常給MyLoaderB
-
MyLoaderB無法加載拋異常給MyLoaderA(這步省略,沒什么看頭)
-
myLoaderA加載類A
- 加載類B的步驟省略,關鍵看全盤委托機制,是否會進入myLoaderA的loadClass()方法中。
很明顯在加載類C時,並沒有進入myLoaderA的loadClass()中
初步結論
一個類在加載的時候,會從引用他的類的加載器自上開始執行雙親委派機制,也就是說,含有多層引用關系的類,被引用類的類加載器只能大於或等於引用類的加載器。
驗證結論
將類A,類B,類C的引用關系改變如下:
public class A {
private C c = new C();
}
public class B {
}
public class C {
private B b = new B();
}
類A引用類C,類C引用類B
猜想
當加載完類C之后,此時引用類為類C,被引用類為類B,按照上面得出的結論,被引用類的類加載器只能大於或等於引用類的加載器,此時大於等於MyLoaderC的加載器不能加載類B,會報錯。
驗證
猜想成功。
一種打破雙親委派機制的場景
原生的JDBC的使用,獲取數據庫連接使用的是 Connection conn = DriverManager.getConnection(xx,xx,xx),DriverManager時jdk提供的,自然使用最上層的啟動類加載器加載,而提供具體實現的是各大廠商如Mysql,按照上面的結論,啟動類加載器必然不能加載Mysql的jar包,如果不打破雙親委派,則會報錯。
線程上下文加載器
通過獲取到規定好的線程上下文加載器,就可以在任何地方使用這個加載器來加載類從而打破雙親委派機制。
// 獲得線程上下文加載器
Thread.currentThread().getContextClassLoader();
// 設置線程上下文加載器,如果沒有設置,則為系統類加載器。
Thread.currentThread().setContextClassLoader(ClassLoader cl);
最終結論
一個類在加載的時候,會從引用他的類的加載器自上開始執行雙親委派機制,也就是說,含有多層引用關系的類,被引用類的類加載器如果不指定特定的類加載器就只能大於或等於引用類的加載器。