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