Подтвердить что ты не робот

Как скрыть предупреждение "Незаконный рефлексивный доступ" в java 9 без аргумента JVM?

Я просто пытался запустить свой сервер с 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, как указано в похожих вопросах.

4b9b3361

Ответ 1

Есть способы отключить предупреждение о незаконном доступе, хотя я не рекомендую это делать.

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 без предупреждения. Решение состоит из reset internal IllegalAccessLogger с помощью Unsafe 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
    }
}

Ответ 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);

Чтобы получить экземпляр Instrumentation, вы можете написать Java-агент, что довольно просто, и указать его в командной строке (а не в пути к классам), используя -javaagent:myjar.jar. Агент будет содержать только метод premain следующим образом:

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

В качестве альтернативы, вы можете подключиться динамически, используя API присоединения, который удобно доступен для проекта byte-buddy-agent (который я создал):

exportAndOpen(ByteBuddyAgent.install());

который вам нужно будет позвонить до незаконного доступа. Обратите внимание, что это доступно только в JDK и в виртуальных машинах Linux, в то время как вам потребуется указывать агент Byte Buddy в командной строке в качестве агента Java, если он вам нужен на других виртуальных машинах. Это может быть удобно, если вы хотите использовать автоматическое вложение на машинах для тестирования и разработки, где обычно устанавливаются JDK.

Как отмечали другие, это должно служить лишь промежуточным решением, но я полностью понимаю, что текущее поведение часто нарушает сканеры журналирования и консольные приложения, поэтому я сам использовал это в производственных средах в качестве краткосрочного решения для использования Java 9 и До тех пор, пока я не столкнулся с какими-либо проблемами.

Хорошая вещь, однако, состоит в том, что это решение устойчиво к будущим обновлениям, так как любая операция, даже динамическое вложение, является законной. Используя вспомогательный процесс, Байт Бадди даже работает вокруг обычно запрещенной самопривязанности.

Ответ 3

Я не знаю, как достичь того, о чем ты просишь. Как вы указали, вам нужно добавить параметры командной строки (хотя --add-opens, но не --illegal-access=deny) к запуску JVM.

Вы написали:

Моя цель - избежать дополнительных инструкций для конечных пользователей. У нас много пользователей с установленными серверами, и это было бы для них большим неудобством.

Судя по всему, ваши требования оставляют только вывод о том, что проект не готов к Java 9. Он должен честно сообщить своим пользователям, что для полной совместимости с Java 9 требуется немного больше времени. Это совершенно нормально в начале этого релиза.

Ответ 4

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.

Ответ 5

Вы можете open пакеты в module-info.java или создать 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;
}

Ответ 6

Вот что сработало для меня

-Djdk.module.illegalAccess=deny