Как решить исключение UnaccessibleObjectException ("Не удается сделать доступным {member}: модуль {A} не "открывает {package}" в {B}") на Java 9?
Это исключение возникает в самых разных сценариях при запуске приложения на Java 9. Некоторые библиотеки и фреймворки (Spring, Hibernate, JAXB) особенно подвержены ему. Вот пример из Javassist:
java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @1941a8ff
at java.base/jdk.internal.reflect.Reflection.throwInaccessibleObjectException(Reflection.java:427)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:201)
at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:192)
at java.base/java.lang.reflect.Method.setAccessible(Method.java:186)
at javassist.util.proxy.SecurityActions.setAccessible(SecurityActions.java:102)
at javassist.util.proxy.FactoryHelper.toClass2(FactoryHelper.java:180)
at javassist.util.proxy.FactoryHelper.toClass(FactoryHelper.java:163)
at javassist.util.proxy.ProxyFactory.createClass3(ProxyFactory.java:501)
at javassist.util.proxy.ProxyFactory.createClass2(ProxyFactory.java:486)
at javassist.util.proxy.ProxyFactory.createClass1(ProxyFactory.java:422)
at javassist.util.proxy.ProxyFactory.createClass(ProxyFactory.java:394)
В сообщении говорится:
Не удается создать защищенный конечный java.lang.Класс java.lang.ClassLoader.defineClass(java.lang.Строка, байт[], int, int, java.безопасность.ProtectionDomain) выдает java.lang.ClassFormatError доступен: модуль java.base не "открывает java.lang" для неназванного модуля @1941a8ff
Что можно сделать, чтобы избежать исключения и обеспечить успешный запуск программы?
Переведено автоматически
Ответ 1
Исключение вызвано системой модулей платформы Java, которая была представлена в Java 9, в частности, ее реализацией строгой инкапсуляции. Он разрешает доступ только при определенных условиях, наиболее важными из которых являются:
- тип должен быть общедоступным
- пакет-владелец должен быть экспортирован
Те же ограничения справедливы для отражения, которое пытался использовать код, вызывающий исключение. Точнее, исключение вызвано вызовом setAccessible
. Это можно увидеть в трассировке стека выше, где соответствующие строки в javassist.util.proxy.SecurityActions
выглядят следующим образом:
static void setAccessible(final AccessibleObject ao,
final boolean accessible) {
if (System.getSecurityManager() == null)
ao.setAccessible(accessible); // <~ Dragons
else {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
ao.setAccessible(accessible); // <~ moar Dragons
return null;
}
});
}
}
Чтобы убедиться, что программа успешно запущена, система модулей должна быть убеждена разрешить доступ к элементу, для которого setAccessible
был вызван.
Вся необходимая для этого информация содержится в сообщении об исключении, но для достижения этого существует ряд механизмов.
Какой из них является лучшим, зависит от точного сценария, который его вызвал.
Не удается сделать {member} доступным: модуль {A} не "открывает {package}" в {B}
На сегодняшний день наиболее известными сценариями являются следующие два:
Библиотека или фреймворк использует отражение для вызова модуля JDK. В этом сценарии:
{A}
является модулем Java (с префиксомjava.
илиjdk.
){member}
и{package}
являются частями Java API{B}
является библиотекой, фреймворком или модулем приложения; частоunnamed module @...
Библиотека / фреймворк на основе отражения, такие как Spring, Hibernate, JAXB, ... отражает код приложения для доступа к компонентам, сущностям... В этом сценарии:
{A}
является модулем приложения{member}
и{package}
являются частью кода приложения{B}
является либо модулем фреймворка, либоunnamed module @...
Обратите внимание, что некоторые библиотеки (например, JAXB) могут давать сбой в обеих учетных записях, поэтому внимательно посмотрите, в каком сценарии вы находитесь! Тот, что в вопросе, относится к случаю 1.
1. Рефлексивный вызов в JDK
Модули JDK неизменяемы для разработчиков приложений, поэтому мы не можем изменять их свойства. Остается только одно возможное решение: флаги командной строки. С их помощью можно открывать определенные пакеты для отражения.
Поэтому в случае, подобном приведенному выше (сокращенно)...
Не удается сделать доступным java.lang.ClassLoader.defineClass: модуль java.base не "открывает java.lang" для неназванного модуля @1941a8ff
... the correct fix is to launch the JVM as follows:
# --add-opens has the following syntax: {A}/{package}={B}
java --add-opens java.base/java.lang=ALL-UNNAMED
If the reflecting code is in a named module, ALL-UNNAMED
can be replaced by its name.
Note that it can sometimes be hard to find a way to apply this flag to the JVM that will actually execute the reflecting code.
This can be particularly tough if the code in question is part of the project's build process and is executed in a JVM that the build tool spawned.
If there are too many flags to be added, you might consider using the encapsulation kill switch --permit-illegal-access
instead. It will allow all code on the class path to reflect overall named modules. Note that this flag will only work in Java 9!
2. Отражение над кодом приложения
В этом сценарии, вероятно, вы можете отредактировать модуль, для взлома которого используется отражение. (Если нет, вы фактически находитесь в случае 1.) Это означает, что флаги командной строки не нужны, и вместо этого дескриптор модуля {A}
можно использовать для открытия его внутренних компонентов. Существует множество вариантов:
- экспортируйте пакет с помощью
exports {package}
, что делает его доступным во время компиляции и выполнения для всего кода - экспортируйте пакет в модуль доступа с помощью
exports {package} to {B}
, что делает его доступным во время компиляции и выполнения, но только для{B}
- открывает пакет с помощью
opens {package}
, что делает его доступным во время выполнения (с отражением или без него) для всего кода - открывает пакет для модуля доступа с помощью
opens {package} to {B}
, что делает его доступным во время выполнения (с отражением или без него), но только для{B}
- открывает весь модуль с помощью
open module {A} { ... }
, что делает все его пакеты доступными во время выполнения (с отражением или без него) для всего кода
Смотрите Этот пост для более подробного обсуждения и сравнения этих подходов.
Ответ 2
Просто недавний отзыв
Многие предложения по решению этой проблемы связаны с опцией запуска виртуальной машины --illegal-access
.
Согласно Oracle, с JEP 403 (link1) и JEP 403 (link2), которые было решено доставлять с JDK 17 и далее , опция запуска --illegal-access
перестанет работать!
Краткое описание Строго инкапсулирует все внутренние элементы JDK, за исключением критических внутренних API, таких как sun.разное.Небезопасный. Больше не будет возможности ослабить строгую инкапсуляцию внутренних элементов с помощью одной опции командной строки, как это было возможно в JDK с 9 по JDK 16.
И
С этим изменением конечные пользователи больше не смогут использовать опцию --illegal-access, чтобы разрешить доступ к внутренним элементам JDK. (Список затронутых пакетов доступен здесь.) Пакеты sun.misc и sun.reflect по-прежнему будут экспортироваться модулем jdk.unsupported и по-прежнему будут открыты, чтобы код мог получить доступ к их закрытым элементам через отражение. Никакие другие пакеты JDK не будут открыты таким образом.
По-прежнему можно будет использовать параметр командной строки --add-opens или атрибут манифеста JAR-файла Add-Opens для открытия определенных пакетов.
Поэтому следующее решение будет продолжать работать
# --add-opens has the following syntax: {A}/{package}={B}
java --add-opens java.base/java.lang=ALL-UNNAMED
Но решение с --illegal-access
перестанет работать с JDK 17
и далее.
Ответ 3
Все еще можно попробовать использовать более старую версию JKD.
Для Eclipse вам нужно сделать 2 вещи. Перейдите к
Window -> Preference -> java -> Compiler Установить уровень соответствия компилятора определенной версии В моем случае для версии Eclipse было установлено значение JDK 16, я вернул его на 1.8, поскольку мой код был написан на 1.8
Окно -> Настройки -> Java -> Установленные JRES. Добавьте путь установки JRE (выберите стандартную виртуальную машину)
У меня все прошло гладко..
Ответ 4
Использование --add-opens следует рассматривать как обходной путь. Правильно, если Spring, Hibernate и другие библиотеки используют незаконный доступ для устранения своих проблем.