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

Как решить проблему InaccessibleObjectException ( "Невозможно сделать {member) доступным: модуль {A} не открывает {package} 'to {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)

В сообщении говорится:

Невозможно сделать защищенный final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String, byte [], int, int, java.security.ProtectionDomain) throws java.lang.ClassFormatError доступен: модуль java.base не "открывает java.lang" для неназванного модуля @1941a8ff

Что можно сделать, чтобы избежать исключения и успешно ли запустить программу?

4b9b3361

Ответ 1

Исключение вызвано Системой платформы Java Platform Module, которая была представлена ​​на Java 9, в частности, ее внедрением сильной инкапсуляции. Он разрешает access при определенных условиях, наиболее важными из них являются:

  • тип должен быть общедоступным.
  • должен быть экспортирован собственный пакет.

Те же ограничения справедливы и для отражения, которые пытался использовать код, вызывающий исключение. Точнее, исключение вызвано вызовом 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,... отражает код приложения для доступа к beans, объектам,.... В этом случае:

    • {A} - это прикладной модуль
    • {member} и {package} являются частью кода приложения
    • {B} является либо каркасным модулем, либо unnamed module @...

Обратите внимание, что некоторые библиотеки (например, JAXB) могут не работать на обеих учетных записях, поэтому внимательно изучите, в каком сценарии вы находитесь! В этом вопросе речь идет о случае 1.

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, создаваемом инструментом сборки.

Если добавлено слишком много флагов, вы можете вместо этого использовать инкапсуляцию kill --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} { ... }, что делает все его пакеты доступными во время выполнения (с отражением или без него) ко всему коду

См. этот пост для более подробного обсуждения и сравнения этих подходов.

Ответ 2

Использование -add-opens следует рассматривать как обходной путь. Правильно, для Spring, Hibernate и других библиотек, занимающихся незаконным доступом, чтобы исправить свои проблемы.

Ответ 3

Это очень сложная задача для решения; и, как отмечают другие, опция -add-opens является лишь обходным решением. Актуальность решения основных проблем будет возрастать только после того, как Java 9 станет общедоступным.

Я нашел себя на этой странице после получения этой точной ошибки Javassist при тестировании моего приложения на базе Hibernate на Java 9. И так как я стремился поддерживать Java 7, 8 и 9 на нескольких платформах, я изо всех сил старался найти лучшее решение, (Обратите внимание, что Java 7 и 8 JVM будут прерваны сразу же, когда в командной строке будут обнаружены нераспознанные аргументы "-add-opens", поэтому это не может быть разрешено с помощью статических изменений в пакетных файлах, сценариях или ярлыках.)

Было бы неплохо получить официальное руководство от авторов основных библиотек (таких как Spring и Hibernate), но за 100 дней до текущей версии Java 9 этот совет все равно трудно найти.

После долгих экспериментов и тестирования я с облегчением нашел решение для Hibernate:

  • Используйте Hibernate 5.0.0 или более поздней версии (более ранние версии не будут работать) и
  • Запросить усовершенствование байт-кода build-time (используя плагины Gradle, Maven или Ant).

Это позволяет избежать необходимости того, чтобы Hibernate выполнял модификации класса Javassist во время выполнения, исключая трассировку стека, показанную в исходном сообщении.

ОДНАКО, вы должны тщательно протестировать свое приложение после этого. Изменения байт-кода, применяемые Hibernate во время сборки, по-видимому, отличаются от тех, которые применяются во время выполнения, вызывая несколько другое поведение приложения. Модульные тесты в моем приложении, которые преуспели в течение многих лет, внезапно потерпели неудачу, когда я включил повышение байт-кода в режиме сборки. (Мне пришлось преследовать новые LazyInitializationExceptions и другие проблемы.) И поведение, похоже, меняется от одной версии Hibernate к другой. Соблюдайте осторожность.

Ответ 4

У меня были предупреждения с гибернацией 5.

Illegal reflective access by javassist.util.proxy.SecurityActions

Я добавил последнюю библиотеку javassist к зависимостям gradle:

compile group: 'org.javassist', name: 'javassist', version: '3.22.0-GA'

Это решило мою проблему.