Java類加載器 簡單自定義類加載器

自定義類加載器的流程:

  • 繼承java.lang.ClassLoader
  • 首先檢查請求的類型是否已經被這個類裝載器裝載到命名空間中了,如果已經裝載,直接返回
  • 委派類加載器請求給父類加載器,如果父類加載器能夠完成 則返回父類加載器加載的Class實例
  • 調用本類加載器的findClass(…)方法,試圖獲取相應的自己碼,如果獲取得到,則調用defineClass(…)方法導入類型到方法區;如果獲取不到字節碼或者其他原因失敗,返回異常給loadClass(…),loadClass(…)轉拋異常,終止加載過程
  • 注意,被兩個類加載器加載的同一個類,JVM不認爲是相同的類

注2:
loadClassAPI

自定義一個加載字節碼文件(.class)的類加載器

package com.feng.ClassTest;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * 自定義文件系統類加載器
 * @author feng
 *
 */

// 傳入指定的目錄,目錄下class文件可以正常加載
// com.feng.ClassTest.User --> D:/myjava/ com/feng/ClassTest/User.class
public class FileSystemClassLoader extends ClassLoader {
	
	private String rootDir;
	
	public FileSystemClassLoader(String rootDir) {
		this.rootDir = rootDir;
	}
	
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		
		Class<?> c = null;
		
		//	-----------------------
		c = findLoadedClass(name);	//是否能在已加載類中找到
		if(c != null) {
			return c;						//如果已加載,直接返回
		}
		
		ClassLoader parent = this.getParent();	//獲得父類加載器,就是應用程序類加載器AppClassLoader
		try {
			c = parent.loadClass(name);	//父類加載器加載該類,父類加載該類過程就使用了雙親委託機制
		} catch (Exception e) {
			// e.printStackTrace();
		}

		if(c != null) {						// 如果父類加載器成功加載該類 
			return c;							// 返回父類加載器加載的類
		}
		//		-----------------------
		
		byte[] classData = getClassData(name);	// 自定義方法獲取類字節碼
		if(classData == null) {
			throw new ClassNotFoundException();	// 如果字節數組爲空,拋出異常
		} else {
			c = defineClass(name, classData, 0, classData.length);
		}
		
		return c;
		
	}
	
	private byte[] getClassData(String classname) {
		// 拼出類class文件地址
		String path = rootDir + "/" + classname.replace('.', '/') + ".class";
		
		InputStream is = null;
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		try {
			is = new FileInputStream(path);
			
			byte[] buffer = new byte[1024];
			int temp = 0;
			
			while((temp = is.read(buffer)) != -1) {
				baos.write(buffer, 0, temp);
			}
			
			return baos.toByteArray();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			
			return null;
		} finally {
			
			try {
				if(is != null) {
				is.close();
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			try {
				if(baos != null) {
					baos.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		}
		
	}
	
}

測試該類

package com.feng.ClassTest;

/**
 * 測試自定義FileSystemClassLoader
 * @author Administrator
 *
 */

public class Demo03 {

	public static void main(String[] args) throws ClassNotFoundException {
		// TODO Auto-generated method stub
		
		FileSystemClassLoader loader = new FileSystemClassLoader("E:\\myjava");
		FileSystemClassLoader loader2 = new FileSystemClassLoader("E:\\myjava");
		
		Class<?> c1 = loader.findClass("com.feng.Hello.Hello");
		Class<?> c2 = loader.loadClass("com.feng.Hello.Hello");
		Class<?> c3 = loader2.loadClass("com.feng.Hello.Hello");
		
		Class<?> c4 = loader2.findClass("com.feng.ClassTest.Demo01");
		Class<?> c5 = loader2.findClass("java.lang.String");

		
		System.out.println(c1);					// class com.feng.Hello.Hello
		
		// 	同一個類被不同加載器加載,JVM也認爲是不同的類。只有同一個類加載器加載的同一個類JVM才認爲是一類
		System.out.println(c1.hashCode());		//1550089733
		System.out.println(c2.hashCode());		// 1550089733
		System.out.println(c3.hashCode());		// 865113938

		
		System.out.println(c3.getClassLoader());	//com.feng.ClassTest.FileSystemClassLoader@4e25154f
		System.out.println(c4.getClassLoader());	//sun.misc.Launcher$AppClassLoader@73d16e93
		System.out.println(c5.getClassLoader());	//null

	}
}

注 c2,c3 調用的是loadClass()方法,該方法會調用我們自定義類加載器重寫的findClass()方法。loadClass()方法執行過程見上圖。也就是說,如果我們寫的自定義類都調用loadClass()方法來加載類的話。可以將自定義類加載器//--------包圍的那一部分省略,因爲loadClass()方法會執行這些步驟,如果加載不到該類纔會調用findClass()方法。

//	-----------------------
c = findLoadedClass(name);	//是否能在已加載類中找到
if(c != null) {
	return c;						//如果已加載,直接返回
}

ClassLoader parent = this.getParent();	//獲得父類加載器,就是應用程序類加載器AppClassLoader
try {
	c = parent.loadClass(name);	//父類加載器加載該類,父類加載該類過程就使用了雙親委託機制
} catch (Exception e) {
	// e.printStackTrace();
}

if(c != null) {						// 如果父類加載器成功加載該類 
	return c;							// 返回父類加載器加載的類
}
//		-----------------------

當然,如果我們想調用自定義類的findClass()方法去加載如java.lang.String這樣的類,那這些就必須寫上。

附:自定義網絡類加載器。改自文件類加載器。

package com.feng.ClassTest;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

/**
 * 網絡類加載器
 * @author feng
 *
 */

// 傳入網絡url
// com.feng.ClassTest.User --> www.feng.cn/myjava/   com/feng/ClassTest/User.class
public class NetClassLoader extends ClassLoader {
	
	private String rootUrl;
	
	public NetClassLoader(String rootUrl) {
		this.rootUrl = rootUrl;
	}
	
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		
		Class<?> c = null;
		
		c = findLoadedClass(name);	//是否能在已加載類中找到
		if(c != null) {
			return c;						//如果已加載,直接返回
		}
		
		ClassLoader parent = this.getParent();	//獲得父類加載器,就是應用程序類加載器AppClassLoader
		try {
			c = parent.loadClass(name);	//父類加載器加載該類,父類加載該類過程就使用了雙親委託機制
		} catch (Exception e) {
			// e.printStackTrace();
		}

		if(c != null) {						// 如果父類加載器成功加載該類 
			return c;							// 返回父類加載器加載的類
		}
		
		byte[] classData = getClassData(name);	// 自定義方法獲取類字節碼
		if(classData == null) {
			throw new ClassNotFoundException();	// 如果字節數組爲空,拋出異常
		} else {
			c = defineClass(name, classData, 0, classData.length);
		}
		
		return c;
		
	}
	
	private byte[] getClassData(String classname) {
		// 拼出類class文件地址
		String path = rootUrl + "/" + classname.replace('.', '/') + ".class";
		
		InputStream is = null;
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		try {
			URL url = new URL(path);
			is = url.openStream();
			
			byte[] buffer = new byte[1024];
			int temp = 0;
			
			while((temp = is.read(buffer)) != -1) {
				baos.write(buffer, 0, temp);
			}
			
			return baos.toByteArray();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			
			return null;
		} finally {
			
			try {
				if(is != null) {
				is.close();
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			try {
				if(baos != null) {
					baos.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			
		}
		
	}
	
}

基本一樣。只是將地址換成了url,將文件讀取換成了網絡讀取。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章