JVM(十二) 創建用戶自定義的類加載器

要創建用戶自己的類加載器,只需要擴展java.lang.ClassLoader類,然後覆蓋它的findClass(String name)方法即可,該方法根據參數指定的類的名字,返回對應的Class對象的引用。

我們來看看官網api文檔的findClass方法。

http://docs.oracle.com/javase/7/docs/api/

  • findClass

    protected Class<?> findClass(String name)
                          throws ClassNotFoundException
    Finds the class with the specified binary name. This method should be overridden by class loader implementations that follow the delegation model for loading classes, and will be invoked by the loadClass method after checking the parent class loader for the requested class. The default implementation throws a ClassNotFoundException.
    Parameters:
    name - The binary name of the class
    Returns:
    The resulting Class object
    Throws:
    ClassNotFoundException - If the class could not be found
    Since:
    1.2
我們來看一個Demo,包括MyClassLoader.java,Sample.java,Dog.java三個文件

MyClass.java


import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader{
	private String name;  //類加載器
	private String path ="d:\\"; //加載類的路徑
	private final String fileType=".class";  //class文件的擴展名
	private MyClassLoader(String name)
	{
		super();  //讓系統類加載器成爲該類加載器的父加載器
		this.name = name;
	}
	
	public MyClassLoader(ClassLoader parent,String name)
	{
		super(parent);  //顯式指定該類加載器的父加載器
		this.name = name;
	}
	
	@Override
	public String toString() {
		return this.name;
	}
	
	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}
	
	@Override
	public Class<?> findClass(String name) throws ClassNotFoundException
	{
		byte[] data = this.loadClassData(name);
		return this.defineClass(name, data, 0,data.length);
	}
	 
	private byte[] loadClassData(String name)
	{
		InputStream is = null;
		byte[] data = null;
		ByteArrayOutputStream baos = null;
		
		try {
			this.name= this.name.replace(".", "\\");
			is = new FileInputStream(new File(path+name+fileType));
			baos = new ByteArrayOutputStream();
			int ch = 0;
			while(-1!=(ch = is.read()))
			{
				baos.write(ch);
			}
			data = baos.toByteArray();
		} catch (Exception ex) {
			ex.printStackTrace();
		}
		finally
		{
			try {
				is.close();
				baos.close();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}
		return data;
	}
	
	public static void main(String[] aregs) throws Exception
	{
		MyClassLoader loader1 = new MyClassLoader("loader1");
		loader1.setPath("d:\\myapp\\serverlib\\");
		MyClassLoader loader2 = new MyClassLoader(loader1,"loader2");
		loader2.setPath("d:\\myapp\\clientlib\\");
		MyClassLoader loader3 = new MyClassLoader(null,"loader3");
		loader3.setPath("d:\\myapp\\otherlib\\");
		test(loader2);
		test(loader3);
	}
	
	public static void test(ClassLoader loader) throws Exception
	{
		Class clazz = loader.loadClass("Sample");
		Object object = clazz.newInstance();
	}
	
	
}

Sample.java

public class Sample {
	public int v1= 1;
	public Sample()
	{
		System.out.println("Sample is loaded by: "+this.getClass().getClassLoader());
		new Dog();
	}
	
}

Dog.java

public class Dog {
	public Dog()
	{
		System.out.println("Dog is loaded by :"+this.getClass().getClassLoader());
	}
}

按照以上代碼類加載的位置分別創建文件夾。


把編譯好的類文件放在各個文件夾下,把Sample.class,Dog.class放在otherlib,serverlib文件夾下。把MyclassLoader.class放在syslib文件夾下。

然後在syslib文件夾下運行dos窗口:

輸入命令:java MyClassLoader

輸出結果:

Sample is loaded by: loader1
Dog is loaded by :loader1
Sample is loaded by: loader3
Dog is loaded by :loader3

我們分析一下:

findClass方法在上面被重寫。


當執行loader2.loadClass("Sample")時,先由它上層的所有父加載器嘗試加載Sample類。loader1從D:\myapp\serverlib目錄下成功地加載了Sample類,因此loader1是Sample類的定義類加載器,loader1和loader2是Sample類的初始類加載器。

當執行loader3.loadClass("Sample")時,先由它上層的所有父加載器嘗試加載Sample類。loader3的父加載器爲根類加載器,它無法加載Sample類,接着loader3從D:\myapp\otherlib目錄下成功地加載了Sample類,因此loader3是Sample類的定義類加載器及初始類加載器。



在loader1和loader3各自的命名空間中都存在Sample類和Dog類。

在Sample類中主動使用了Dog類,當執行Sample類的構造方法中的new Dog()語句時,Java虛擬機需要先加載Dog類,到底是用哪個類加載器加載呢?從輸出結果可以看出,加載Sample類的loader1還加載了Dog類,Java虛擬機會用Sample類的定義類加載器去加載Dog類,加載過程也同樣採用父親委託機制。爲了驗證這一點,可以把D:\myapp\serverlib目錄下的Dog.class文件刪除,然後在D:\myapp\syslib目錄下存放一個Dog.class文件,此時程序的打印結果爲:

Sample is loaded by: loader1
Dog is loaded by :sun.misc.Launcher$AppClassLoader@73d16e93
Sample is loaded by: loader3
Dog is loaded by :loader3

由此可見,當由loader1加載的Sample類首次主動使用Dog類時,Dog類由系統類加載器加載。如果再把D:\myapp\syslib目錄下的Dog.class對象去掉,在D:\myapp\clientlib目錄下存放一個Dog.class文件


當由loader1加載的Sample類首次主動使用Dog類時,由於loader1及它的父加載器都無法加載Dog類,因此test(loader2)方法會拋出ClassNotFoundException.

拓展:

1、命令中環境變量的指定

輸入命令:java -cp .;d:\myapp\serverlib MyClassLoader

輸出結果

Sample is loaded by: sun.misc.Launcher$AppClassLoader@73d16e93
Dog is loaded by :sun.misc.Launcher$AppClassLoader@73d16e93
Sample is loaded by: loader3
Dog is loaded by :loader3

分析(僅限test(loader2)):

.;d:\myapp\serverlib指定系統加載器的加載位置。

統加載器從指定的目錄D:\myapp\serverlib目錄下成功地加載了Sample類,因此係統加載器是Sample類的定義類加載器,系統加載器、loader1和loader2是Sample類的初始類加載器。

2、用代碼驗證加載器之間的關係

public class Test8 {
	public static void main(String[] args)
	{
		ClassLoader classLoader = ClassLoader.getSystemClassLoader();
		System.out.println(classLoader);
		while(null!=classLoader)
		{
			classLoader = classLoader.getParent();
			System.out.println(classLoader);
		}
	}
}
輸出結果:

sun.misc.Launcher$AppClassLoader@73d16e93
sun.misc.Launcher$ExtClassLoader@15db9742
null

分析:剛開始得到系統加載器,然後得到父加載器應用加載器,然後應用加載器的父加載器的爲 null。


發佈了254 篇原創文章 · 獲贊 159 · 訪問量 64萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章