繼承 ClassLoader 並重寫 findClass 可實現:指定路徑加載、字節碼解密加載、類熱替換。本文提供三個完整可運行示例,並深入解析 defineClass、雙親委派打破條件及熱部署核心邏輯。
| 方法 | 職責 | 備註 |
|---|---|---|
findClass(name) | 定位字節碼並返回 byte[] | 自定義加載路徑/解密邏輯入口 |
defineClass(name, bytes, off, len) | 將字節碼轉為 JVM 識別的 Class 對象 | final,不可覆蓋;支援非文件來源(網絡/內存) |
loadClass(name, resolve) | 雙親委派入口,最終調用 findClass | 覆蓋可改變委派行為(如 Tomcat) |
關鍵點:僅覆蓋 findClass 時,getClass().getClassLoader() 仍返回 AppClassLoader,因實際加載動作仍由父加載器完成;需覆蓋 loadClass 才能成為定義類加載器。
public class PathClassLoader extends ClassLoader {
private final String classPath;
public PathClassLoader(String classPath) {
this.classPath = classPath;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] data = loadClassData(name);
if (data == null) throw new ClassNotFoundException(name);
return defineClass(name, data, 0, data.length);
}
private byte[] loadClassData(String className) {
String path = classPath + File.separator + className.replace('.', File.separatorChar) + ".class";
try (InputStream is = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
byte[] buf = new byte[2048];
int len;
while ((len = is.read(buf)) != -1) baos.write(buf, 0, len);
return baos.toByteArray();
} catch (IOException e) {
return null;
}
}
public static void main(String[] args) throws Exception {
ClassLoader cl = new PathClassLoader("D:/workspace_jee/JavaTest/src/");
Class> c = cl.loadClass("classloader.SingleClass");
System.out.println(c.newInstance()); // 輸出: classloader.SingleClass@hashcode
}
}
在 findClass 獲取字節碼後,調用解密算法還原:
private byte[] decrypt(byte[] encrypted) {
// 示例:異或加密/自定義算法
byte[] decrypted = new byte[encrypted.length];
for (int i = 0; i < encrypted.length; i++) {
decrypted[i] = (byte) (encrypted[i] ^ 0xFF);
}
return decrypted;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] encrypted = loadClassData(name);
if (encrypted == null) throw new ClassNotFoundException(name);
byte[] decrypted = decrypt(encrypted);
return defineClass(name, decrypted, 0, decrypted.length);
}
應用場景:付費插件分發、敏感邏輯防反編譯、動態下發業務規則。
LinkageError)。public class ClassReloader extends ClassLoader {
private final String classPath;
public ClassReloader(String classPath) { this.classPath = classPath; }
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] data = loadClassData(name);
if (data == null) throw new ClassNotFoundException(name);
return defineClass(name, data, 0, data.length);
}
// loadClassData 同 PathClassLoader
public static void main(String[] args) throws Exception {
String path = "D:/workspace_jee/JavaTest/src/classloader/";
// 第一次加載
ClassReloader loader1 = new ClassReloader(path);
Class> c1 = loader1.findClass("SingleClass");
System.out.println("第一次: " + c1.newInstance());
// 修改 SingleClass.class 文件後,創建新加載器
ClassReloader loader2 = new ClassReloader(path);
Class> c2 = loader2.findClass("SingleClass");
System.out.println("第二次: " + c2.newInstance());
// 驗證類不相等
System.out.println("c1 == c2 ? " + (c1 == c2)); // false
System.out.println("c1.equals(c2) ? " + c1.equals(c2)); // false
}
}
第一次: classloader.SingleClass@15db9742
第二次: classloader.SingleClass@6d06d69c
c1 == c2 ? false
c1.equals(c2) ? false
若嘗試用同一實例 loader1 兩次調用 findClass,將拋出 LinkageError: attempted duplicate class definition。
serialVersionUID)。