L'exception est causée par le système de module de plate-forme Java qui a été introduit dans Java 9, en particulier son implémentation de l'encapsulation forte. Il permet uniquement un accès dans certaines conditions, les plus importantes étant :
- le type doit être public
- le package propriétaire doit être exporté
Les mêmes limitations s'appliquent à la réflexion, que le code causant l'exception a tenté d'utiliser. Plus précisément, l'exception est causée par un appel à setAccessible
. Cela peut être vu dans la trace de pile ci-dessus, où les lignes correspondantes dans javassist.util.proxy.SecurityActions
ressemblent à ceci :
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;
}
});
}
}
Pour s'assurer que le programme s'exécute avec succès, le système de module doit être convaincu d'autoriser l'accès à l'élément sur lequel setAccessible
a été appelé. Toutes les informations nécessaires à cela sont contenues dans le message d'exception, mais il existe un certain nombre de mécanismes pour y parvenir. Lequel est le meilleur dépend du scénario exact qui l'a causé.
Impossible de rendre {membre} accessible : le module {A} ne 'ouvre pas {package}' à {B}
De loin, les scénarios les plus courants sont les deux suivants :
-
Une bibliothèque ou un framework utilise la réflexion pour appeler un module JDK. Dans ce scénario :
{A}
est un module Java (préfixé par java.
ou jdk.
)
{membre}
et {package}
font partie de l'API Java
{B}
est un module de bibliothèque, de framework ou d'application ; souvent unnamed module @...
-
Une bibliothèque/framework basé(e) sur la réflexion comme Spring, Hibernate, JAXB,... fait de la réflexion sur le code d'application pour accéder aux beans, entités,... Dans ce scénario :
{A}
est un module d'application
{membre}
et {package}
font partie du code d'application
{B}
est soit un module de framework soit unnamed module @...
Remarquez que certaines bibliothèques (comme JAXB, par exemple) peuvent échouer sur les deux points, donc examinez attentivement dans quel scénario vous vous trouvez ! Celui de la question est le cas 1.
1. Appel Réflexif dans le JDK
Les modules JDK sont immuables pour les développeurs d'applications, donc nous ne pouvons pas changer leurs propriétés. Cela laisse une seule solution possible : des indicateurs en ligne de commande. Avec eux, il est possible d'ouvrir des packages spécifiques à la réflexion.
Ainsi, dans un cas comme ci-dessus (abrégé)...
Impossible de rendre java.lang.ClassLoader.defineClass accessible : le module java.base ne "ouvre pas java.lang" à unnamed module @1941a8ff
... la correction correcte est de lancer le JVM comme suit :
# --add-opens a la syntaxe suivante : {A}/{package}={B}
java --add-opens java.base/java.lang=ALL-UNNAMED
Si le code réfléchi se trouve dans un module nommé, ALL-UNNAMED
peut être remplacé par son nom.
Notez qu'il peut parfois être difficile de trouver un moyen d'appliquer cet indicateur au JVM qui exécutera effectivement le code réfléchi. Cela peut être particulièrement difficile si le code en question fait partie du processus de construction du projet et est exécuté dans un JVM que l'outil de construction a démarré.
S'il y a trop d'indicateurs à ajouter, vous pouvez envisager d'utiliser le basculement d'accès à l'encapsulation --permit-illegal-access
à la place. Il permettra à tout le code sur le chemin de classe de réfléchir globalement à l'ensemble des modules nommés. Notez que cet indicateur fonctionnera uniquement avec Java 9!
2. Réflexion sur le Code d'Application
Dans ce scénario, il est probable que vous puissiez modifier le module que la réflexion utilise pour s'infiltrer. (Sinon, vous êtes effectivement dans le cas 1.) Cela signifie que les indicateurs en ligne de commande ne sont pas nécessaires et que instead le descripteur du module {A}
peut être utilisé pour ouvrir ses éléments internes. Il y a plusieurs choix :
- exporter le package avec
exports {package}
, ce qui le rend disponible en compilation et en exécution pour tout le code
- exporter le package au module accédant avec
exports {package} to {B}
, ce qui le rend disponible en compilation et en exécution mais uniquement pour {B}
- ouvrir le package avec
opens {package}
, ce qui le rend disponible en exécution (avec ou sans réflexion) pour tout le code
- ouvrir le package au module accédant avec
opens {package} to {B}
, ce qui le rend disponible en exécution (avec ou sans réflexion) mais uniquement pour {B}
- ouvrir l'ensemble du module avec
open module {A} { ... }
, ce qui rend tous ses packages disponibles en exécution (avec ou sans réflexion) pour tout le code
Voir cet article pour une discussion plus détaillée et une comparaison de ces approches.