破壞雙親委派模型和自定義自己的類載入器

z1340954953發表於2018-04-25

ClassLoader loadeClass原始碼:

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 . 首先檢查,這個類是否已經被載入過了

2.  沒有被載入過,如果父類載入存在,首先呼叫父類的載入器載入,沒有父類載入使用Bootstrap ClassLoader載入

3. 如果仍然沒有成功載入,呼叫findClass方法(這個方法可以被子類載入器重寫),

從原始碼看出findClass方法是需要開發者自己去實現的,到這裡其實就是呼叫實現的類載入器載入

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

從這裡看出,雙親委派模型的實現依賴於loadClass方法:

1. 如果不想不破壞雙親委派模型,只要去重寫findClass方法

2. 如果想要去破壞雙親委派模型,需要去重寫loadClass方法

自定義類載入器

1. 不破壞雙親委派模型,重寫findClass方法

public class ProtectedClassLoader extends ClassLoader {
	public  ProtectedClassLoader(ClassLoader parent){
		super(parent);
	}
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		byte[] by = null;
		try {
			by = getByteByClassName(name);
			return defineClass(name, by, 0, by.length);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return super.findClass(name);
	}
	@Override
	public InputStream getResourceAsStream(String name) {
		return super.getResourceAsStream(name);
	}
	private byte[] getByteByClassName(String name) throws IOException {
		File file = new File("D:\\MyTest.class");
		InputStream is = new FileInputStream(file);
		byte[] by = new byte[is.available()];
		is.read(by);
		is.close();
		return by;
	}
	public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException, IOException {
		ClassLoader ml = new ProtectedClassLoader(ClassLoader.getSystemClassLoader().getParent());
		Object obj = Class.forName("com.gold.aip.test.MyTest", true, ml).newInstance();
		System.out.println(obj);
		System.out.println(obj.getClass().getClassLoader());
	}
}

輸出結果:

com.gold.aip.test.MyTest@b03be0
com.gold.aip.test.ProtectedClassLoader@14e8cee

測試這段程式碼的過程出現了兩個問題:

1. 如果直接呼叫自定義類載入器的loadClass,結果還是ApplicationClassLoader,改用Class.forName指定載入器去載入

2. Myeclipse環境下,儲存這個類,會自動編譯,結果還是使用ApplicationClassLoader,解決方案:

*  刪除classpath下編譯的class檔案

* 指定類載入器的父類為ExtensionClassLoader,這樣父載入器無法載入,自然給子類載入載入。

* 因為指定了父類載入器,使用getResourceAsStream方法,從原始碼看出是呼叫父類載入器的這個方法,取得的位元組流是空的

改用FileInputStream直接讀取

2. 破壞雙親載入模型自定義類載入器

package cn.erong.test;

import java.io.InputStream;

public class Test {
	public static void main(String[] args) throws Exception {
		ClassLoader myloader = new ClassLoader() {
			@Override
			public Class<?> loadClass(String name)
					throws ClassNotFoundException {
				String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
				InputStream is;
					try {
						is =getClass().getResourceAsStream(fileName);
						if(is==null) {return super.loadClass(fileName);}
						byte[] by = new byte[is.available()];
						is.read(by);
						is.close();
						return defineClass(name, by, 0, by.length);
					} catch (Exception e) {
						e.printStackTrace();
					}
				return null;
			}
		};
		myloader.loadClass("cn.erong.test.Test").newInstance();
	}
}

這段程式碼是有問題的,丟擲異常:

java.lang.ClassNotFoundException: Object.class

不過能瞭解到一些東西:

* 測試類Test,繼承Object,因為破壞了雙親載入模型,Object類也會使用這個載入器載入,從Classpath下找這個類,必然會出錯。

* 斷點測試,Object類是後載入的,因為父類永遠比子類先初始化,所以可以看出父類是後載入,但是先解析,初始化


相關文章