该异常是由以下原因引起的Java平台模块系统Java 9 中引入了这一点,特别是其强封装的实现。
它只允许access在某些条件下,最突出的是:
同样的限制也适用于反射,导致异常的代码尝试使用反射。
更准确地说,异常是由调用引起的setAccessible。
这可以在上面的堆栈跟踪中看到,其中相应的行javassist.util.proxy.SecurityActions
如下所示:
static void setAccessible(final AccessibleObject ao,
final boolean accessible) {
if (System.getSecurityManager() == null)
ao.setAccessible(accessible); // <~ Dragons
else {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
ao.setAccessible(accessible); // <~ moar Dragons
return null;
}
});
}
}
为了确保程序成功运行,必须说服模块系统允许访问其上的元素setAccessible
被称为。
所需的所有信息都包含在异常消息中,但有多项机制为了达成这个。
哪一种是最好的取决于导致它的具体场景。
无法使 {member} 可访问:模块 {A} 未向 {B} “打开 {package}”
到目前为止,最突出的场景有以下两种:
-
库或框架使用反射来调用 JDK 模块。
在这种情况下:
-
{A}
是一个 Java 模块(前缀为java.
or jdk.
)
-
{member}
and {package}
是 Java API 的一部分
-
{B}
是一个库、框架或应用程序模块;经常unnamed module @...
-
基于反射的库/框架(如 Spring、Hibernate、JAXB...)通过应用程序代码反射来访问 Bean、实体...
在这种情况下:
-
{A}
是一个应用程序模块
-
{member}
and {package}
是应用程序代码的一部分
-
{B}
是一个框架模块或unnamed module @...
请注意,某些库(例如 JAXB)可能在两个帐户上都失败,因此请仔细查看您所处的场景!
问题中的情况是情况1。
1.反射调用JDK
JDK 模块对于应用程序开发人员来说是不可变的,因此我们无法更改它们的属性。
这就只剩下一种可能的解决方案:命令行标志。
使用它们可以打开特定的包进行反思。
所以在像上面这样的情况下(缩短)......
无法使 java.lang.ClassLoader.defineClass 可访问:模块 java.base 不会向未命名模块“打开 java.lang”@1941a8ff
...正确的修复方法是按如下方式启动 JVM:
# --add-opens has the following syntax: {A}/{package}={B}
java --add-opens java.base/java.lang=ALL-UNNAMED
如果反射代码位于命名模块中,ALL-UNNAMED
可以用它的名字来代替。
请注意,有时很难找到一种方法将此标志应用于实际执行反射代码的 JVM。
如果相关代码是项目构建过程的一部分并且在构建工具生成的 JVM 中执行,那么这可能会特别困难。
如果要添加的标志太多,您可以考虑使用封装终止开关 --permit-illegal-access
反而。它将允许类路径上的所有代码反映整体命名模块。注意这个标志仅适用于 Java 9!
2. 对应用程序代码的反思
在这种情况下,您很可能可以编辑使用反射侵入的模块。
(如果没有,那么您实际上处于情况 1 中。)这意味着命令行标志不是必需的,而是模块{A}
的描述符可用于打开其内部结构。
有多种选择:
- 导出包
exports {package}
,这使得它在编译和运行时可用于所有代码
- 将包导出到访问模块
exports {package} to {B}
,这使得它在编译和运行时可用,但仅限于{B}
- 打开包装
opens {package}
,这使得它在运行时(有或没有反射)对所有代码可用
- 打开访问模块的包
opens {package} to {B}
,这使得它在运行时可用(有或没有反射),但仅限于{B}
- 打开整个模块
open module {A} { ... }
,这使得它的所有包在运行时(有或没有反射)对所有代码可用
See 这个帖子对这些方法进行更详细的讨论和比较。