Come risolvere InaccessibleObjectException (“Imansible rendere {member} accessibile: il modulo {A} non” apre {package} “a {B}”) su Java 9?

Questa eccezione si verifica in un’ampia varietà di scenari durante l’esecuzione di un’applicazione su Java 9. Alcune librerie e framework (Spring, Hibernate, JAXB) sono particolarmente inclini ad esso. Ecco un esempio di 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) 

Il messaggio dice:

Imansible rendere finale java.lang.Class java.lang.ClassLoader.defineClass (java.lang.String, byte [], int, int, java.security.ProtectionDomain) java.lang.ClassFormatError accessibile: modulo java.base non “apre java.lang” al modulo senza nome @ 1941a8ff

Cosa si può fare per evitare l’eccezione e far funzionare correttamente il programma?

L’eccezione è causata dal Java Platform Module System che è stato introdotto in Java 9, in particolare dalla sua implementazione di forte incapsulamento. Permette solo l’ accesso a determinate condizioni, le più importanti sono:

  • il tipo deve essere pubblico
  • il pacchetto proprietario deve essere esportato

Le stesse limitazioni sono valide per la riflessione, che il codice che causa l’eccezione ha cercato di utilizzare. Più precisamente l’eccezione è causata da una chiamata a setAccessible . Questo può essere visto nella traccia dello stack sopra, dove le linee corrispondenti in javassist.util.proxy.SecurityActions il seguente aspetto:

 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; } }); } } 

Per assicurarsi che il programma funzioni correttamente, il sistema del modulo deve essere convinto per consentire l'accesso all'elemento su cui setAccessible stato chiamato setAccessible . Tutte le informazioni richieste sono contenute nel messaggio di eccezione, ma esistono numerosi meccanismi per raggiungere questo objective. Qual è il migliore dipende dallo scenario esatto che lo ha causato.

Imansible rendere {member} accessibile: il modulo {A} non "apre {pacchetto}" a {B}

Di gran lunga gli scenari più importanti sono i seguenti due:

  1. Una libreria o un framework utilizza la reflection per chiamare in un modulo JDK. In questo scenario:

    • {A} è un modulo Java (preceduto da java. o jdk. ) jdk.
    • {member} e {package} fanno parte dell'API Java
    • {B} è un modulo libreria, framework o applicazione; unnamed module @... spesso unnamed module @...
  2. Una libreria / framework basata sulla riflessione come Spring, Hibernate, JAXB, ... riflette sul codice dell'applicazione per accedere ai bean, alle quadro, .... In questo scenario:

    • {A} è un modulo di applicazione
    • {member} e {package} fanno parte del codice dell'applicazione
    • {B} è un modulo framework o un modulo unnamed module @...

Nota che alcune librerie (JAXB, ad esempio) possono fallire su entrambi gli account, quindi dai uno sguardo ravvicinato allo scenario in cui ti trovi! Quello nella domanda è il caso 1.

1. Chiamata riflessiva in JDK

I moduli JDK sono immutabili per gli sviluppatori di applicazioni, quindi non possiamo modificare le loro proprietà. Questo lascia solo una ansible soluzione: i flag della riga di comando . Con loro è ansible aprire pacchetti specifici per la riflessione.

Quindi in un caso come sopra (abbreviato) ...

Imansible rendere java.lang.ClassLoader.defineClass accessibile: il modulo java.base non "apre java.lang" al modulo senza nome @ 1941a8ff

... la correzione corretta è quella di avviare la JVM come segue:

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

Se il codice riflettente si trova in un modulo con nome, ALL-UNNAMED può essere sostituito dal suo nome.

Si noti che a volte può essere difficile trovare un modo per applicare questo flag alla JVM che eseguirà effettivamente il codice riflettente. Questo può essere particolarmente difficile se il codice in questione è parte del processo di generazione del progetto ed è eseguito in una JVM generata dallo strumento di generazione.

Se ci sono troppi flag da aggiungere, si potrebbe prendere in considerazione l'utilizzo del kill switch di incapsulamento --permit-illegal-access . Consentirà a tutto il codice sul percorso della class di riflettere su tutti i moduli denominati. Nota che questo flag funzionerà solo in Java 9 !

2. Riflessione sul codice dell'applicazione

In questo scenario è probabile che tu possa modificare il modulo in cui è usato il riflesso per penetrare. (In caso contrario, sei effettivamente nel caso 1.) Ciò significa che i flag della riga di comando non sono necessari e invece il descrittore del modulo {A} può essere utilizzato per aprire i suoi interni. Ci sono una varietà di scelte:

  • esportare il pacchetto con exports {package} , che lo rende disponibile in fase di compilazione e tempo di esecuzione a tutto il codice
  • esporta il pacchetto nel modulo di accesso con exports {package} to {B} , che lo rende disponibile in fase di compilazione e di esecuzione ma solo a {B}
  • apri il pacchetto con opens {package} , che lo rende disponibile in fase di esecuzione (con o senza riflessione) a tutto il codice
  • apri il pacchetto al modulo di accesso con opens {package} to {B} , che lo rende disponibile in fase di esecuzione (con o senza riflessione) ma solo a {B}
  • apri l'intero modulo con il modulo open module {A} { ... } , che rende disponibili tutti i suoi pacchetti in fase di esecuzione (con o senza riflessione) a tutto il codice

Vedi questo post per una discussione più dettagliata e il confronto di questi approcci.

L’uso di –add-opens dovrebbe essere considerato una soluzione alternativa. La cosa giusta è per Spring, Hibernate e altre biblioteche che fanno accesso illegale per risolvere i loro problemi.

Questo è un problema molto impegnativo da risolvere; e come notato da altri, l’opzione –add-opens è solo una soluzione. L’urgenza di risolvere i problemi sottostanti aumenterà solo quando Java 9 diventerà disponibile pubblicamente.

Mi sono ritrovato su questa pagina dopo aver ricevuto questo errore Javassist esatto durante il test della mia applicazione basata su Hibernate su Java 9. E dal momento che il mio objective è supportare Java 7, 8 e 9 su più piattaforms, ho faticato a trovare la soluzione migliore. (Si noti che Java 7 e 8 JVM si interromperanno immediatamente quando vedranno un argomento non riconosciuto “–add-opens” sulla riga di comando, quindi questo non può essere risolto con modifiche statiche a file batch, script o collegamenti.)

Sarebbe bello ricevere una guida ufficiale dagli autori delle biblioteche mainstream (come Spring e Hibernate), ma a 100 giorni dalla data di rilascio di Java 9, quel consiglio sembra ancora difficile da trovare.

Dopo molte sperimentazioni e test, sono stato sollevato di trovare una soluzione per Hibernate:

  1. Utilizzare Hibernate 5.0.0 o successivo (le versioni precedenti non funzioneranno) e
  2. Richiedi l’ ottimizzazione bytecode del build-time (utilizzando i plug-in Gradle, Maven o Ant).

Ciò evita la necessità che Hibernate esegua modifiche di class basate su Javassist in fase di runtime, eliminando la traccia di stack mostrata nel post originale.

TUTTAVIA , è necessario testare accuratamente la domanda in un secondo momento. Le modifiche del bytecode applicate da Hibernate in fase di compilazione sembrano differire da quelle applicate al runtime, causando un comportamento dell’applicazione leggermente diverso. I test delle unità nella mia app che sono riusciti per anni sono improvvisamente falliti quando ho triggersto l’ottimizzazione bytecode del build-time. (Ho dovuto inseguire nuove LazyInitializationExceptions e altri problemi.) E il comportamento sembra variare da una versione di Hibernate a un’altra. Procedi con caucanvas.

Avevo avvertimenti con l’ibernazione 5.

 Illegal reflective access by javassist.util.proxy.SecurityActions 

Ho aggiunto l’ultima libreria javassist alle dipendenze gradle:

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

Questo ha risolto il mio problema.