什么是非法的反射访问


126

关于Java 9中的非法反射访问,存在很多问题。

现在我找不到,因为所有Google都涌现出来,是人们试图解决错误消息,实际上是非法的反射访问。

所以我的问题很简单:

什么定义了非法的反射访问,什么情况触发了警告?

我已经了解到,它与Java 9中引入的封装原理有关,但是在我找不到解释的情况下,它们如何结合在一起以及触发该警告的原因是什么。


Answers:


53

除了了解模块及其各自程序包之间的访问。我相信它的症结在于模块系统#Relaxed-strong-encapsulation,我只是挑选它的相关部分来尝试回答这个问题。

什么定义了非法的反射访问,什么情况触发了警告?

为了帮助迁移到Java-9,可以放松对模块的强封装。

  • 一个实现可以提供静态访问,即通过编译的字节码。

  • 可以提供一种方法,用其一个或多个模块的一个或多个软件包打开其运行时系统,以在所有未命名模块中进行编码,即在类路径上进行编码。如果以这种方式调用运行时系统,并且这样做,则反射API的某些调用将成功,否则它们将失败。

在这种情况下,实际上您实际上是进行了“非法”反射访问,因为在纯模块化的世界中,您无意进行此类访问。

在所有情况下,它们如何相互关联以及触发该警告的原因是什么?

封装的放松是在运行时由新的启动器选项控制的,--illegal-access在Java9中默认情况下等于permit。该permit模式确保

对任何此类包装的第一次反射式访问操作都会导致发出警告,但在此之后不再发出警告。此单个警告描述了如何启用进一步的警告。此警告不能被抑制。

可以使用值debug(每个此类访问的消息和堆栈跟踪),warn(每个此类访问的消息)和deny(禁用此类操作)值来配置模式。


调试和修复应用程序的几件事是:

  • 运行它--illegal-access=deny来了解并避免在没有模块声明(包括此类指令()或显式使用VM arg )的情况下将软件包从一个模块打开到另一个模块。opens--add-opens
  • 使用jdeps带有--jdk-internals选项的工具,可以识别从已编译代码到JDK内部API的静态引用。

检测到非法反射访问操作时发出的警告消息具有以下形式:

WARNING: Illegal reflective access by $PERPETRATOR to $VICTIM

哪里:

$PERPETRATOR 是类型的标准名称,其中包含调用了相关反射操作的代码以及代码源(即JAR文件路径)(如果有),并且

$VICTIM 是描述正在访问的成员的字符串,包括封闭类型的全限定名称

有关示例警告的问题:= JDK9:发生了非法的反射访问操作。org.python.core.PySystemState

最后一个重要说明,在尝试确保您不会遇到此类警告并日后安全时,您需要做的就是确保您的模块没有进行那些非法的反射访问。:)


21

我找到了一篇有关Java 9模块系统的Oracle 文章

默认情况下,除非是公共类型并且您导出其包,否则其他模块将无法访问模块中的类型。您只公开要公开的软件包。对于Java 9,这也适用于反射。

https://stackoverflow.com/a/50251958/134894所指出AccessibleObject#setAccessible,JDK8和JDK9 之间的区别是有启发性的。具体来说,添加了JDK9

如果满足以下任何条件,则类C中的调用者可以使用此方法来允许访问声明类D的成员:

  • C和D在同一模块中。
  • 在包含D的模块至少导出到包含C的模块的包中,成员是public且D是public。
  • 成员受到静态保护,D在包含D的模块至少导出到包含C的模块的包中是公共的,并且C是D的子类。
  • D在包含D的模块至少打开包含C的模块的包中。未命名和打开的模块中的所有包都向所有模块开放,因此,当D在未命名或打开的模块中时,此方法始终成功。

强调了模块及其导出的重要性(在Java 9中)


2
因此,如果我读过这篇文章,那么正确地修改了导出类中的私有属性就不存在了。只能修改受保护的公共财产。现在,我不太在意Java内部结构的导出,而是对第三方库的更多了解,在某些情况下,我有时需要访问私有变量以将其设置为特定值。如果它将自己定义为模块,那么在该方案中将不再可能,这是正确的吗?
Tschallacka

1
我没有直接的经验,但这是我的理解,并与其他地方提到的文章(jaxenter.com/jdk-9-replace-permit-illegal-access-134180.html)一起阅读案子。用–illegal-access=permit... 启动JVM
ptomli

1
嗯,这将使事情变得更加有趣,当他们决定采用模块方式时,尝试使某些事情起作用。超级有趣的时代。
Tschallacka

1
对于fun
ptomli '18年

我接受了另一个答案,因为它提供了更多的解释,并且更多地是对问题的答案,但可惜我不能接受两个答案。
Tschallacka

13

只需查看setAccessible()用于访问private字段和方法的方法即可:

https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/AccessibleObject.html#setAccessible-boolean-

https://docs.oracle.com/javase/9​​/docs/api/java/lang/reflect/AccessibleObject.html#setAccessible-boolean-

现在,要使此方法起作用,还需要更多条件。不会破坏几乎所有旧版软件的唯一原因是,从普通JAR自动生成的模块是非常宽松的(为所有人打开和导出所有内容)。


1

如果要使用add-open选项,请使用以下命令查找哪个模块提供了哪个软件包->

java --list-modules | tr @ " " | awk '{ print $1 }' | xargs -n1 java -d

模块的名称将显示@,而没有名称的软件包的名称

注意:已通过JDK 11测试

重要提示:显然比不对软件包的提供者进行非法访问更好

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.