如何解决 Java 9 上的 InaccessibleObjectException(“无法使 {member} 可访问:模块 {A} 不会‘打开 {package}’到 {B}”)?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/41265266/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 23:16:06  来源:igfitidea点击:

How to solve InaccessibleObjectException ("Unable to make {member} accessible: module {A} does not 'opens {package}' to {B}") on Java 9?

javareflectionjava-9

提问by Nicolai

This exception occurs in a wide variety of scenarios when running an application on Java 9. Certain libraries and frameworks (Spring, Hibernate, JAXB) are particularly prone to it. Here's an example from Javassist:

在 Java 9 上运行应用程序时,这种异常发生在各种场景中。某些库和框架(Spring、Hibernate、JAXB)特别容易出现这种情况。这是来自 Javassist 的示例:

java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @1941a8ff
    at java.base/jdk.internal.reflect.Reflection.throwInaccessibleObjectException(Reflection.java:427)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:201)
    at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:192)
    at java.base/java.lang.reflect.Method.setAccessible(Method.java:186)
    at javassist.util.proxy.SecurityActions.setAccessible(SecurityActions.java:102)
    at javassist.util.proxy.FactoryHelper.toClass2(FactoryHelper.java:180)
    at javassist.util.proxy.FactoryHelper.toClass(FactoryHelper.java:163)
    at javassist.util.proxy.ProxyFactory.createClass3(ProxyFactory.java:501)
    at javassist.util.proxy.ProxyFactory.createClass2(ProxyFactory.java:486)
    at javassist.util.proxy.ProxyFactory.createClass1(ProxyFactory.java:422)
    at javassist.util.proxy.ProxyFactory.createClass(ProxyFactory.java:394)

The message says:

消息说:

Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @1941a8ff

无法使受保护的最终 java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) 抛出 java.lang.ClassFormatError 可访问:模块 java.base不会“打开 java.lang”到未命名的模块 @1941a8ff

What can be done to avoid the exception and have the program run successfully?

可以做些什么来避免异常并使程序成功运行?

采纳答案by Nicolai

The exception is caused by the Java Platform Module Systemthat was introduced in Java 9, particularly its implementation of strong encapsulation. It only allows accessunder certain conditions, the most prominent ones are:

该异常是由Java 9 中引入的Java Platform Module System引起的,特别是它的强封装实现。它只允许在某些条件下访问,最突出的是:

  • the type has to be public
  • the owning package has to be exported
  • 类型必须是公开的
  • 必须导出拥有的包

The same limitations are true for reflection, which the code causing the exception tried to use. More precisely the exception is caused by a call to setAccessible. This can be seen in the stack trace above, where the corresponding lines in javassist.util.proxy.SecurityActionslook as follows:

反射也有同样的限制,导致异常的代码试图使用反射。更准确地说,异常是由调用引起的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;
            }
        });
    }
}

To make sure the program runs successfully the module system must be convinced to allow access to the element on which setAccessiblewas called. All information required for that are contained in the exception message but there are a number of mechanismsto achieve this. Which is the best one depends on the exact scenario that caused it.

为了确保程序成功运行,必须说服模块系统允许访问setAccessible被调用的元素。为此所需的所有信息都包含在异常消息中,但有许多机制可以实现这一点。哪个是最好的取决于导致它的确切情况。

Unable to make {member} accessible: module {A} does not 'opens {package}' to {B}

无法使 {member} 可访问:模块 {A} 不会“打开 {package}”到 {B}

By far the most prominent scenarios are the following two:

到目前为止,最突出的场景是以下两种:

  1. A library or framework uses reflection to call into a JDK module. In this scenario:

    • {A}is a Java module (prefixed with java.or jdk.)
    • {member}and {package}are parts of the Java API
    • {B}is a library, framework, or application module; often unnamed module @...
  2. A reflection-based library/framework like Spring, Hibernate, JAXB, ... reflects over application code to access beans, entities, .... In this scenario:

    • {A}is an application module
    • {member}and {package}are part of the application code
    • {B}is either a framework module or unnamed module @...
  1. 库或框架使用反射来调用 JDK 模块。在这种情况下:

    • {A}是一个 Java 模块(以java.或为前缀jdk.
    • {member}并且{package}是 Java API 的一部分
    • {B}是一个库、框架或应用程序模块;经常unnamed module @...
  2. 一个基于反射的库/框架,如 Spring、Hibernate、JAXB ……反射应用程序代码以访问 bean、实体……。在这种情况下:

    • {A}是一个应用模块
    • {member}并且{package}是应用程序代码的一部分
    • {B}是框架模块或 unnamed module @...

Note that some libraries (JAXB, for example) can fail on both accounts so have a close look at what scenario you're in! The one in the question is case 1.

请注意,某些库(例如 JAXB)可能在两个帐户上都失败,因此请仔细查看您所处的场景!问题中的一个是案例1。

1. Reflective Call Into JDK

1.反射调用JDK

The JDK modules are immutable for application developers so we can not change their properties. This leaves only one possible solution: command line flags. With them it is possible to open specific packages up for reflection.

JDK 模块对于应用程序开发人员来说是不可变的,因此我们无法更改它们的属性。这仅留下一种可能的解决方案:命令行标志。有了它们,就可以打开特定的包进行反思。

So in a case like above (shortened)...

所以在上面的情况下(缩短)......

Unable to make java.lang.ClassLoader.defineClass accessible: module java.base does not "opens java.lang" to unnamed module @1941a8ff

无法使 java.lang.ClassLoader.defineClass 可访问:模块 java.base 不会“打开 java.lang”到未命名的模块 @1941a8ff

... the correct fix is to launch the JVM as follows:

...正确的解决方法是按如下方式启动 JVM:

# --add-opens has the following syntax: {A}/{package}={B}
java --add-opens java.base/java.lang=ALL-UNNAMED

If the reflecting code is in a named module, ALL-UNNAMEDcan be replaced by its name.

如果反射代码在一个命名模块中,ALL-UNNAMED可以用它的名字代替。

Note that it can sometimes be hard to find a way to apply this flag to the JVM that will actually execute the reflecting code. This can be particularly tough if the code in question is part of the project's build process and is executed in a JVM that the build tool spawned.

请注意,有时很难找到将这个标志应用到实际执行反射代码的 JVM 的方法。如果有问题的代码是项目构建过程的一部分并且在构建工具生成的 JVM 中执行,这可能会特别困难。

If there are too many flags to be added, you might consider using the encapsulation kill switch--permit-illegal-accessinstead. It will allow all code on the class path to reflect over all named modules. Note that this flag will only work in Java 9!

如果要添加的标志太多,您可以考虑使用封装终止开关--permit-illegal-access。它将允许类路径上的所有代码反映所有命名模块。请注意,此标志 仅适用于 Java 9

2. Reflection Over Application Code

2. 对应用程序代码的反思

In this scenario it is likely that you can edit the module that reflection is used to break into. (If not, you're effectively in case 1.) That means that command line flags are not necessary and instead module {A}'s descriptor can be used to open up its internals. There are a variety of choices:

在这种情况下,您很可能可以编辑反射用于闯入的模块。(如果不是,则在情况 1 中有效。)这意味着不需要命令行标志,而是{A}可以使用module的描述符来打开其内部结构。有多种选择:

  • export the package with exports {package}, which makes it available at compile and run time to all code
  • export the package to the accessing module with exports {package} to {B}, which makes it available at compile and run time but only to {B}
  • open the package with opens {package}, which makes it available at run time (with or without reflection) to all code
  • open the package to the accessing module with opens {package} to {B}, which makes it available at run time (with or without reflection) but only to {B}
  • open the entire module with open module {A} { ... }, which makes all its packages available at run time (with or without reflection) to all code
  • 使用 导出包exports {package},这使其在编译和运行时可用于所有代码
  • 使用 将包导出到访问模块exports {package} to {B},这使其在编译和运行时可用,但只能用于{B}
  • 用 来打开包opens {package},这使它在运行时(有或没有反射)对所有代码可用
  • 使用 将包打开到访问模块opens {package} to {B},这使其在运行时可用(有或没有反射),但只能用于{B}
  • 打开整个模块open module {A} { ... },这使得它的所有包在运行时(有或没有反射)对所有代码可用

See this postfor a more detailed discussion and comparison of these approaches.

有关这些方法的更详细讨论和比较,请参阅此帖子

回答by Alan Bateman

Using --add-opens should be considered a workaround. The right thing is for Spring, Hibernate and other libraries doing illegal access to fix their issues.

使用 --add-opens 应该被视为一种解决方法。正确的做法是让 Spring、Hibernate 和其他库通过非法访问来解决他们的问题。

回答by David T

This is a very challenging problem to solve; and as noted by others, the --add-opens option is only a workaround. The urgency to resolve the underlying issues will only grow once Java 9 becomes publicly available.

这是一个非常具有挑战性的问题。正如其他人所指出的, --add-opens 选项只是一种解决方法。一旦 Java 9 公开可用,解决潜在问题的紧迫性只会增加。

I found myself on this page after receiving this exact Javassist error while testing my Hibernate-based application on Java 9. And since I aim to support Java 7, 8, and 9 on multiple platforms, I struggled to find the best solution. (Note that Java 7 and 8 JVMs will abort immediately when they see an unrecognized "--add-opens" argument on the command line; so this can't be solved with static changes to batch files, scripts, or shortcuts.)

在 Java 9 上测试我的基于 Hibernate 的应用程序时收到这个确切的 Javassist 错误后,我发现自己在这个页面上。由于我的目标是在多个平台上支持 Java 7、8 和 9,我努力寻找最佳解决方案。(请注意,Java 7 和 8 JVM 在命令行上看到无法识别的“--add-opens”参数时将立即中止;因此无法通过对批处理文件、脚本或快捷方式进行静态更改来解决此问题。)

It would be nice to receive official guidance from the authors of mainstream libraries (such as Spring and Hibernate), but with 100 days to go until the currently projected release of Java 9, that advice still seems hard to find.

从主流库(例如 Spring 和 Hibernate)的作者那里得到官方指导会很高兴,但是距离当前预计的 Java 9 发布还有 100 天,这个建议似乎仍然很难找到。

After much experimentation and testing, I was relieved to find a solution for Hibernate:

经过大量的实验和测试,我终于找到了 Hibernate 的解决方案:

  1. Use Hibernate 5.0.0 or higher (earlier versions won't work), and
  2. Request build-time bytecode enhancement(using the Gradle, Maven, or Ant plugins).
  1. 使用 Hibernate 5.0.0 或更高版本(早期版本不起作用),以及
  2. 请求构建时字节码增强(使用 Gradle、Maven 或 Ant 插件)。

This avoids the need for Hibernate to perform Javassist-based class modifications at runtime, eliminating the stack trace shown in the original post.

这避免了 Hibernate 在运行时执行基于 Javassist 的类修改的需要,消除了原始帖子中显示的堆栈跟踪。

HOWEVER, you should thoroughly test your application afterward. The bytecode changes applied by Hibernate at build-time appear to differ from the ones applied at runtime, causing slightly different application behavior. Unit tests in my app that have succeeded for years suddenly failed when I enabled build-time bytecode enhancement. (I had to chase down new LazyInitializationExceptions and other problems.) And the behavior seems to vary from one version of Hibernate to another. Proceed with caution.

但是,您应该在之后彻底测试您的应用程序。Hibernate 在构建时应用的字节码更改似乎与运行时应用的不同,导致应用程序行为略有不同。当我启用构建时字节码增强时,我的应用程序中已经成功多年的单元测试突然失败了。(我不得不追查新的 LazyInitializationExceptions 和其他问题。)而且行为似乎因 Hibernate 的一个版本而异。谨慎行事。

回答by Karol Golec

I had warnings with hibernate 5.

我对 hibernate 5 有警告。

Illegal reflective access by javassist.util.proxy.SecurityActions

I added latest javassist library to dependencies gradle:

我将最新的 javassist 库添加到依赖项 gradle:

compile group: 'org.javassist', name: 'javassist', version: '3.22.0-GA'

This solved my problem.

这解决了我的问题。