JEP 181:基于嵌套的访问控制
概括
引入_Nests_,这是一个与 Java 编程语言中现有的嵌套类型概念一致的访问控制上下文。嵌套允许逻辑上属于同一代码实体但被编译为不同类文件的类访问彼此的私有成员,而无需编译器插入可访问性扩展 的桥接方法。
非目标
该 JEP 不涉及大规模的访问控制,例如模块。
动机
许多 JVM 语言支持单个源文件中的多个类(例如 Java 的嵌套类),或者将非类源工件转换为类文件。然而,从用户的角度来看,这些通常被认为都属于“同一类”,因此用户希望它们共享共同的访问控制机制。为了保持这些期望,编译器经常必须通过添加访问桥来扩大private
对成员的访问:对私有成员的调用被编译为对目标类中编译器生成的包私有方法的调用,该方法在package
turn 访问预期的私有成员。这些桥颠覆了封装,稍微增加了已部署应用程序的大小,并且可能使用户和工具感到困惑。一组类文件形成_嵌套_的正式概念,其中_嵌套成员_共享公共访问控制机制,允许以更简单、更安全、更透明的方式直接实现所需的结果。
公共访问控制上下文的概念也出现在其他地方,例如 中的主机类机制Unsafe.defineAnonymousClass()
,其中动态加载的类可以使用主机的访问控制上下文。巢成员资格的正式概念将使该机制具有更坚实的基础(但实际上提供受支持的替代品defineAnonymousClass()
将是一项单独的工作。)
描述
Java 语言规范允许类和接口相互嵌套。在顶级声明 (JLS 7.6) 的范围内,可以嵌套任意数量的类型。这些嵌套类型可以不受限制地相互访问 (JLS 6.6.1),包括私有字段、方法和构造函数。我们可以描述一个顶级类型,加上嵌套在其中的所有类型,形成一个_Nest_,并且一个 Nest 的两个成员被描述为_Nestmates_。
私有访问在包含顶级类型的整个声明中是完整的(无差别的、平坦的)。 (可以将其视为定义一种“迷你包”的顶级类型,在其中授予额外的访问权限,甚至超出向同一 Java 包的其他成员提供的访问权限。)
如今,JVM 访问规则不允许嵌套成员之间的私有访问。为了提供允许的访问,Java 源代码编译器必须引入一定程度的间接访问。例如,对私有成员的调用被编译为对目标类中编译器生成的包私有桥接方法的调用,该调用又调用预期的私有方法。这些访问桥仅根据需要生成,以满足嵌套内请求的成员访问。
JVM 缺乏对嵌套内私有访问的支持的进一步后果是核心反射也拒绝访问。java.lang.reflect.Method.invoke
从一个嵌套对象到另一个嵌套对象的反射方法调用(使用)会抛出异常IllegalAccessError
(除非已禁用访问控制)。鉴于反射调用的行为应与源级调用相同,这是令人惊讶的。类似地,MethodHandle
API 拒绝直接“查找”私有 Nestmate 方法,但提供特殊支持以Lookup.in
允许表达源级别调用语义。
通过将 Nestmates 的概念和相关的访问规则编入 JVM 中,我们简化了 Java 源代码编译器的工作,加强了现有的访问检查,并从核心反射和MethodHandle
API 中删除了令人惊讶的行为。我们还允许未来的增强功能利用“嵌套”概念。例如:
- 在泛型专业化中,每个专业类型都可以创建为泛型类型的 嵌套对象。
- 安全且受支持的 API 替代品
Unsafe.defineAnonymousClass()
可以创建新类作为现有类的嵌套类。 - “密封类”的概念可以通过仅允许嵌套子类来实现。
- 真正的私有嵌套类型可能会受到影响(目前私有嵌套类型是通过包访问定义的)。
嵌套类文件属性
现有的类文件格式定义了InnerClasses
和EnclosingMethod
属性(JVMS 4.7.6 和 4.7.7),以允许 Java 源代码编译器(例如javac
)具体化源级别嵌套关系。每个嵌套类型都编译为自己的类文件,不同的类文件通过这些属性的值“链接”。虽然这些属性足以让 JVM 确定嵌套关系,但它们并不直接适合访问控制,并且本质上与单个 Java 语言概念相关。
为了允许超越简单的 Java 语言嵌套类型的更广泛、更通用的嵌套概念,并且为了有效的访问控制检查,建议修改类文件格式以定义两个新属性。一个嵌套成员(通常是顶级类)被指定为_嵌套主机_,并包含一个属性 ( NestMembers
) 来标识其他静态已知的嵌套成员。其他每个巢成员都有一个属性 ( NestHost
) 来标识其巢宿主。
Nestmate 的 JVM 访问控制
我们将通过在 JVMS 5.4.4 中添加类似以下子句来调整 JVM 的访问规则:
当且仅当以下任一条件成立时,类或接口_D_才能访问字段或方法_R :_
- ...
- R 是私有的,并且在不同的类或接口 C 中声明,并且 C 和 D 是嵌套对象。
要使_C_型和_D_型成为同窝伴侣,它们必须具有相同的巢宿主。如果类型_C_在其属性中列出_D ,则它声称是__D_托管的巢的成员。如果_D_也在其属性中列出了_C_ ,则成员资格有效。 _D_隐含地是它所承载的巢的成员。NestHost``NestMembers
没有NestHost
orNestMembers
属性的类隐式地形成一个嵌套,其自身作为嵌套宿主和唯一的嵌套成员。
放宽的访问规则将影响以下活动期间的访问检查:
- 解析字段和方法(JVMS 5.4.3.2等)
- 解析方法句柄常量 (JVMS 5.4.3.5)
- 解析调用站点说明符 (JVMS 5.4.3.6)
- 通过实例检查 Java 语言访问
java.lang.reflect.AccessibleObject
- 在查询期间检查访问
java.lang.invoke.MethodHandles.Lookup
通过访问规则的更改以及对字节码规则的适当调整,我们可以允许生成调用字节码的简化规则:
invokespecial
对于私有嵌套构造函数,invokevirtual
对于私有非接口、nestmate 实例方法,invokeinterface
对于私有接口,nestmate 实例方法;和invokestatic
对于私有巢友,静态方法
这放宽了必须使用invokespecial
(JVMS 6.5) 调用私有接口方法的现有约束,并且更普遍地允许invokevirtual
用于私有方法调用,而不是添加围绕invokespecial
.可以对MethodHandle
调用的语义进行类似的更改(它反映了调用字节码约束)。