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

2022-08-31 11:04:08

我只是尝试使用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

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

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

有什么办法可以做到这一点吗?

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

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


答案 1

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

1. 方法简单

由于警告已打印到默认错误流,因此您只需关闭此流并重定向到 。stderrstdout

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

笔记:

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

2. 无需改变 stderr 的复杂方法

一个好消息是,仍然可以在没有警告的情况下在JDK 9中访问它。解决方案是在不安全API的帮助下在内部重置。sun.misc.UnsafeIllegalAccessLogger

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
    }
}

答案 2

还有另一个选项不需要任何流抑制,并且不依赖于未记录或不支持的 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);

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

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

或者,您可以使用附加 API 进行动态附加,该 API 可通过字节伙伴代理项目(我编写)方便地访问:

exportAndOpen(ByteBuddyAgent.install());

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

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

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


推荐