JDK 1.2+ 採用雙親委派模型:類加載請求先委派給父加載器,無法找到時由子加載器嘗試。系統類加載器為根節點,負責從本地文件系統載入類。理解此機制是實現熱部署、插件系統、加密類加載的基礎。
| 方法 | 用途 |
|---|---|
loadClass(String, boolean) | 入口點,JDK 1.2+ 默認調用 findClass |
findClass(String) | 自定義加載邏輯核心,獲取位元組碼並調用 defineClass |
defineClass(String, byte[], int, int) | 將位元組碼轉為 Class 對象,final 方法不可覆寫 |
findSystemClass(String) | 從本地文件系統載入(系統類加載器行為) |
resolveClass(Class) | 解析類(連結階段),依 loadClass 的 resolve 參數決定 |
findLoadedClass(String) | 緩存查詢,避免重複加載 |
getParent() | 獲取父加載器,用於委派 |
findLoadedClass 檢查緩存loadClass(父為 null 則用系統加載器)findClass 尋找類resolveClassClassNotFoundExceptionClass 對象public class CompilingClassLoader extends ClassLoader {
private byte[] getBytes(String filename) throws IOException {
File file = new File(filename);
byte[] raw = new byte[(int) file.length()];
try (FileInputStream fin = new FileInputStream(file)) {
int r = fin.read(raw);
if (r != raw.length) throw new IOException("Read incomplete");
return raw;
}
}
private boolean compile(String javaFile) throws IOException {
Process p = Runtime.getRuntime().exec("javac " + javaFile);
try { p.waitFor(); } catch (InterruptedException e) { return false; }
return p.exitValue() == 0;
}
@Override
public Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(name);
if (clazz != null) return clazz;
String path = name.replace('.', '/');
File javaFile = new File(path + ".java");
File classFile = new File(path + ".class");
// 源檔較新或 class 不存在則重新編譯
if (javaFile.exists() && (!classFile.exists() ||
javaFile.lastModified() > classFile.lastModified())) {
if (!compile(javaFile.getPath()) || !classFile.exists()) {
throw new ClassNotFoundException("Compile failed: " + javaFile);
}
}
try {
byte[] raw = getBytes(classFile.getPath());
clazz = defineClass(name, raw, 0, raw.length);
} catch (IOException e) {
// 忽略,嘗試系統加載器
}
if (clazz == null) clazz = findSystemClass(name);
if (resolve && clazz != null) resolveClass(clazz);
if (clazz == null) throw new ClassNotFoundException(name);
return clazz;
}
}
public class Hello {
public static void main(String[] args) {
if (args.length != 1) { System.err.println("Error"); System.exit(1); }
System.out.println("Hello, " + args[0]);
}
}
public class TestRun {
public static void main(String[] args) throws Exception {
String className = args[0];
String[] progArgs = Arrays.copyOfRange(args, 1, args.length);
CompilingClassLoader ccl = new CompilingClassLoader();
Class<?> clazz = ccl.loadClass(className);
Method main = clazz.getMethod("main", String[].class);
main.invoke(null, (Object) progArgs);
}
}
> javac CompilingClassLoader.java TestRun.java
> java TestRun Hello 阿飛
CCL:Compiling Hello.java...
Hello,阿飛
defineClassClassCastException 風險)Thread.getContextClassLoader())常用於 SPI 機制