Comment résoudre InaccessibleObjectException ("impossible de rendre {member} accessible: le module {a} n'ouvre pas {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 Java 9. Certaines bibliothèques et frameworks (Spring, Hibernate, JAXB) y sont particulièrement enclins. 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 faire java final protégé.lang.La classe java.lang.Chargeur de classe.defineClass (java.lang.String,byte[],int,int,java.sécurité.ProtectionDomain) jette java.lang.ClassFormatError accessible: module java.base ne fait pas "ouvre java.lang " au module sans nom @ 1941a8ff

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

38
demandé sur Nicolai 2016-12-21 17:34:40

4 réponses

L'exception est causée par le Java Platform Module System {[29] } qui a été introduit dans Java 9, en particulier son implémentation d'encapsulation forte. Il permet seulement 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 vraies pour la réflexion, que le code à l'origine de l'exception a essayé d'utiliser. Plus précisément l'exception est causée par un appel à setAccessible. Cela peut être vu dans la trace de la 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 s'assurer 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 existe un certain nombre de mécanismes pour y parvenir. Quel est le meilleur dépend du scénario exact qui il a causé.

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

De loin les scénarios les plus importants sont les deux suivants:

  1. Une bibliothèque ou un framework utilise reflection pour appeler un module JDK. Dans ce scénario:

    • {A} est un module Java (préfixé par java. ou jdk.)
    • {member} et {[9] } font partie de L'API Java
    • {[10] } est une bibliothèque, un framework ou une application module; souvent unnamed module @...
  2. Une bibliothèque/framework basée sur la réflexion comme Spring, Hibernate, JAXB, ... reflète sur 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 d'application
    • {B} est soit un module framework, soit unnamed module @...

Notez que certaines bibliothèques (JAXB, par exemple) peuvent échouer sur les deux comptes regardez de près dans quel scénario vous êtes! Celui de la question est le cas 1.

1. Appel réfléchissant dans JDK

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

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

Impossible de faire java.lang.Chargeur de classe.defineClass accessible: module java.la base de ne pas "s'ouvre java.lang " au module sans nom @ 1941a8ff

... le correctif correct est de lancer la JVM comme suit:

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

Si le code réfléchissant 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 JVM qui exécutera réellement le code réfléchissant. Cela peut être particulièrement difficile si le code en question fait partie du processus de construction du projet et est exécuté dans une JVM générée par l'outil de génération.

S'il y a trop d'indicateurs à ajouter, vous pouvez envisager d'utiliser l'interrupteur d'arrêt d'encapsulation --permit-illegal-access au lieu de cela. Cela permettra à tout le code sur le chemin de la classe de réfléchir sur tous les modules nommés. Notez que cet indicateur ne fonctionnera qu'en Java 9 !

2. Réflexion Sur Le Code D'Application

Dans ce scénario, il est probable que vous pouvez modifier le module que la réflexion est utilisé pour percer. (Si non, vous êtes effectivement, dans le cas 1.) Cela signifie que les drapeaux de ligne de commande ne sont pas nécessaires et que le descripteur du module {A} peut être utilisé pour ouvrir ses internes. Il y a une variété de choix:

  • exportez le paquet avec exports {package}, ce qui le rend disponible à la compilation et à l'exécution pour tout le code
  • exportez le paquet vers le module d'accès avec exports {package} to {B}, ce qui le rend disponible lors de la compilation et de l'exécution, mais uniquement vers {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 paquet au 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 seulement à {B}
  • ouvrez le module entier avec open module {A} { ... }, ce qui rend tous ses paquets disponibles au moment de l'exécution (avec ou sans réflexion) à tout le code

Voir cet article pour une discussion plus détaillée et une comparaison de ces approches.

58
répondu Nicolai 2017-05-09 13:23:30

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

3
répondu Alan Bateman 2017-03-27 10:25:41

C'est un problème très difficile à résoudre; et comme noté par d'autres, l'option --add-opens n'est qu'une solution de contournement. L'urgence de résoudre les problèmes sous-jacents ne fera que croître une fois que Java 9 sera accessible au public.

Je me suis retrouvé sur cette page après avoir reçu cette erreur javassist exacte lors du test de mon application basée sur Hibernate sur Java 9. Et comme je vise à soutenir Java 7, 8 et 9 sur plusieurs plates-formes, j'ai eu du mal à trouver la meilleure solution. (Notez que les jvm Java 7 et 8 abandonner immédiatement lorsqu'ils voient un argument "--add-opens" non reconnu sur la ligne de commande; cela ne peut donc pas être résolu avec des modifications statiques des fichiers batch, des scripts ou des raccourcis.)

Il serait bon de recevoir des conseils officiels des auteurs des bibliothèques grand public (comme Spring et Hibernate), mais avec 100 jours avant la sortie actuellement prévue de Java 9, ce conseil semble encore difficile à trouver.

Après beaucoup d'expérimentation et de tests, j'ai été soulagé de trouver un solution pour hiberner:

  1. Utilisez Hibernate 5.0.0 ou supérieur (les versions antérieures ne fonctionneront pas), et
  2. Request build-time bytecode enhancement (en utilisant les plugins Gradle, Maven ou Ant).

Cela évite la nécessité pour Hibernate d'effectuer des modifications de classe basées sur Javassist au moment de l'exécution, éliminant ainsi la trace de pile affichée dans la publication d'origine.

Cependant , Vous devriez tester soigneusement votre application par la suite. Les modifications de bytecode appliquées par Hibernate au moment de la construction semblent différer de ceux appliqués à l'exécution, provoquant un comportement d'application légèrement différent. 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 à l'autre. Procédez avec prudence.

2
répondu David T 2017-04-07 03:42:24

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 aux dépendances gradle:

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

Cela a résolu mon problème.

1
répondu Karol Golec 2018-01-12 11:12:55