自己編寫類加載器的意義
- 當class文件不在ClassPath目錄下時婚苹,默認的系統(tǒng)類加載器無法找到該class文件,這時候需要自定義一個ClassLoader來加載特定路徑下的class對象
- 當一個class文件需要通過網(wǎng)絡(luò)傳輸征绸,甚至可能會進行相應(yīng)的解密操作時祝拯,這時候需要編寫自定義的ClassLoader來實現(xiàn)相應(yīng)邏輯
- 當要實現(xiàn)熱部署時(一個class文件通過不同的類加載器產(chǎn)生不同的class對象而實現(xiàn)熱部署功能),需要實現(xiàn)自定義的ClassLoader的邏輯
目標
本目錄針對以上三個意義,將分類實戰(zhàn)完成三類自定義ClassLoader的自定義實現(xiàn)财松,以便更好的掌握。
- 自定義文件類加載器
- 自定義網(wǎng)絡(luò)類加載器
- 熱部署類加載器
自定義File類加載器
package classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 從特定路徑的文件獲取類的File類加載器
* @author XZP
*
*/
public class FileClassLoader extends ClassLoader {
private String rootDir;
public FileClassLoader(String rootDir) {
this.rootDir = rootDir;
}
/**
* 重寫父類的findClass方法
* @param name 文件名
* @return 找到類的字節(jié)碼就用defineClass返回對應(yīng)的對象
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 獲取類的class文件字節(jié)數(shù)組
byte[] classData = getClassDataFromFile(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
// 直接生成class對象
return defineClass(name, classData, 0, classData.length);
}
}
/**
* 獲取class文件并轉(zhuǎn)換為字節(jié)碼流的邏輯
* @param name 文件名
* @return
*/
private byte[] getClassDataFromFile(String name) {
// 讀取類文件的字節(jié)
String path = name2Path(name);
System.out.println("path:" + path);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
// 讀取文件字節(jié)碼
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch(IOException io) {
io.printStackTrace();
}
return null;
}
/**
* 通過文件名獲取class文件的完全路徑
* @param className 文件名
* @return
*/
private String name2Path(String className) {
return rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
}
}
自定義網(wǎng)絡(luò)類加載器
package classloader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
public class NetClassLoader extends ClassLoader {
private String url; // class文件所在的URL
public NetClassLoader(String url) {
this.url = url;
}
/**
* 重寫父類的findClass方法
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassDataFromNet(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name,classData, 0, classData.length);
}
}
/**
* 從網(wǎng)絡(luò)獲取class文件,不包含解密
* @param name
* @return
*/
private byte[] getClassDataFromNet(String name) {
String path = name2Path(name);
try {
URL url = new URL(path);
InputStream ins = url.openStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int hasRead = 0;
while ((hasRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, hasRead);
}
// 可能還涉及解密,在此省略,但是是非常重要的一個內(nèi)容
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 根據(jù)文件名得到文件URL
* @param name
* @return
*/
private String name2Path(String name) {
// 得到class文件的URL
return url + "/" + name.replace('.', '/') + ".class"; // 注意網(wǎng)絡(luò)路徑表示和File路徑表示還是有區(qū)別的
}
}
熱部署類加載器
package classloader;
/**
* 熱部署的測試類
* 作如下特殊說明:
* 熱部署就是利用同一個class文件不同的類加載器在內(nèi)存創(chuàng)建出兩個不同的class對象.
* 由于JVM在加載類之前會檢測請求的類是否已加載過(即在loadClass()方法中調(diào)用findLoadedClass()方法),
* 如果被加載過惫皱,則直接從緩存獲取捷绑,不會重新加載进苍。注意同一個類加載器的實例和同一個class文件只能被加載器一次宋下,多次加載將報錯.
* 因此我們實現(xiàn)的熱部署必須讓同一個class文件可以根據(jù)不同的類加載器重復(fù)加載揭蜒,以實現(xiàn)熱部署:也即為同一個class文件寫兩個不同的類加載器
* @author XZP
*
*/
public class HotDeployTest {
public static void main(String[] args) {
String rootDir = "F:\\java_workspace\\jvm\\src\\classloader";
// 創(chuàng)建自定義類的加載器:兩個不同的對象
FileClassLoader loader1 = new FileClassLoader(rootDir);
FileClassLoader loader2 = new FileClassLoader(rootDir);
try {
// 通過調(diào)用loadClass()加載指定的class文件
Class<?> object1 = loader1.loadClass("classloader.FileResObj");
Class<?> object2 = loader2.loadClass("classloader.FileResObj");
System.out.println("obj1 by loadClass:" + object1.hashCode()); // 輸出的兩者hashCode相等葱峡,說明是同一對象
System.out.println("obj2 by loadClass:" + object2.hashCode());
// 通過調(diào)用findClass()方法繞過檢測,創(chuàng)建不同的class對象
Class<?> object3 = loader1.findClass("FileResObj"); // 輸出的兩者hashCode不相等說明是不同對象
Class<?> object4 = loader2.findClass("FileResObj");
System.out.println("obj3 by findClass:" + object3.hashCode());
System.out.println("obj4 by findClass:" + object4.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}