web-dev-qa-db-fra.com

Comment résoudre InaccessibleObjectException ("Impossible de rendre {membre} accessible: le module {A} n'ouvre pas {le package} 'à {B}") sur Java 9??

Cette exception se produit dans une grande variété de scénarios lors de l'exécution d'une application sur Java 9. Certaines bibliothèques et infrastructures (Spring, Hibernate, JAXB) y sont particulièrement exposées. Voici un exemple de 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)

Le message dit:

Impossible de rendre Java.lang.Class final protégé. Java.lang.ClassLoader.defineClass (Java.lang.String, octet [], int, int, Java.security.ProtectionDomain) lève Java.lang.ClassFormatError accessible: module Java.base ne "ouvre pas Java.lang" au module sans nom @ 1941a8ff

Que peut-on faire pour éviter l'exception et faire fonctionner le programme avec succès?

49
Nicolai

L'exception est provoquée par le Java Platform Module System qui a été introduit dans Java 9, en particulier la mise en oeuvre d'une encapsulation forte. Il ne permet que accès = sous certaines conditions, les plus importantes sont:

  • le type doit être public
  • le paquet propriétaire doit être exporté

Les mêmes limitations sont valables pour la réflexion que le code à l'origine de l'exception a essayé d'utiliser. Plus précisément, l'exception est provoquée par un appel à setAccessible . Ceci peut être vu dans la trace de pile ci-dessus, où les lignes correspondantes dans javassist.util.proxy.SecurityActions se présentent comme suit:

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 que le programme fonctionne correctement, 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 requises pour cela sont contenues dans le message d'exception mais il y a plusieurs mécanismes pour y parvenir. Le meilleur dépend du scénario exact qui l’a provoquée.

Impossible de rendre {membre} accessible: le module {A} n'ouvre pas {package} 'à {B}

Les scénarios de loin les plus importants sont les deux suivants:

  1. Une bibliothèque ou une structure utilise la réflexion pour appeler un module JDK. Dans ce scénario:

    • {A} est un module Java (préfixé par Java. ou jdk.)
    • {member} et {package} font partie de l'API Java
    • {B} est une bibliothèque, un framework ou un module d'application; souvent unnamed module @...
  2. Une bibliothèque/infrastructure basée sur la réflexion telle que Spring, Hibernate, JAXB, ... reflète le code de l'application pour accéder aux beans, entités, ... Dans ce scénario:

    • {A} est un module d'application
    • {member} et {package} font partie du code de l'application
    • {B} est soit un module cadre, soit unnamed module @...

Notez que certaines bibliothèques (JAXB, par exemple) peuvent échouer sur les deux comptes. Examinez de près le scénario dans lequel vous vous trouvez! Le cas dans la question est le cas 1.

1. Appel réfléchi dans JDK

Les modules JDK étant immuables pour les développeurs d'applications, nous ne pouvons pas en modifier les propriétés. Cela ne laisse qu'une solution possible: drapeaux de ligne de commande . Avec eux, il est possible d’ouvrir des paquets spécifiques pour la réflexion.

Donc, dans un cas comme ci-dessus (raccourci) ...

Impossible de rendre Java.lang.ClassLoader.defineClass accessible: le module Java.base n'ouvre pas "Java.lang" au module sans nom @ 1941a8ff

... le correctif consiste à lancer la machine virtuelle Java comme suit:

# --add-opens has the following syntax: {A}/{package}={B}
Java --add-opens Java.base/Java.lang=ALL-UNNAMED

Si le code reflétant est 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 à la machine virtuelle Java qui exécutera réellement le code de réflexion. Cela peut être particulièrement difficile si le code en question fait partie du processus de génération du projet et est exécuté dans une machine virtuelle que l'outil de génération a générée.

S'il y a trop d'indicateurs à ajouter, vous pouvez envisager d'utiliser à la place le commutateur d'arrêt de l'encapsulation--permit-illegal-access. Cela permettra à tout le code sur le chemin de classe de se refléter sur tous les modules nommés. Notez que cet indicateur ne fonctionnera que dans Java 9 !

2. Réflexion sur le code d'application

Dans ce scénario, il est probable que vous puissiez modifier le module dans lequel la réflexion est utilisée. Cela signifie que les indicateurs de ligne de commande ne sont pas nécessaires et que le descripteur du module {A} peut être utilisé pour ouvrir ses éléments internes. Il y a une variété de choix:

  • exporter le paquet avec exports {package}, ce qui le rend disponible à la compilation et à l'exécution à tout le code
  • exporter le paquet dans le module accédant avec exports {package} to {B}, ce qui le rend disponible au moment de la compilation et de l'exécution, mais uniquement pour {B}
  • ouvrez le paquet avec opens {package}, ce qui le rend disponible au moment de l'exécution (avec ou sans réflexion) pour tout le code
  • ouvrez le package dans le module d'accès avec opens {package} to {B}, ce qui le rend disponible au moment de l'exécution (avec ou sans réflexion), mais uniquement pour {B}
  • ouvrez le module entier avec open module {A} { ... }, ce qui rend tous ses packages disponibles au moment de l'exécution (avec ou sans réflexion) pour tout le code

Voir this post pour une discussion plus détaillée et une comparaison de ces approches.

75
Nicolai

C'est un problème très difficile à résoudre; et comme d'autres l'ont fait remarquer, l'option --add-opens n'est qu'une solution de contournement. L'urgence de résoudre les problèmes sous-jacents ne augmentera que lorsque Java 9 sera disponible publiquement.

Je me suis retrouvé sur cette page après avoir reçu cette erreur exacte Javassist lors du test de mon application basée sur Hibernate sur Java 9. Et depuis que je vise à prendre en charge Java 7, 8 et 9 sur plusieurs plates-formes, j’ai eu du mal à trouver la meilleure solution. (Notez que Java Les JVM 7 et 8 sont abandonnées immédiatement quand elles voient un argument "--add-opens" non reconnu) le la ligne de commande; il est donc impossible de résoudre ce problème en modifiant statiquement les fichiers de commandes, les scripts ou les raccourcis.)

Il serait bien de recevoir les conseils officiels des auteurs des bibliothèques principales (telles que Spring et Hibernate), mais à 100 jours de la sortie actuellement prévue de Java 9, cet avis semble toujours difficile à trouver.

Après de nombreuses expériences et tests, j'ai été soulagé de trouver une solution pour Hibernate:

  1. Utilisez Hibernate 5.0.0 ou supérieur (les versions antérieures ne fonctionneront pas), et
  2. Request amélioration du bytecode au moment de la compilation (en utilisant les plugins Gradle, Maven ou Ant).

Cela évite à Hibernate d’effectuer des modifications de classe basées sur Javassist au moment de l’exécution, ce qui élimine la trace de pile indiquée dans la publication originale.

CEPENDANT , vous devez tester votre application de manière approfondie par la suite. Les modifications de bytecode appliquées par Hibernate au moment de la construction semblent différer de celles appliquées au moment de l'exécution, ce qui entraîne un comportement légèrement différent de l'application. Les tests unitaires dans mon application qui ont réussi pendant des années ont soudainement échoué lorsque j'ai activé l'amélioration du bytecode au moment de la construction. (J'ai dû chasser de nouvelles LazyInitializationExceptions et d'autres problèmes.) Et le comportement semble varier d'une version d'Hibernate à une autre. Procéder avec prudence.

3
David T

L'utilisation de --add-opens doit être considérée comme une solution de contournement. La meilleure chose à faire est que Spring, Hibernate et d'autres bibliothèques effectuent un accès illégal pour résoudre leurs problèmes.

2
Alan Bateman

J'ai eu des avertissements avec Hibernate 5.

Illegal reflective access by javassist.util.proxy.SecurityActions

J'ai ajouté la dernière bibliothèque javassist à Dependencies Gradle:

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

Cela a résolu mon problème.

1
Karol Golec