類加載器載入類時,優先委派給父加載器,遞歸向上至啟動類加載器;父加載器無法載入時,子加載器嘗試自行載入。這確保了核心類庫(如 java.lang.String)不被惡意篡改,實現「沙箱安全」。
<JAVA_HOME>/lib 核心庫(rt.jar 等)。<JAVA_HOME>/lib/ext 或 java.ext.dirs 指定目錄。classpath 下類,ClassLoader.getSystemClassLoader() 獲取。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+ 模組系統 | 模組圖委派 | 強封裝、可讀性邊界替代單純層級委派 |
// 框架代碼:獲取調用者的類加載器載入 SPI 實現
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class, tccl);
這是打破雙親委派最通用的標準化機制,JDBC、JNDI、JAXP、SLF4J 等廣泛採用。
java.lang.* 導致安全漏洞。--add-exports、--add-opens 替代舊有暴力反射破壞封裝。