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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/46454995/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 02:21:58  来源:igfitidea点击:

How to hide warning "Illegal reflective access" in java 9 without JVM argument?

javajvmnettyjava-9

提问by Dmitriy Dumanskiy

I just tried to run my server with Java 9 and got next warning:

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

I would like to hide this warning without adding --illegal-access=denyto JVM options during start. Something like:

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

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

Is there any way to do that?

有没有办法做到这一点?

All related answers suggesting to use JVM options, I would like to turn off this from code. Is that possible?

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

To clarify - my question is about turning this warning from the code and not via JVM arguments/flags as stated in similar questions.

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

采纳答案by apangin

There are ways to disable illegal access warning, though I do not recommend doing this.

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

1. Simple approach

1. 简单的方法

Since the warning is printed to the default error stream, you can simply close this stream and redirect stderrto stdout.

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

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

Notes:

笔记:

  • This approach merges error and output streams. That may not be desirable in some cases.
  • You cannot redirect warning message just by calling System.setErr, since the reference to error stream is saved in IllegalAccessLogger.warningStreamfield early at JVM bootstrap.
  • 这种方法合并错误和输出流。在某些情况下,这可能是不可取的。
  • 您不能仅通过调用来重定向警告消息System.setErr,因为错误流的引用IllegalAccessLogger.warningStream在 JVM 引导程序的早期保存在字段中。

2. Complicated approach without changing stderr

2. 不改变stderr的复杂方法

A good news is that sun.misc.Unsafecan be still accessed in JDK 9 without warnings. The solution is to reset internal IllegalAccessLoggerwith the help of Unsafe API.

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

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

回答by TheKojuEffect

You can openpackages in module-info.javaor create an open module.

您可以open打包module-info.java或创建一个open module.

For Example: Checkout Step 5 and 6 of Migrating Your Project to Jigsaw Step by Step

例如:检查将项目逐步迁移到 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;
}

回答by Nicolai

I know of no way to achieve what you are asking for. As you have pointed out, you would need to add command line options(--add-opens, though, not --illegal-access=deny) to the JVM launch.

我知道没有办法实现你的要求。正如您所指出的,您需要向JVM 启动添加命令行选项--add-opens虽然,不是--illegal-access=deny)。

You wrote:

你写了:

My goal is to avoid the additional instructions for end users. We have many users with our servers installed and that would be a big inconvenience for them.

我的目标是避免为最终用户提供额外的说明。我们有很多用户安装了我们的服务器,这对他们来说是一个很大的不便。

By the looks of it, your requirements only leave the conclusion that the project is not ready for Java 9. It should honestly report to its users that it takes a little more time to be fully Java 9 compatible. That's totally ok this early after the release.

从表面上看,您的要求只会得出该项目尚未为 Java 9 做好准备的结论。它应该诚实地向其用户报告,要完全兼容 Java 9 还需要一点时间。发布后这么早就完全没问题。

回答by Rafael Winterhalter

There is another option that does not come with any need for stream suppression and that does not rely on undocumented or unsupported APIs. Using a Java agent, it is possible to redefine modules to export/open the required packages. The code for this would look something like this:

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

You can now run any illegal access without the warning as your application is contained in the unnamed module as for example:

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

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

In order to get hold of the Instrumentationinstance, you can either write a Java agentwhat is quite simple and specify it on the command line (rather than the classpath) using -javaagent:myjar.jar. The agent would only contain an premainmethod as follows:

为了获取Instrumentation实例,您可以编写一个非常简单的Java 代理,并使用-javaagent:myjar.jar. 代理将只包含premain如下方法:

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

Alternatively, you can attach dynamically using the attach API which is made accessible conveniently by the byte-buddy-agentproject(which I authored):

或者,你可以动态地连接使用由制造方便访问的API附加字节好友代理的项目(这是我创作的):

exportAndOpen(ByteBuddyAgent.install());

which you would need to call prior to the illegal access. Note that this is only available on JDKs and on Linux VM whereas you would need to supply the Byte Buddy agent on the command line as a Java agent if you needed it on other VMs. This can be convenient when you want the self-attachment on test and development machines where JDKs are typically installed.

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

As others pointed out, this should only serve as an intermediate solution but I fully understand that the current behavior often breaks logging crawlers and console apps which is why I have used this myself in production environments as a short-term solution to using Java 9 and so long I did not encounter any problems.

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

The good thing, however, is that this solution is robust towards future updates as any operation, even the dynamic attachment is legal. Using a helper process, Byte Buddy even works around the normally forbidden self-attachment.

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

回答by Gan

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();
    }
}

It works for me in JAVA 11.

它在 JAVA 11 中对我有用。

回答by Ich

Here is what worked for me

这对我有用

-Djdk.module.illegalAccess=deny

回答by Ivan

There is another method, not based on any hacks, that was not mentioned in any of the answers above. It works however only for code running on classpath. So any library that needs to support running on Java 9+ could use this technique, as long as it is run from classpath.

还有另一种方法,不基于任何黑客,在上面的任何答案中都没有提到。然而,它仅适用于在类路径上运行的代码。所以任何需要支持在 Java 9+ 上运行的库都可以使用这种技术,只要它是从类路径运行的。

It is based on a fact that it is allowed for code running on classpath (i.e. from the unnamed module) to freely dynamically open packages of any module (it can be done only from the target module itself, or from the unnamed module).

它基于这样一个事实,即允许在类路径上运行的代码(即来自未命名模块)自由地动态打开任何模块的包(它只能从目标模块本身或未命名模块完成)。

For example, given this code, accessing a private field of java.io.Consoleclass:

例如,给定此代码,访问java.io.Console类的私有字段:

Field field = Console.class.getDeclaredField("formatter");
field.setAccessible(true);

In order not to cause the warning, we have to open the target module's package to our module:

为了不引起警告,我们必须打开目标模块的包到我们的模块:

if (!ThisClass.class.getModule().isNamed()) {
    Console.class.getModule().addOpens(Console.class.getPackageName(), ThisClass.class.getModule());
}

We've also added a check that we're indeed running on classpath.

我们还添加了一个检查,我们确实在类路径上运行。

回答by Leandro

I've come up with a way to disable that warning without using Unsafe nor accessing any undocumented APIs. It works by using Reflection to set the FilterOutputStream::outfield of System.errto null.

我想出了一种在不使用 Unsafe 或访问任何未记录的 API 的情况下禁用该警告的方法。它的工作原理是使用反射将 的FilterOutputStream::out字段设置System.err为空。

Of course, attempting to use Reflection will actually throw the warning we're trying to supress, but we can exploit concurrency to work around that:

当然,尝试使用反射实际上会抛出我们试图抑制的警告,但我们可以利用并发来解决这个问题:

  1. Lock System.errso that no other thread can write to it.
  2. Spawn 2 threads that call setAccessibleon the outfield. One of them will hang when trying to show the warning, but the other will complete.
  3. Set the outfield of System.errto null and release the lock on System.err. The second thread will now complete, but no warning will be displayed.
  4. Wait for the second thread to end and restore the outfield of System.err.
  1. 锁定System.err以便其他线程无法写入。
  2. 生成 2 个调用setAccessibleout字段的线程。其中一个会在尝试显示警告时挂起,但另一个会完成。
  3. 将 的out字段设置System.err为 null 并释放对 的锁定System.err。第二个线程现在将完成,但不会显示警告。
  4. 等待第二个线程结束并恢复 的out字段System.err

The following code demostrates this:

以下代码演示了这一点:

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

This code will work even if --illegal-accessis set to "warn" or "debug", since these modes don't show the warning more than once for the same caller.

即使--illegal-access设置为“warn”或“debug”,这段代码也能工作,因为这些模式不会对同一个调用者多次显示警告。

Also, instead of restoring the original state of System.err, you can also set its outfield to a custom OutputStream, so you can filter future warnings.

此外,System.err您还可以将其out字段设置为自定义 OutputStream ,而不是恢复 的原始状态,以便过滤未来的警告。