package java.lang;
public class Test {
public static void main(String[] args) {
char[] c = "1234567890".toCharArray();
// String 包私有建構子:String(int offset, int count, char[] value)
String s = new String(0, 10, c);
}
}
編譯通過,運行拋出:
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.defineClass(...)
明明同屬 `java.lang` 套件,為何無法存取包私有建構子?答案在於 ClassLoader 隔離機制。
關鍵結論:同一個類只能被同一個類加載器載入一次。不同類加載器載入的同名類是完全不同的類型,不可互相轉換賦值。
只有由同一個 ClassLoader 載入的類,才擁有相互的 package-private(預設)存取權限。
回到開頭範例:
這是 JVM 層面的安全防線,防止使用者自定義 `java.lang.*`、`javax.*` 等核心套件竄改核心類行為。
| 層級 | 機制 | 作用 |
|---|---|---|
| 編譯期 | javac 檢查 import、修飾符 | 阻擋明顯的非法存取 |
| 類加載期 | ClassLoader 雙親委派 + 套件封鎖 | 阻止核心套件被用戶代碼替換/竄改 |
| 運行期 | SecurityManager + Reflection setAccessible 檢查 | 動態控管敏感 API 呼叫 |
Spring、Hibernate 等框架大量使用反射存取私有欄位/方法,核心在於:
Field field = clazz.getDeclaredField("privateField");
field.setAccessible(true); // 關鍵:關閉 Java 語言存取檢查
field.set(targetObject, newValue);
setAccessible(true) 的前提是:
public class PluginClassLoader extends ClassLoader {
private final String pluginDir;
public PluginClassLoader(String pluginDir, ClassLoader parent) {
super(parent); // 明確指定父加載器,避免預設為系統加載器
this.pluginDir = pluginDir;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
// 1. 讀取類字節碼(可解密、驗證簽名)
byte[] classData = loadClassBytes(name);
if (classData == null) throw new ClassNotFoundException(name);
// 2. 定義類
return defineClass(name, classData, 0, classData.length);
}
// 打破雙親委派:優先自己載入插件類,避免衝突
@Override
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
if (isPluginClass(name)) {
return findClass(name);
}
return super.loadClass(name, resolve);
}
}
深入理解 ClassLoader 與 Package 機制,是排查 `NoClassDefFoundError`、`ClassCastException`、`IllegalAccessError` 等疑難雜症、設計模組化架構、實現熱部署插件系統的核心基石。