Java的ClassLoader与Package机制
实验验证
为了深入探究 Java 的 ClassLoader 机制,我们先进行以下实验。尝试在 java.lang 包下定义一个类,并访问 String 类的包权限构造函数:
package java.lang;
public class Test {
public static void main(String[] args) {
char[] c = "1234567890".toCharArray();
// 尝试访问 String 的 package-private 构造函数
String s = new String(0, 10, c);
}
}String 类拥有一个包权限(Package-private)的构造函数 String(int offset, int length, char[] array)。按照 Java 默认的访问控制规则,由于 Test 类也属于 java.lang 包,理论上应该可以访问该构造函数。编译可以通过!
然而,执行时结果如下:
Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.security.SecureClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.defineClass(Unknown Source)
at java.net.URLClassLoader.access$100(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClassInternal(Unknown Source)为何会出现这种 SecurityException?要弄清原因,必须深入理解 ClassLoader 的机制。
ClassLoader 机制与双亲委派
Java 的 ClassLoader 负责动态装载 class 文件。对于一个 class,ClassLoader 只会装载一次。JVM 中常见的 ClassLoader 主要分为以下 4 种,优先级依次从高到低:
- 启动类装载器(Bootstrap ClassLoader)
- 标准扩展类装载器(Extension ClassLoader)
- 类路径装载器(App/System ClassLoader)
- 网络类装载器(Network ClassLoader)
这些 ClassLoader 之间使用所谓的“双亲委派模型”(Parent Delegation Model)。确切地说,如果一个网络类装载器被请求装载一个类(例如 java.lang.Integer),它会首先把请求发送给上一级的类路径装载器。如果上级返回已装载,则网络类装载器不会再次装载;只有当上一级返回未装载时,它才会尝试自行装载。
类似的,类路径装载器收到请求后(无论是直接请求还是下级上传的请求),也会先把请求发送到上一级的标准扩展类装载器。这样一层一层上传,直到启动类装载器。由于启动类装载器优先级最高,如果它按照自己的方式找到了 java.lang.Integer,则下层的 ClassLoader 都不能再装载该类。
这意味着,尽管你自己写了一个 java.lang.Integer 试图取代核心库的类,这是不可能的。因为核心类已由启动类装载器加载,自己写的类根本无法被下层的 ClassLoader 装载。
Package 权限与 ClassLoader 身份
再说说 Package 权限。Java 语言规定,在同一个包中的 class,如果没有修饰符,默认为 Package 权限,包内的 class 都可以互相访问。
但是这还不够准确。确切地说,只有由同一个 ClassLoader 装载的 class 才具有以上的 Package 权限访问能力。
例如,启动类装载器装载了 java.lang.String,而类路径装载器装载了我们自己写的 java.lang.Test。尽管它们包名相同,但由于装载它们的 ClassLoader 不同,它们不能互相访问对方具有 Package 权限的方法。这种机制有效阻止了恶意代码通过自定义类访问核心类的包权限方法,保障了 Java 核心库的安全性。
说明
- 适用版本:本文实验代码基于 JDK 8 及更早版本。在 JDK 9 及之后版本中,
String类的内部实现发生了变更(如移除了基于char[]的特定构造函数),且模块系统(Project Jigsaw)引入了更严格的封装机制。 - 安全限制:禁止用户自定义类使用
java.lang.包名的安全限制在现代 JDK 中依然有效,但具体的异常堆栈或内部 API(如loadClassInternal)可能随版本更新而变化。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/java-de-classloader-yu-package-ji-zhi.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。