文章目錄
Pre 雙親委派
何爲打破雙親委派
舉個例子 有個類 Artisan
我們希望通過自定義加載器 直接從某個路徑下讀取Artisan.class . 而不是說 通過自定義加載器 委託給 AppClassLoader ------> ExtClassLoader ----> BootClassLoader 這麼走一遍,都沒有的話,才讓自定義加載器去加載 Artisan.class . 這麼一來 還是 雙親委派。
我們期望的是 Artisan.class 及時在 AppClassLoader 中存在,也不要從AppClassLoader 去加載。
說白了,就是 直接讓自定義加載器去直接加載Artisan.class 而不讓它取委託父加載器去加載,不要去走雙親委派那一套。
我們知道 雙親委派的機制是在ClassLoader # loadClass方法中實現的,打破雙親委派,那我們是不是可以考慮從這個地方下手呢?
如何打破雙親委派
核心: 重寫ClassLoader#loadClass方法
演示
剛纔的思路是對的,要打破它,那就搞loadClass方法。
重寫loadClass方法唄。
我們基於 JVM - 自定義類加載器 再來搞一搞
需要再此基礎上 重寫loadClass 方法
迴歸下雙親委派的源碼
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) {
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//調用URLClassLoader的findClass方法在加載器的類路徑裏查找並加載該類
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;
}
}
那打破它,那我們就不要委託父加載器了唄,直接去findClass 不就好了?
我們把loadClass方法的源碼copy過來 把雙親委派的部分代碼去掉吧,走 改下
重寫 ClassLoader#loadClass
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name
+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
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) {
c = findClass(name);
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass將一個字節數組轉爲Class對象,這個字節數組是class文件讀取後最終的字節數組。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
重點
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 嘗試加載,不存在直接去findClass ,不走委託父類
Class<?> c = findLoadedClass(name);
if (c == null) {
c = findClass(name);
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
運行下
失敗原因探究
略微尷尬, Object.class 找不到 。 爲啥 呢? 你加載Boss1的時候, Boss1的父類也需要被加載, 你又把雙親委派給關了, 這個自定義的加載器在本地路徑下是找不到Object.class的 。
咋辦? 放到自定義的加載器加載的路徑下 ?
-----> 其實是不行的,Object 誰能篡改的了啊 ,Object只能由引導類加載器來加載。
臨時解決辦法
所以換個思路 ,自己的類路徑下的對象走我自己的classLoader, 其他的類 還是走雙親委派
if ("com.gof.facadePattern.Boss1".equals(name)){
c = findClass(name);
}else{
// 交由父加載器去加載
c = this.getParent().loadClass(name);
}
驗證是否成功
這個時候我們在AppClassLoader加載的路徑下 再創建個Boss1 (如果走的還是雙親委派,那加載器肯定還是AppClassLoader)
看 是不是這個Boss1 還是被自定義的ClassLoader加載,如果是,說明打破成功。
應用下新建Boss1類
自定義加載路徑D:/artisan/com/gof/facadePattern下保留Boss1.class
驗證
輸出結果
OK,雙親委派機制 打破成功。
這個在tomcat類加載機制中非常重要,所以需要徹底明白這一點。