Ответ 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} { ... }
, что делает все его пакеты доступными во время выполнения (с отражением или без него) ко всему коду
См. этот пост для более подробного обсуждения и сравнения этих подходов.