使用开放模块和开放包进行反射访问
模块系统的强封装也适用于反射,反射已经失去了“超级力量”来打破内部 API。当然,反射是 Java 生态系统的重要组成部分,因此模块系统具有支持反射的特定指令。它允许打开包,这使得它们在编译时不可访问,但在运行时允许深度反射,以及打开整个模块。
注意: 您需要了解 模块系统基础知识 才能充分利用本文。
为什么导出包不适合反射
使类型在模块外部可访问的主要机制是使用模块声明中的 exports
指令导出包含它们的包。这对于反射来说不合适,原因有两个
- 导出包使其成为模块公共 API 的一部分。这会邀请其他模块使用它包含的类型,并传达一定程度的稳定性。这通常不适合处理 HTTP 请求或与数据库交互的类。
- 一个更技术性的问题是,即使在导出的包中,也只有公共类型的公共成员是可访问的。但是依赖于反射的框架通常会访问非公共类型、构造函数、访问器或字段,这些操作仍然会失败。
开放包(和模块)专门设计用于解决这两个问题。
为反射打开包
模块可以通过在模块声明中添加 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 日