Domanda

Sviluppando un'applicazione Java fortemente basata su XML, di recente ho riscontrato un problema interessante su Ubuntu Linux.

La mia applicazione, utilizzando il Java Plugin Framework , sembra non essere in grado di convertire un dom4j ha creato un documento XML in Batik's implementazione della specifica SVG.

Sulla console, apprendo che si verifica un errore:

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 <bootloader>) for interface org/w3c/dom/Document have different Class objects for the type org/w3c/dom/Attr used in the signature
    at org.apache.batik.dom.svg.SVGDOMImplementation.createDocument(SVGDOMImplementation.java:149)
    at org.dom4j.io.DOMWriter.createDomDocument(DOMWriter.java:361)
    at 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 dei plugin.

Per quanto ne sappia, non è possibile specificare un classloader da utilizzare per il framework. Potrebbe essere possibile hackerarlo, ma preferirei un approccio meno aggressivo per risolvere questo problema, poiché (per qualsiasi motivo) si verifica solo su sistemi Linux.

Uno di voi ha riscontrato questo problema e ha idea di come risolverlo o almeno arrivare al nocciolo del problema?

È stato utile?

Soluzione

LinkageError è ciò che otterrai in un caso classico in cui hai una classe C caricata da più di un classloader e quelle classi vengono usate insieme nello stesso codice (comparato, cast, ecc.). Non importa se ha lo stesso nome di classe o anche se è caricato dallo stesso identico jar: una classe di un classloader viene sempre trattata come una classe diversa se caricata da un altro classloader.

Il messaggio (che è migliorato molto negli 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 caricata con Batik è stata caricata da un programma di caricamento classi diverso dall'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 integrati (boot classpath, ESOM o classpath).

I tre principali modelli di classloader sono:

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

Non so quale strategia di delega utilizza il classloader JPF, ma la chiave è che si desidera caricare una versione della libreria dom e che tutti provengano da quella classe dalla stessa posizione. Ciò può significare rimuoverlo dal percorso di classe e caricarlo come plugin, o impedire a Batik di caricarlo o qualcos'altro.

Altri suggerimenti

Sembra un problema di gerarchia del classloader. Non so dire in quale tipo di ambiente viene distribuita 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:

javahome / lib - come root
appserver / lib - come figlio di root
webapp / WEB-INF / lib - come figlio del figlio della radice
etc

Di solito i classloader delegano il caricamento al loro classloader genitore (questo è noto come " genitore-primo "), e se quel classloader non riesce a trovare la classe, allora il classloader secondario tenta di farlo. Ad esempio, se una classe distribuita come JAR in webapp / WEB-INF / lib tenta di caricare una classe, per prima cosa chiede al classloader corrispondente a appserver / lib di caricare la classe (che a sua volta chiede al classloader corrispondente a javahome / lib per caricare la classe), e se questa ricerca fallisce, allora WEB-INF / lib viene cercata una corrispondenza con questa classe.

In un ambiente Web, è possibile riscontrare problemi con questa gerarchia. Ad esempio, un errore / problema che ho riscontrato prima era quando una classe in WEB-INF / lib dipendeva da una classe distribuita in appserver / lib, che a sua volta dipendeva da una classe distribuita in WEB-INF / lib. Ciò ha causato errori perché, mentre i classloader sono in grado di delegare al classloader principale, non possono delegare nuovamente l'albero. Quindi, il classloader WEB-INF / lib chiederebbe a class classificatore appserver / lib di caricare una classe, classloader appserver / lib caricerebbe quella classe e proverebbe a caricare la classe dipendente, e fallire perché non troverebbe quella classe in appserver / lib o javahome / lib.

Quindi, mentre potresti non distribuire la tua app in un ambiente server web / app, la mia spiegazione troppo lunga potrebbe applicarti se il tuo ambiente ha una gerarchia di programmi di caricamento classi. Vero? JPF sta facendo una sorta di magia del classloader per essere in grado di implementare le sue funzionalità di plugin?

Potrebbe essere questo che aiuterà qualcuno perché funziona abbastanza bene per me. Il problema può essere risolto integrando le proprie dipendenze. Segui questi semplici passaggi

Prima controlla l'errore che dovrebbe essere così:

  • Esecuzione del metodo non riuscita:
  • java.lang.LinkageError: violazione del vincolo del caricatore:
  • durante la risoluzione del metodo " org.slf4j.impl. StaticLoggerBinder .getLoggerFactory () Lorg / slf4j / ILoggerFactory; "
  • caricatore di classi (istanza di org / openmrs / module / ModuleClassLoader) della classe corrente, org / slf4j / LoggerFactory ,
  • e il programma di caricamento classi (istanza di org / apache / catalina / loader / WebappClassLoader) per classe risolta, org / slf4j / impl / StaticLoggerBinder ,
  • hanno oggetti Class diversi per il tipo taticLoggerBinder.getLoggerFactory () Lorg / slf4j / ILoggerFactory; utilizzato nella firma

  1. Vedi le due classi evidenziate. Google li cerca come " StaticLoggerBinder.class jar download " & Amp; " LoggeraFactory.class jar download " ;. Questo ti mostrerà il primo o in qualche caso il secondo link (il sito è http://www.java2s.com ) che è una delle versioni jar che hai incluso nel tuo progetto. Puoi identificarlo in modo intelligente da solo, ma siamo dipendenti da Google;)

  2. Dopo di che conoscerai il nome del file jar, nel mio caso è come slf4j-log4j12-1.5.6.jar & amp; slf4j-api-1.5.8

  3. Ora l'ultima versione di questo file è disponibile qui http://mvnrepository.com/ (in realtà tutte le versioni fino ad oggi, questo è il sito da cui Maven ottiene le tue dipendenze).
  4. Ora aggiungi entrambi i file come dipendenze con l'ultima versione (o mantieni la stessa versione del file, entrambe le versioni scelte sono vecchie). Di seguito è la dipendenza che devi includere in pom.xml

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.7</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.7</version>
</dependency>

Come ottenere la definizione di dipendenza dal sito Maven

Puoi specificare un caricatore di classi? In caso contrario, prova a specificare il caricatore di classi di 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 provare.

Le risposte di Alex e Matt sono molto utili. Potrei beneficiare anche della loro analisi.

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

In Netbeans, i singoli moduli utilizzano caricatori di classi dedicati e la relazione di dipendenza tra i moduli implica il routing di delega del caricatore di classi adatto.

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

Come specificato in questa domanda , abilitando -verbose: class renderà le informazioni del registro JVM su tutte le classi caricate, il che può essere incredibilmente utile per capire da dove provengono le classi in scenari più complessi & amp; applicazioni.

L'output che ottieni è più o meno simile a questo (copiato da quella domanda):

[Opened /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/sunrsasign.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/jsse.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/jce.jar]
[Opened /usr/java/j2sdk1.4.1/jre/lib/charsets.jar]
[Loaded java.lang.Object from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.io.Serializable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.lang.Comparable from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.lang.CharSequence from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded java.lang.String from /usr/java/j2sdk1.4.1/jre/lib/rt.jar]
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top