如何在没有JVM参数的情况下隐藏java 9中的“非法reflection访问”警告?
我只是尝试用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.简单的方法
由于警告打印到默认错误流,您只需关闭此流并将stderr
重定向到stdout
。
public static void disableWarning() { System.err.close(); System.setErr(System.out); }
笔记:
- 此方法合并错误和输出流。 在某些情况下,这可能并不理想。
- 您不能仅通过调用
System.setErr
重定向警告消息,因为错误流的引用在JVM引导程序早期保存在IllegalAccessLogger.warningStream
字段中。
2.复杂的方法而不改变stderr
一个好消息是,在没有警告的情况下,仍然可以在JDK 9中访问sun.misc.Unsafe
。 解决方案是在Unsafe 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 } }
还有另一个选项不需要流抑制,也不依赖于未记录或不支持的API。 使用Java代理,可以重新定义模块以导出/打开所需的包。 这个代码看起来像这样:
void exportAndOpen(Instrumentation instrumentation) { Set 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); } }
或者,您可以使用attach-API动态附加,该API可以通过byte-buddy-agent项目 (我创作的)方便地访问:
exportAndOpen(ByteBuddyAgent.install());
在非法访问之前您需要调用它。 请注意,这仅适用于JDK和Linux VM,而如果您需要在其他VM上使用,则需要在命令行上提供Byte Buddy代理作为Java代理。 当您希望在通常安装JDK的测试和开发机器上进行自我附件时,这可能很方便。
正如其他人指出的那样,这应该只是一个中间解决方案,但我完全理解当前的行为通常会破坏日志记录爬虫和控制台应用程序,这就是为什么我自己在生产环境中使用它作为使用Java 9的短期解决方案这么久我没有遇到任何问题。
然而,好处是这个解决方案对于未来的更新是强大的,因为任何操作,甚至动态附件都是合法的。 使用帮助程序,Byte Buddy甚至可以解决通常禁止的自我附着问题。
我知道无法实现你的要求。 正如您所指出的,您需要添加命令行选项 ( --add-opens
,但不是--illegal-access=deny
)到JVM启动。
你写了:
我的目标是避免为最终用户提供额外的说明。 我们有许多用户安装了我们的服务器,这将给他们带来很大的不便。
从它的外观来看,你的要求只能得出项目还没有为Java 9做好准备的结论。它应该诚实地向用户报告,完全兼容Java 9需要花费更多的时间。 在发布后的早期,这完全没问题。
您可以在module-info.java
open
包或创建一个open module
。
例如:结帐步骤5和6将项目迁移到Jigsaw的步骤
module shedlock.example { requires spring.context; requires spring.jdbc; requires slf4j.api; requires shedlock.core; requires shedlock.spring; requires HikariCP; requires shedlock.provider.jdbc.template; requires java.sql; opens net.javacrumbs.shedlockexample to spring.core, spring.beans, spring.context; } open module shedlock.example { requires spring.context; requires spring.jdbc; requires slf4j.api; requires shedlock.core; requires shedlock.spring; requires HikariCP; requires shedlock.provider.jdbc.template; requires java.sql; }