Как решить 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}

Безусловно, наиболее выдающимися сценариями являются следующие два:

  1. Библиотека или фреймворк использует отражение для вызова модуля JDK. В этом сценарии:

    • {A} является модулем Java (с префиксом java. или же jdk.)
    • {member} а также {package} являются частью Java API
    • {B} библиотека, фреймворк или модуль приложения; довольно часто unnamed module @...
  2. Основанная на отражении библиотека / инфраструктура, такая как 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 вам нужно две вещи: перейти к

  1. Window -> Preference -> java -> CompilerSet Compiler Compliance Level to Specific version В моем случае версия Eclipse была установлена ​​на JDK 16, я вернул ее к 1.8, поскольку мой код был написан в 1.8

  2. Окно -> Настройки -> 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:

  1. Используйте Hibernate 5.0.0 или выше (более ранние версии не будут работать), и
  2. Запросить улучшение байт-кода во время сборки (используя плагины 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.

У меня это сработало!

Другие вопросы по тегам