[英]How to hide warning "Illegal reflective access" in java 9 without JVM argument?
我只是尝试使用 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 参数/标志转换此警告。
有一些方法可以禁用非法访问警告,但我不建议这样做。
由于警告打印到默认错误流,您可以简单地关闭此流并将stderr
重定向到stdout
。
public static void disableWarning() {
System.err.close();
System.setErr(System.out);
}
注意事项:
System.setErr
来重定向警告消息,因为在 JVM 引导程序早期,对错误流的引用保存在IllegalAccessLogger.warningStream
字段中。一个好消息是sun.misc.Unsafe
仍然可以在 JDK 9 中访问而不会发出警告。 解决方案是借助 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<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 甚至可以解决通常被禁止的自我附加。
我知道没有办法实现你的要求。 正如您所指出的,您需要向 JVM 启动添加命令行选项( --add-opens
,而不是--illegal-access=deny
)。
你写道:
我的目标是避免为最终用户提供额外的说明。 我们有很多用户安装了我们的服务器,这对他们来说是一个很大的不便。
从表面上看,您的要求只会得出该项目尚未为 Java 9 做好准备的结论。它应该诚实地向其用户报告,要完全兼容 Java 9 还需要一点时间。 发布后这么早就完全没问题。
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Main {
@SuppressWarnings("unchecked")
public static void disableAccessWarnings() {
try {
Class unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Object unsafe = field.get(null);
Method putObjectVolatile = unsafeClass.getDeclaredMethod("putObjectVolatile", Object.class, long.class, Object.class);
Method staticFieldOffset = unsafeClass.getDeclaredMethod("staticFieldOffset", Field.class);
Class loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger");
Field loggerField = loggerClass.getDeclaredField("logger");
Long offset = (Long) staticFieldOffset.invoke(unsafe, loggerField);
putObjectVolatile.invoke(unsafe, loggerClass, offset, null);
} catch (Exception ignored) {
}
}
public static void main(String[] args) {
disableAccessWarnings();
}
}
它在 JAVA 11 中对我有用。
还有另一种方法,不基于任何黑客,在上面的任何答案中都没有提到。 然而,它仅适用于在类路径上运行的代码。 所以任何需要支持在 Java 9+ 上运行的库都可以使用这种技术,只要它是从类路径运行的。
它基于这样一个事实,即允许在类路径上运行的代码(即来自未命名模块)自由地动态打开任何模块的包(它只能从目标模块本身或未命名模块完成)。
例如,给定这段代码,访问java.io.Console
类的私有字段:
Field field = Console.class.getDeclaredField("formatter");
field.setAccessible(true);
为了不引起警告,我们必须打开目标模块的包到我们的模块:
if (!ThisClass.class.getModule().isNamed()) {
Console.class.getModule().addOpens(Console.class.getPackageName(), ThisClass.class.getModule());
}
我们还添加了一个检查,我们确实在类路径上运行。
这对我有用
-Djdk.module.illegalAccess=deny
我想出了一种在不使用 Unsafe 或访问任何未记录的 API 的情况下禁用该警告的方法。 它的工作原理是使用反射将System.err
的FilterOutputStream::out
字段设置为 null。
当然,尝试使用反射实际上会抛出我们试图抑制的警告,但我们可以利用并发来解决这个问题:
System.err
以便其他线程无法写入。out
字段上调用setAccessible
线程。 其中一个会在尝试显示警告时挂起,但另一个会完成。out
现场System.err
对空和释放上的锁System.err
。 第二个线程现在将完成,但不会显示警告。System.err
的out
字段。以下代码演示了这一点:
public void suppressWarning() throws Exception
{
Field f = FilterOutputStream.class.getDeclaredField("out");
Runnable r = () -> { f.setAccessible(true); synchronized(this) { this.notify(); }};
Object errorOutput;
synchronized (this)
{
synchronized (System.err) //lock System.err to delay the warning
{
new Thread(r).start(); //One of these 2 threads will
new Thread(r).start(); //hang, the other will succeed.
this.wait(); //Wait 1st thread to end.
errorOutput = f.get(System.err); //Field is now accessible, set
f.set(System.err, null); // it to null to suppress the warning
} //release System.err to allow 2nd thread to complete.
this.wait(); //Wait 2nd thread to end.
f.set(System.err, errorOutput); //Restore System.err
}
}
即使--illegal-access
设置为“警告”或“调试”,此代码也将起作用,因为这些模式不会为同一个调用者多次显示警告。
此外,您还可以将其out
字段设置为自定义 OutputStream,而不是恢复System.err
的原始状态,以便您可以过滤未来的警告。
如果有人想重定向日志消息而不是丢弃它们,这在 Java 11 中对我有用。它替换了非法访问记录器写入的流。
public class AccessWarnings {
public static void redirectToStdOut() {
try {
// get Unsafe
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
Field field = unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Object unsafe = field.get(null);
// get Unsafe's methods
Method getObjectVolatile = unsafeClass.getDeclaredMethod("getObjectVolatile", Object.class, long.class);
Method putObject = unsafeClass.getDeclaredMethod("putObject", Object.class, long.class, Object.class);
Method staticFieldOffset = unsafeClass.getDeclaredMethod("staticFieldOffset", Field.class);
Method objectFieldOffset = unsafeClass.getDeclaredMethod("objectFieldOffset", Field.class);
// get information about the global logger instance and warningStream fields
Class<?> loggerClass = Class.forName("jdk.internal.module.IllegalAccessLogger");
Field loggerField = loggerClass.getDeclaredField("logger");
Field warningStreamField = loggerClass.getDeclaredField("warningStream");
Long loggerOffset = (Long) staticFieldOffset.invoke(unsafe, loggerField);
Long warningStreamOffset = (Long) objectFieldOffset.invoke(unsafe, warningStreamField);
// get the global logger instance
Object theLogger = getObjectVolatile.invoke(unsafe, loggerClass, loggerOffset);
// replace the warningStream with System.out
putObject.invoke(unsafe, theLogger, warningStreamOffset, System.out);
} catch (Throwable ignored) {
}
}
}
您可以在module-info.java
open
包或创建一个open module
。
例如:检查将项目逐步迁移到 Jigsaw 的第5 步和第 6 步
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;
}
sun.misc.Unsafe 仍然可以在没有警告的情况下在 JDK 9 - 15 中访问。 我使用以下方法:
通过'unsafe.defineAnonymousClass(hostClass, useMethodCode)'方法将用户代码注册为宿主类的匿名内部类。 然后,您可以获得 hostClass 权限。
支持:
示例1:
// use jdk.internal.misc.Unsafe.defineClass(), but not --add-opens
Class<T> aClass = Platform.doPrivileged(Unsafe.class, new PrivilegedAction<Class<T>>()
{
@Override
public Class<T> run()
throws Exception
{
Object theInternalUnsafe = Class.forName("jdk.internal.misc.Unsafe")
.getMethod("getUnsafe")
.invoke(null);
return (Class<T>) Class.forName("jdk.internal.misc.Unsafe").getMethod("defineClass",
String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class)
.invoke(theInternalUnsafe, null, classBytes, 0, classBytes.length, classLoader, defaultDomain);
}
});
例子2:
1. Defining interfaces
public static interface Java11PrivateApi
{
/**
* not printWarring
* Make this method get permission to `FilterOutputStream.class`
*/
@Privilege(FilterOutputStream.class)
public OutputStream getSystemOut()
throws Exception;
/**
* not --add-opens=java.base/jdk.internal.ref=ALL-UNNAMED
* Make this method get permission to `ByteBuffer.class`
*/
@Privilege(ByteBuffer.class)
public Object createCleaner(Object ob, Runnable thunk)
throws Exception;
}
2. Implementation method
/* warring: Don't rely on any user classes here */
public static class JvmPrivilegeFunctionDemo
implements Java11PrivateApi
{
@Override
public OutputStream getSystemOut()
throws Exception
{
Field field = FilterOutputStream.class.getDeclaredField("out");
field.setAccessible(true);
return (OutputStream) field.get(System.out);
}
@Override
public Object createCleaner(Object ob, Runnable thunk)
throws Exception
{
Method method = Class.forName("jdk.internal.ref.Cleaner").getDeclaredMethod("create", Object.class, Runnable.class);
method.setAccessible(true);
return method.invoke(null, ob, thunk);
}
}
3. Use
Java11PrivateApi api = Platform.doPrivileged(Java11PrivateApi.class, new JvmPrivilegeFunctionDemo());
// show
OutputStream outputStream = api.getSystemOut();
Assert.assertNotNull(outputStream);
Object cleaner = api.createCleaner(null, () -> System.out.println("hello java 11"));
Assert.assertEquals("jdk.internal.ref.Cleaner", cleaner.getClass().getName());
完整维基: https : //github.com/harbby/gadtry/wiki/Hide-warning-%60Illegal-reflective-access-and-not-open-module%60-in-java-9--without-JVM-argument
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.