首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> JSP 程式設計 >> 關於JSP

Java 類加載機制深度解析:委派模型、自定義 ClassLoader 與熱編譯實戰

     編輯:關於JSP

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()獲取父加載器,用於委派

標準加載流程

  1. 調用 findLoadedClass 檢查緩存
  2. 委派父加載器 loadClass(父為 null 則用系統加載器)
  3. 調用自身 findClass 尋找類
  4. 若 resolve 為 true,調用 resolveClass
  5. 未找到拋出 ClassNotFoundException
  6. 返回 Class 對象

實戰:編譯型 ClassLoader(自動編譯 .java 為 .class 並加載)

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;
    }
}

測試驗證

Hello.java

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]);
    }
}

TestRun.java

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,阿飛

進階應用場景

  • 熱部署:監控 .class 文件變更,重新加載類
  • 加密類保護:解密位元組碼後調用 defineClass
  • 插件系統:隔離插件類命名空間,避免衝突
  • 動態代理/AOP:運行時生成類位元組碼並加載

關鍵注意事項

  • 破壞雙親委派需謹慎(如 Tomcat WebappClassLoader 優先自加載)
  • 同一類由不同加載器加載視為不同類型(ClassCastException 風險)
  • 線程上下文類加載器(Thread.getContextClassLoader())常用於 SPI 機制
  • Java 9+ 模組系統對類加載有額外限制
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved