如何在没有 JVM 参数的情况下在 Java 9 中隐藏警告“非法反射访问”?

新手上路,请多包涵

我刚刚尝试使用 Java 9 运行我的服务器并收到下一个警告:

 WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by io.netty.util.internal.ReflectionUtil (file:/home/azureuser/server-0.28.0-SNAPSHOT.jar) to constructor java.nio.DirectByteBuffer(long,int)
WARNING: Please consider reporting this to the maintainers of io.netty.util.internal.ReflectionUtil
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

我想在启动期间隐藏此警告而不添加 --illegal-access=deny 到 JVM 选项。就像是:

 System.setProperty("illegal-access", "deny");

有什么办法吗?

所有建议使用 JVM 选项的相关答案,我想从代码中关闭它。那可能吗?

澄清 - 我的问题是关于从代码而不是通过类似问题中所述的 JVM 参数/标志来转换此警告。

原文由 Dmitriy Dumanskiy 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 778
2 个回答

有一些方法可以禁用非法访问警告,但我不建议这样做。

1. 简单的方法

由于警告打印到默认错误流,您可以简单地关闭此流并将 stderr 重定向到 stdout

 public static void disableWarning() {
    System.err.close();
    System.setErr(System.out);
}

笔记:

  • 这种方法合并了错误和输出流。在某些情况下,这可能是不可取的。
  • 您不能仅通过调用 System.setErr 来重定向警告消息,因为对错误流的引用保存在 IllegalAccessLogger.warningStream JVM 引导程序早期的字段中。

2. 不改变 stderr 的复杂方法

好消息是 sun.misc.Unsafe 仍然可以在 JDK 9 中访问而不会出现警告。解决方案是在不安全 API 的帮助下重置内部 IllegalAccessLogger

 public static void disableWarning() {
    try {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe u = (Unsafe) theUnsafe.get(null);

        Class cls = Class.forName("jdk.internal.module.IllegalAccessLogger");
        Field logger = cls.getDeclaredField("logger");
        u.putObjectVolatile(cls, u.staticFieldOffset(logger), null);
    } catch (Exception e) {
        // ignore
    }
}

原文由 apangin 发布,翻译遵循 CC BY-SA 3.0 许可协议

还有另一个选项不需要任何流抑制,也不依赖于未记录或不受支持的 API。使用 Java 代理,可以重新定义模块以导出/打开所需的包。这个代码看起来像这样:

 void exportAndOpen(Instrumentation instrumentation) {
  Set<Module> unnamed =
    Collections.singleton(ClassLoader.getSystemClassLoader().getUnnamedModule());
  ModuleLayer.boot().modules().forEach(module -> instrumentation.redefineModule(
        module,
        unnamed,
        module.getPackages().stream().collect(Collectors.toMap(
          Function.identity(),
          pkg -> unnamed
        )),
        module.getPackages().stream().collect(Collectors.toMap(
           Function.identity(),
           pkg -> unnamed
         )),
         Collections.emptySet(),
         Collections.emptyMap()
  ));
}

您现在可以在没有警告的情况下运行任何非法访问,因为您的应用程序包含在未命名的模块中,例如:

 Method method = ClassLoader.class.getDeclaredMethod("defineClass",
    byte[].class, int.class, int.class);
method.setAccessible(true);

为了掌握 Instrumentation 实例,您可以编写一个非常简单的 Java 代理,并使用 -javaagent:myjar.jar 在命令行(而不是类路径)上指定它。该代理将仅包含一个 premain 方法,如下所示:

 public class MyAgent {
  public static void main(String arg, Instrumentation inst) {
    exportAndOpen(inst);
  }
}

或者,您可以使用 byte-buddy-agent 项目(我编写的)可以方便地访问的附加 API 动态附加:

 exportAndOpen(ByteBuddyAgent.install());

您需要在非法访问之前调用它。请注意,这仅在 JDK 和 Linux VM 上可用,而如果您需要在其他 VM 上使用,则需要在命令行上将 Byte Buddy 代理作为 Java 代理提供。当您希望在通常安装 JDK 的测试和开发机器上进行自我连接时,这会很方便。

正如其他人指出的那样,这只能作为一种中间解决方案,但我完全理解当前的行为经常会破坏日志记录爬虫和控制台应用程序,这就是为什么我自己在生产环境中使用它作为使用 Java 9 和这么久我没有遇到任何问题。

然而,好消息是这个解决方案对于未来的更新是健壮的,因为任何操作,甚至动态附件都是合法的。使用辅助进程,Byte Buddy 甚至可以绕过通常被禁止的自我依恋。

原文由 Rafael Winterhalter 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题