系列中的上一篇
当前教程
使用开放模块和开放包进行反射访问

系列中的上一篇: Java 中的模块简介

系列中的下一篇: 使用 requires static 的可选依赖项

使用开放模块和开放包进行反射访问

模块系统的强封装也适用于反射,反射已经失去了“超级力量”来打破内部 API。当然,反射是 Java 生态系统的重要组成部分,因此模块系统具有支持反射的特定指令。它允许打开包,这使得它们在编译时不可访问,但在运行时允许深度反射,以及打开整个模块。

注意: 您需要了解 模块系统基础知识 才能充分利用本文。

为什么导出包不适合反射

使类型在模块外部可访问的主要机制是使用模块声明中的 exports 指令导出包含它们的包。这对于反射来说不合适,原因有两个

  1. 导出包使其成为模块公共 API 的一部分。这会邀请其他模块使用它包含的类型,并传达一定程度的稳定性。这通常不适合处理 HTTP 请求或与数据库交互的类。
  2. 一个更技术性的问题是,即使在导出的包中,也只有公共类型的公共成员是可访问的。但是依赖于反射的框架通常会访问非公共类型、构造函数、访问器或字段,这些操作仍然会失败。

开放包(和模块)专门设计用于解决这两个问题。

为反射打开包

模块可以通过在模块声明中添加 opens 指令来打开一个包以进行反射

module com.example.app {
    opens com.example.entities;
}

在编译时,该包完全封装,就像没有该指令一样。这意味着模块com.example.app外部使用来自包com.example.entities 的类型的代码将无法编译。

另一方面,在运行时,该包的类型可用于反射。这意味着反射可以自由地与所有类型和成员交互 - 公共或非公共(使用 AccessibleObject.setAccessible() 像往常一样用于非公共成员)。

正如您可能已经猜到的那样,opens 是专门为反射用例设计的,并且与 exports 的行为截然不同

  • 允许访问所有成员,因此不会影响您关于可见性的决定
  • 防止针对打开的包中的代码进行编译,并且只允许在运行时访问
  • 传达使用基于反射的框架的意图

如果需要,可以导出和打开一个包。

打开模块

如果您有一个包含许多需要暴露给反射的包的大型模块,您可能会发现单独打开每个包很麻烦。虽然没有像 opens com.example.* 这样的通配符,但存在类似的东西。通过在模块声明中的 module 之前放置关键字 open,将创建一个开放模块

open module com.example.entities {
    // ...
}

开放模块会打开它包含的所有包,就像每个包都在 opens 指令中单独使用一样。因此,手动打开更多包没有意义,这就是为什么开放模块中的 opens 指令会导致编译错误。


上次更新: 2021 年 9 月 14 日


系列中的上一篇
当前教程
使用开放模块和开放包进行反射访问

系列中的上一篇: Java 中的模块简介

系列中的下一篇: 使用 requires static 的可选依赖项