Come gestire LinkageErrors in Java?

Sviluppando un’applicazione Java fortemente basata su XML, recentemente ho riscontrato un problema interessante su Ubuntu Linux.

La mia applicazione, che utilizza Java Plugin Framework , non è in grado di convertire un documento XML con dom4j in implementazione di Batik delle specifiche SVG.

Sulla console, imparo che si verifica un errore:

 Eccezione nel thread "AWT-EventQueue-0" java.lang.LinkageError: violazione del vincolo del caricatore nell'inizializzazione dell'interfaccia itable: quando si risolve il metodo "org.apache.batik.dom.svg.SVGOMDocument.createAttribute (Ljava / lang / String;) Lorg / W3C / dom / Attr;"  il programma di caricamento classi (istanza di org / java / plugin / standard / StandardPluginClassLoader) della class corrente, org / apache / batik / dom / svg / SVGOMDocument e il programma di caricamento classi (istanza di ) per l'interfaccia org / w3c / dom / Document hanno diversi oggetti di class per il tipo org / w3c / dom / Attr usati nella firma
     a org.apache.batik.dom.svg.SVGDOMImplementation.createDocument (SVGDOMImplementation.java:149)
     a org.dom4j.io.DOMWriter.createDomDocument (DOMWriter.java:361)
     a org.dom4j.io.DOMWriter.write (DOMWriter.java:138)

Immagino che il problema sia causato da un conflitto tra il classloader originale dalla JVM e il classloader distribuito dal framework del plugin.

Per quanto ne so, non è ansible specificare un classloader per il framework da utilizzare. Potrebbe essere ansible hackerarlo, ma preferirei un approccio meno aggressivo alla risoluzione di questo problema, poiché (per qualsiasi motivo) si verifica solo sui sistemi Linux.

Uno di voi ha riscontrato un tale problema e ha qualche idea su come risolverlo o almeno arrivare al nocciolo del problema?

LinkageError è ciò che otterrai in un caso classico in cui hai una class C caricata da più di un classloader e quelle classi vengono utilizzate insieme nello stesso codice (confrontato, cast, ecc.). Non importa se è lo stesso nome della class o anche se è caricato dal jar identico – una class da un classloader viene sempre considerata come una class diversa se caricata da un altro classloader.

Il messaggio (che è migliorato molto nel corso degli anni) dice:

Exception in thread "AWT-EventQueue-0" java.lang.LinkageError: loader constraint violation in interface itable initialization: when resolving method "org.apache.batik.dom.svg.SVGOMDocument.createAttribute(Ljava/lang/String;)Lorg/w3c/dom/Attr;" the class loader (instance of org/java/plugin/standard/StandardPluginClassLoader) of the current class, org/apache/batik/dom/svg/SVGOMDocument, and the class loader (instance of ) for interface org/w3c/dom/Document have different Class objects for the type org/w3c/dom/Attr used in the signature 

Quindi, qui il problema è nella risoluzione del metodo SVGOMDocument.createAttribute (), che utilizza org.w3c.dom.Attr (parte della libreria DOM standard). Ma la versione di Attr caricato con Batik è stata caricata da un diverso classloader rispetto all’istanza di Attr che stai passando al metodo.

Vedrai che la versione di Batik sembra essere caricata dal plugin Java. E il tuo viene caricato da “”, che è probabilmente uno dei caricatori JVM incorporati (classpath di avvio, ESOM o classpath).

I tre modelli di classloader di spicco sono:

  • delegazione (l’impostazione predefinita nel JDK – chiedi a genitore, poi a me)
  • post-delega (comune in plugin, servlet e luoghi in cui si desidera l’isolamento: chiedere a me, quindi a un genitore)
  • fratello (comune nei modelli di dipendenza come OSGi, Eclipse, ecc.)

Non so quale sia la strategia di delega utilizzata dal classloader JPF, ma la chiave è che si desidera caricare una versione della libreria dom e che tutti possano risalire alla stessa class dalla stessa posizione. Ciò potrebbe significare rimuoverlo dal classpath e caricarlo come plugin, o impedire a Batik di caricarlo, o qualcos’altro.

Sembra un problema di gerarchia di classloader. Non posso dire quale tipo di ambiente è distribuito l’applicazione, ma a volte questo problema può verificarsi in un ambiente Web – in cui il server delle applicazioni crea una gerarchia di programmi di caricamento classi, simile a qualcosa di simile:

javahome / lib – come root
appserver / lib – come figlio di root
webapp / WEB-INF / lib – come figlio di child di root
eccetera

Solitamente i classloader delegano il caricamento al loro programma di caricamento di class genitore (questo è noto come ” parent-first “), e se il classloader non riesce a trovare la class, il tentativo del classloader secondario lo tenta. Ad esempio, se una class distribuita come JAR in webapp / WEB-INF / lib prova a caricare una class, prima chiede al classloader corrispondente a appserver / lib di caricare la class (che a sua volta chiede al classloader corrispondente a javahome / lib per caricare la class), e se questa ricerca fallisce, WEB-INF / lib viene cercato una corrispondenza con questa class.

In un ambiente Web, è ansible incontrare problemi con questa gerarchia. Ad esempio, un errore / problema che ho incontrato prima era quando una class in WEB-INF / lib dipendeva da una class distribuita in appserver / lib, che a sua volta dipendeva da una class distribuita in WEB-INF / lib. Ciò ha causato errori perché mentre i programmi di caricamento classi sono in grado di debind al programma di caricamento classi genitore, non possono debind di nuovo l’albero. Quindi, il classloader WEB-INF / lib chiede al classloader appserver / lib per una class, il classloader appserver / lib carica questa class e prova a caricare la class dipendente e fallisce poiché non riesce a trovare quella class in appserver / lib o javahome / lib.

Pertanto, mentre non è ansible distribuire l’app in un ambiente server Web / app, la mia spiegazione troppo lunga potrebbe applicarsi a te se il tuo ambiente ha una gerarchia di programmi di caricamento di classi. Lo fa? JPF sta facendo una sorta di magia del classloader per essere in grado di implementare le sue funzionalità di plugin?

Potrebbe essere questo aiuterà qualcuno perché funziona abbastanza bene per me. Il problema può essere risolto integrando le proprie dipendenze. Segui questa semplice procedura

Innanzitutto controlla l’errore che dovrebbe essere così:

  • Esecuzione del metodo fallita:
  • java.lang.LinkageError: violazione del vincolo del caricatore:
  • quando si risolve il metodo “org.slf4j.impl. StaticLoggerBinder .getLoggerFactory () Lorg / slf4j / ILoggerFactory;”
  • il programma di caricamento classi (istanza di org / openmrs / module / ModuleClassLoader) della class corrente, org / slf4j / LoggerFactory ,
  • e il programma di caricamento classi (istanza di org / apache / catalina / loader / WebappClassLoader) per la class risolta, org / slf4j / impl / StaticLoggerBinder ,
  • avere oggetti Classe diversi per il tipo taticLoggerBinder.getLoggerFactory () Lorg / slf4j / ILoggerFactory; usato nella firma

  1. Vedi le due classi evidenziate. Google li ricerca come “download di jar di StaticLoggerBinder.class” e “download di jar di LoggeraFactory.class”. Questo ti mostrerà prima o in qualche caso il secondo link (Sito è http://www.java2s.com ) che è una delle versioni jar che hai incluso nel tuo progetto. Puoi identificarlo da solo, ma siamo dipendenti da Google;)

  2. Dopodiché saprai il nome del file jar, nel mio caso è come slf4j-log4j12-1.5.6.jar e slf4j-api-1.5.8

  3. Ora l’ultima versione di questo file è disponibile qui http://mvnrepository.com/ (in realtà tutte le versioni fino alla data, questo è il sito da cui Maven ottiene le tue dipendenze).
  4. Ora aggiungi entrambi i file come dipendenze con la versione più recente (o mantieni la stessa versione di entrambi i file, o la versione scelta è vecchia). Di seguito è riportata la dipendenza che devi includere in pom.xml

  org.slf4j slf4j-api 1.7.7   org.slf4j slf4j-log4j12 1.7.7  

Come ottenere la definizione di dipendenza dal sito Maven

Puoi specificare un caricatore di class? In caso contrario, prova a specificare il caricatore di class del contesto in questo modo:

 Thread thread = Thread.currentThread(); ClassLoader contextClassLoader = thread.getContextClassLoader(); try { thread.setContextClassLoader(yourClassLoader); callDom4j(); } finally { thread.setContextClassLoader(contextClassLoader); } 

Non ho familiarità con Java Plugin Framework, ma scrivo codice per Eclipse e di tanto in tanto mi imbatto in problemi simili. Non garantisco che lo risolverà, ma probabilmente vale la pena sparare.

Le risposte di Alex e Matt sono molto utili. Potrei anche trarre beneficio dalla loro analisi.

Ho avuto lo stesso problema quando utilizzo la libreria Batik in un framework RCP Netbeans, la libreria Batik inclusa come “Modulo wrapper libreria”. Se qualche altro modulo fa uso di apis XML e non è necessaria alcuna dipendenza da Batik e stabilita per quel modulo, il problema di violazione del vincolo del caricatore di class si presenta con messaggi di errore simili.

In Netbeans, i singoli moduli utilizzano caricatori di class dedicati e la relazione di dipendenza tra i moduli implica il routing di delega della class loader adatto.

Potrei risolvere il problema semplicemente omettendo il file jar xml-apis dal bundle della libreria Batik.