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

Java 類加載器雙親委派模型深度解析與打破機制實戰

     編輯:Java 相關知識

雙親委派模型核心原理

類加載器載入類時,優先委派給父加載器,遞歸向上至啟動類加載器;父加載器無法載入時,子加載器嘗試自行載入。這確保了核心類庫(如 java.lang.String)不被惡意篡改,實現「沙箱安全」。

標準加載器層級

  1. Bootstrap ClassLoader(C++ 實現):載入 <JAVA_HOME>/lib 核心庫(rt.jar 等)。
  2. Extension ClassLoader:載入 <JAVA_HOME>/lib/ext 或 java.ext.dirs 指定目錄。
  3. Application ClassLoader(系統類加載器):載入 classpath 下類,ClassLoader.getSystemClassLoader() 獲取。
  4. 自定義類加載器:繼承 ClassLoader,實現熱部署、模組隔離、加密類解密等。

遵循雙親委派:自定義類加載器實現

僅重寫 findClass(),不觸動 loadClass(),父加載器優先嘗試。

public class DelegatingClassLoader extends ClassLoader {
    private final String name;
    private final String classPath; // 類文件根目錄
    
    public DelegatingClassLoader(ClassLoader parent, String name, String classPath) {
        super(parent);
        this.name = name;
        this.classPath = classPath;
    }
    
    @Override
    public String toString() { return name; }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File classFile = new File(classPath, name.replace('.', '/') + ".class");
        if (!classFile.exists()) throw new ClassNotFoundException(name);
        
        try (FileInputStream fis = new FileInputStream(classFile);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) != -1) baos.write(buffer, 0, len);
            byte[] classBytes = baos.toByteArray();
            return defineClass(name, classBytes, 0, classBytes.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name, e);
        }
    }
    
    // 測試
    public static void main(String[] args) throws Exception {
        DelegatingClassLoader loader = new DelegatingClassLoader(
            DelegatingClassLoader.class.getClassLoader(), "MyLoader", "d:/classes");
        Class<?> clazz = loader.loadClass("com.example.Test");
        clazz.getDeclaredConstructor().newInstance(); // 輸出: MyLoader
    }
}

打破雙親委派:逆向委派實現

重寫 loadClass(),先嘗試系統類加載器,失敗再委派給自己 findClass(),跳過父加載器。典型場景:JNDI、JDBC、OSGi、熱部署(如 Tomcat WebappClassLoader)。

public class BreakingDelegationClassLoader extends ClassLoader {
    public BreakingDelegationClassLoader(ClassLoader parent, String name) {
        super(parent);
    }
    
    @Override
    public String toString() { return "BreakingLoader"; }
    
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 同 DelegatingClassLoader,省略...
        return defineClass(name, readClassBytes(name), 0, classBytes.length);
    }
    
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> clazz = null;
        // 1. 先嘗試系統類加載器(打破雙親委派核心)
        try { clazz = ClassLoader.getSystemClassLoader().loadClass(name); } 
        catch (ClassNotFoundException ignored) {}
        if (clazz != null) return clazz;
        // 2. 系統加載失敗,由自己 findClass 載入
        return findClass(name);
    }
}

經典打破場景深度剖析

場景打破方式核心原因
JDBC DriverManager線程上下文類加載器核心庫需載入應用提供的 Driver 實現(SPI)
Tomcat WebappClassLoader優先自載,再委派父Web 應用隔離、版本衝突解決、熱部署
OSGi / 模組化框架網狀委派Bundle 間精準依賴、多版本共存
JDK 9+ 模組系統模組圖委派強封裝、可讀性邊界替代單純層級委派

線程上下文類加載器(TCCL)模式

// 框架代碼:獲取調用者的類加載器載入 SPI 實現
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class, tccl);

這是打破雙親委派最通用的標準化機制,JDBC、JNDI、JAXP、SLF4J 等廣泛採用。

最佳實踐與避坑

  1. 自定義加載器優先重寫 findClass(),遵循雙親委派,除非有明確隔離需求。
  2. 打破委派時謹慎處理核心類,避免載入偽造的 java.lang.* 導致安全漏洞。
  3. 熱部署場景需配合類卸載:確保舊類加載器可被 GC 回收(無強引用、線程池清理、註冊反射緩存清理)。
  4. JDK 9+ 開發理解模組系統:--add-exports、--add-opens 替代舊有暴力反射破壞封裝。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved