Как решить InaccessibleObjectException ("Невозможно сделать {член} доступным: модуль {A} не" открывает {пакет} "для {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.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) создает доступ к java.lang.ClassFormatError: модуль java.base не "открывает java.lang" для неназванного модуля @1941a8ff
Что можно сделать, чтобы избежать исключения и успешно запустить программу?
22 ответа
Исключение вызвано модульной системой платформы 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
назывался. Вся необходимая для этого информация содержится в сообщении об исключении, но для этого есть ряд механизмов. Что является лучшим, зависит от точного сценария, который вызвал это.
Невозможно сделать {член} доступным: модуль {A} не 'открывает {пакет}' для {B}
Безусловно, наиболее выдающимися сценариями являются следующие два:
Библиотека или фреймворк использует отражение для вызова модуля JDK. В этом сценарии:
{A}
является модулем Java (с префиксомjava.
или жеjdk.
){member}
а также{package}
являются частью Java API{B}
библиотека, фреймворк или модуль приложения; довольно частоunnamed module @...
Основанная на отражении библиотека / инфраструктура, такая как Spring, Hibernate, JAXB, ... отражает код приложения для доступа к бинам, сущностям, .... В этом сценарии:
{A}
это модуль приложения{member}
а также{package}
являются частью кода приложения{B}
это либо модуль фреймворка, либоunnamed module @...
Обратите внимание, что некоторые библиотеки (например, JAXB) могут не работать на обеих учетных записях, поэтому внимательно изучите сценарий, в котором вы находитесь! В вопросе один случай.
1. Светоотражающий призыв в JDK
Модули JDK являются неизменными для разработчиков приложений, поэтому мы не можем изменять их свойства. Это оставляет только одно возможное решение: флаги командной строки. С ними можно открыть определенные пакеты для размышления.
Так что в случае, как указано выше (сокращено)...
Невозможно сделать java.lang.ClassLoader.defineClass доступным: модуль java.base не "открывает java.lang" для неназванного модуля @ 1941a8ff
... правильное решение - запустить JVM следующим образом:
# --add-opens has the following syntax: {A}/{package}={B}
java --add-opens java.base/java.lang=ALL-UNNAMED
Если отражающий код находится в именованном модуле, ALL-UNNAMED
можно заменить его именем.
Обратите внимание, что иногда бывает трудно найти способ применить этот флаг к JVM, который фактически будет выполнять отражающий код. Это может быть особенно сложно, если рассматриваемый код является частью процесса сборки проекта и выполняется в JVM, созданной инструментом сборки.
Если добавлено слишком много флагов, вы можете использовать переключатель уничтожения инкапсуляции --permit-illegal-access
вместо. Это позволит всему коду на пути к классам отражаться над всеми именованными модулями. Обратите внимание, что этот флаг будет работать только в Java 9!
2. Размышление над кодом приложения
В этом случае вполне вероятно, что вы можете редактировать модуль, в который используется отражение. (Если нет, то вы фактически в случае 1.) Это означает, что флаги командной строки не нужны, и вместо этого модуль {A}
Дескриптор может быть использован, чтобы открыть его внутренности. Есть множество вариантов:
- экспортировать пакет с
exports {package}
, что делает его доступным во время компиляции и выполнения для всего кода - экспортировать пакет в модуль доступа с
exports {package} to {B}
, что делает его доступным во время компиляции и запуска, но только для{B}
- открыть пакет с
opens {package}
, что делает его доступным во время выполнения (с или без отражения) для всего кода - открыть пакет для модуля доступа с
opens {package} to {B}
, что делает его доступным во время выполнения (с или без отражения), но только для{B}
- открыть весь модуль с
open module {A} { ... }
, который делает все свои пакеты доступными во время выполнения (с или без отражения) для всего кода
Смотрите этот пост для более подробного обсуждения и сравнения этих подходов.
Только недавний отзыв
Многие предложения по решению этой проблемы связаны с опцией запуска vm.
Согласно Oracle, с JEP 403 (link1) и JEP 403 (link2), которые было решено поставлять с JDK 17 и далее , опция запуска перестанет работать!
Резюме Строго инкапсулируйте все внутренние элементы JDK, за исключением критически важных внутренних API, таких как sun.misc.Unsafe. Больше невозможно будет ослабить строгую инкапсуляцию внутренних элементов с помощью единственной опции командной строки, как это было возможно в 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
и далее.
Для Eclipse вам нужно две вещи: перейти к
Window -> Preference -> java -> CompilerSet Compiler Compliance Level to Specific version В моем случае версия Eclipse была установлена на JDK 16, я вернул ее к 1.8, поскольку мой код был написан в 1.8
Окно -> Настройки -> Java -> Установленные JRE. Добавьте путь установки JRE (выберите стандартную виртуальную машину)
У меня все прошло гладко ..
Использование --add-opens должно рассматриваться как обходной путь. Правильно, что Spring, Hibernate и другие библиотеки делают нелегальный доступ для решения своих проблем.
После модуляризации Java доступ к некоторым внутренним классам jdk будет недоступен, поэтому они сообщат об ошибках напрямую. Итак, вам нужно добавить следующую опцию в конфигурацию виртуальной машины при выполнении программы Java:
Для пользователей Intellij выполните следующие действия: отредактируйте конфигурацию запуска/отладки -> добавьте следующую опцию в параметры виртуальной машины:
--add-opens java.base/java.lang=ALL-UNNAMED
Эта ссылка может быть полезна: обновление до Java 17
Добавьте --illegal-access = warn и --add-opens java.base / java.lang = ALL-UNNAMED в свой eclipse.ini
Это очень сложная проблема для решения; и, как отмечали другие, опция --add-opens - это только обходной путь. Срочность решения основных проблем будет расти только после того, как Java 9 станет общедоступной.
Я оказался на этой странице после получения именно этой ошибки Javassist во время тестирования моего приложения на основе Hibernate на Java 9. И так как я стремлюсь поддерживать Java 7, 8 и 9 на нескольких платформах, я изо всех сил пытался найти лучшее решение. (Обратите внимание, что Java 7 и 8 JVM будут немедленно прерваны, когда они увидят нераспознанный аргумент "--add-opens" в командной строке; поэтому это невозможно решить с помощью статических изменений в пакетных файлах, сценариях или ярлыках.)
Было бы неплохо получить официальное руководство от авторов основных библиотек (таких как Spring и Hibernate), но до выхода намеченного в настоящее время Java 9 за 100 дней этот совет все еще кажется трудным найти.
После долгих экспериментов и испытаний я с облегчением нашел решение для Hibernate:
- Используйте Hibernate 5.0.0 или выше (более ранние версии не будут работать), и
- Запросить улучшение байт-кода во время сборки (используя плагины Gradle, Maven или Ant).
Это устраняет необходимость в Hibernate для выполнения модификаций классов на основе Javassist во время выполнения, устраняя трассировку стека, показанную в исходном посте.
ОДНАКО, вы должны тщательно протестировать свое приложение после этого. Изменения байт-кода, применяемые Hibernate во время сборки, по-видимому, отличаются от изменений, применяемых во время выполнения, что вызывает немного другое поведение приложения. Модульные тесты в моем приложении, которые были успешными в течение многих лет, внезапно потерпели неудачу, когда я включил улучшение байт-кода во время сборки. (Мне пришлось искать новые исключения LazyInitializationException и другие проблемы.) И поведение, похоже, меняется от одной версии Hibernate к другой. Действовать с осторожностью.
Перейдите в файл eclipse.ini в каталоге Eclipse и добавьте следующее. Это решит проблему.
--illegal-access= предупреждать
--add-opens java.base/java.lang=ALL-UNNAMED
У меня была такая же проблема в 2021 году при использовании openJDK 1.8 и STS 4.
Window => Preferences => Java => Installed JREs.
Я добавил новую JRE (упомянутую ниже), используя опцию добавления, перешел в папку openJdk и выбрал OK. Сделайте новый JDK по умолчанию. Нажмите «Применить» и «закрыть».
/usr/lib/jvm/java-1.8.0-openjdk-amd64/jre
Оно работало завораживающе:)
В 2021 году проблема появилась только тогда, когда я использовал версию 16 языка OpenJDK. Она сработала, когда я перешел на более раннюю версию. Я предполагаю, что это потому, что OpenJDK сделал это: JEP 396: Strongly Encapsulate JDK Internals по умолчанию
У меня были предупреждения с Hibernate 5.
Illegal reflective access by javassist.util.proxy.SecurityActions
Я добавил последнюю библиотеку javassist в gradle зависимостей:
compile group: 'org.javassist', name: 'javassist', version: '3.22.0-GA'
Это решило мою проблему.
Я столкнулся с той же проблемой после импорта существующего проекта Springboot на основе JDK1.8 в SpringTestSuite4. Когда я запустил приложение на встроенном сервере Tomcat, я получил эту ошибку
java.lang.reflect.InaccessibleObjectException: невозможно сделать защищенный окончательный java.lang.Class java.lang.ClassLoader.defineClass (java.lang.String, byte [], int, int, java.security.ProtectionDomain) бросает java.lang.ClassFormatError доступен: модуль java.base не «открывает java.lang» для безымянного модуля @140c9f39%09
Я добавил предустановленную JDK1.8 JRE со своего компьютера в SpringTestSuite4 в разделе «Установленная JRE» в разделе «Окно» -> «Настройки» -> «Java» и сделал добавленную JRE по умолчанию. Затем я нажал «Применить», а затем «Закрыть».
У меня это сработало.
Сегодня я потратил много времени, пытаясь обойти добавление опции --add-opens JVM. Обновление до SpringBoot 2.6.4 (которое, если вы используете управление зависимостями, обновится до Spring Framework 5.3.16) устранило проблему без добавления опции JVM.
/questions/60073085/isklyuchenie-im-phasing-pri-vyizove-aop/61420575#61420575
В затмении:
Windows -> Preferences -> Java -> Installed JREs
В открывшемся мастере нажмите Добавить, выберите
Standard VM
и нажмите
Next
, а затем перейдите к пути установленной Java в программных файлах. Например, для меня это было
C:\Program Files\Java\jre1.8.0_301\lib
.
Нажмите «Готово», затем выберите только что добавленную JR.
Right-click on your Project -> Build Path -> Configure Build Path
В открывшемся мастере перейдите к
Libraries
вкладка и нажмите на
Add Library
затем выберите
JRE System Library
Нажмите "Далее. Выбирать
Alternate JRE
а затем выберите JRE, добавленную ранее, из раскрывающегося списка. Щелкните Готово. Перейти к
Order and Export
вкладку и выберите добавленную JRE и переместите ее над другой системной библиотекой, которая вызвала проблему. Выберите применить и закрыть. Готово.
Я столкнулся с аналогичной проблемой при запуске SornarQube, и я нашел обходной путь, чтобы загрузить отчет сонара, выполнив следующие действия:
Существующий пакет: Java 16.0.2, MacOS (BigSur)
- Установлен sonar_scanner
- Выполните эту команду: export SONAR_SCANNER_OPTS="- незаконный доступ = разрешение"
Надеюсь, это поможет кому-то, если он столкнется с той же проблемой, он / она может попробовать это. :-)
Я использовал jdk18+, но хотел только jdk8. Я удалил 18 из пути к классам и добавил 8 для приложения, которое работало нормально.
Я столкнулся с этой проблемой во время тестирования. Я использую Shazamcrest в сочетании с jMock. Это очень удобно, но shazamcrest использует gson, который, в свою очередь, использует некоторый подход для сравнения объектов, вызывающий эту ошибку.
Я добавил уже предложенное ранее решение в свой Maven pom.xml в конфигурации плагина Surefire следующим образом:
<configuration>
<!-- The add-opens are for shazamcrest that uses gson for random classes -->
<argLine>
--add-opens java.base/java.time=ALL-UNNAMED
--add-opens java.base/java.math=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
</argLine>
</configuration>
Это решило проблему.
Единственная оставшаяся проблема заключалась в том, что выполнение этих тестов внутри Eclipse по-прежнему представляет собой проблему. Для этого мне пришлось добавить те же настройки в «Выполнить конфигурацию» в аргументах JVM.
вы можете использоватьUnsafe
обойти, например
import sun.misc.Unsafe;
import java.lang.reflect.Field;
class Scratch {
private static Unsafe reflectGetUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
return null;
}
}
public static void main (String[]args) throws Exception {
Class<?> clazz = Class.forName("java.lang.ApplicationShutdownHooks");
Field field = clazz.getDeclaredField("hooks");
// this method can not be used in Java 9+ with java modules
// Unable to make field private static java.util.IdentityHashMap java.lang.ApplicationShutdownHooks.hooks accessible: module java.base does not "opens java.lang" to unnamed module @452b3a41
// field.setAccessible(true);
Unsafe safe = reflectGetUnsafe();
Object m = safe.getObject(clazz, safe.staticFieldOffset(field));
System.out.println(m.getClass().toString());
}
}
Ссылка: https://xz.aliyun.com/t/10075
экспорт версии Java решил эту проблему для меня
экспорт JAVA_HOME=
/usr/libexec/java_home -v 1.8
экспорт JAVA_HOME=
/usr/libexec/java_home -v 11.0.11
Как указать eclipse на добавление-экспорт при компиляции
Я выполнил шаги из верхнего комментария, но в
Add-exports configuration
всплывающее окно, я ввожу модули, которые были в моей ошибке и выбрал
opens
флажок вместо
exports
флажок и сохранил его. Это устранило мою проблему.
Для меня я просто изменил версию spring-boot-starter-web и работал на меня
Столкнулся с той же проблемой. Одна из причин может заключаться в использовании неправильного JDK / Project SDK.
Чтобы решить эту проблему в Intellij:
Щелкните правой кнопкой мыши проект Maven -> Открыть настройки модуля -> Настройки проекта -> Проект -> Выберите свой SDK для уже установленного JDK 1.8.
У меня это сработало!