Как скрыть предупреждение "Незаконный рефлексивный доступ" в 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, как указано в похожих вопросах.
Ответы
Ответ 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