Trattare con "Xerces hell" in Java / Maven?


732

Nel mio ufficio, la sola menzione della parola Xerces è sufficiente per incitare alla rabbia omicida degli sviluppatori. Una rapida occhiata alle altre domande di Xerces su SO sembra indicare che quasi tutti gli utenti Maven sono "toccati" da questo problema ad un certo punto. Sfortunatamente, comprendere il problema richiede un po 'di conoscenza sulla storia di Xerces ...

Storia

  • Xerces è il parser XML più utilizzato nell'ecosistema Java. Quasi tutte le librerie o framework scritti in Java utilizzano Xerces in qualche modo (transitivamente, se non direttamente).

  • I barattoli Xerces inclusi nei binari ufficiali non sono, fino ad oggi, versioni. Ad esempio, il vaso di implementazione di Xerces 2.11.0 è denominato xercesImpl.jare non xercesImpl-2.11.0.jar.

  • Il team di Xerces non utilizza Maven , il che significa che non caricano una versione ufficiale su Maven Central .

  • Xerces veniva rilasciato come un singolo jar ( xerces.jar), ma era diviso in due vasetti, uno contenente l'API ( xml-apis.jar) e uno contenente le implementazioni di tali API ( xercesImpl.jar). Molti vecchi POM Maven dichiarano ancora una dipendenza xerces.jar. Ad un certo punto in passato, è stato rilasciato anche Xerces xmlParserAPIs.jar, da cui dipendono anche alcuni vecchi POM.

  • Le versioni assegnate ai vasetti xml-apis e xercesImpl da parte di coloro che distribuiscono i loro vasetti nei repository Maven sono spesso diverse. Ad esempio, xml-apis potrebbe avere la versione 1.3.03 e xercesImpl potrebbe avere la versione 2.8.0, anche se entrambi provengono da Xerces 2.8.0. Questo perché le persone spesso taggano il vaso xml-apis con la versione delle specifiche che implementa. C'è una ripartizione molto bella, ma incompleta di questo qui .

  • A complicare le cose, Xerces è il parser XML utilizzato nell'implementazione di riferimento dell'API Java per l'elaborazione XML (JAXP), incluso in JRE. Le classi di implementazione sono riconfezionate nello com.sun.*spazio dei nomi, il che rende pericoloso accedervi direttamente, poiché potrebbero non essere disponibili in alcuni JRE. Tuttavia, non tutte le funzionalità di Xerces sono esposte tramite le API java.*e javax.*; ad esempio, non esiste un'API che espone la serializzazione di Xerces.

  • Aggiungendo al disordine confuso, quasi tutti i contenitori servlet (JBoss, Jetty, Glassfish, Tomcat, ecc.), Vengono spediti con Xerces in una o più delle loro /libcartelle.

I problemi

Risoluzione del conflitto

Per alcuni - o forse tutti - dei motivi sopra, molte organizzazioni pubblicano e consumano build personalizzate di Xerces nei loro POM. Questo non è davvero un problema se si dispone di una piccola applicazione e si utilizza solo Maven Central, ma diventa rapidamente un problema per il software aziendale in cui Artifactory o Nexus sta eseguendo il proxy di più repository (JBoss, Hibernate, ecc.):

xml-apis proxy di Artifactory

Ad esempio, l'organizzazione A potrebbe pubblicare xml-apiscome:

<groupId>org.apache.xerces</groupId>
<artifactId>xml-apis</artifactId>
<version>2.9.1</version>

Nel frattempo, l'organizzazione B potrebbe pubblicare lo stesso jardi:

<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
<version>1.3.04</version>

Sebbene B jarsia una versione inferiore di A jar, Maven non sa che sono lo stesso artefatto perché hanno groupIds diversi . Pertanto, non è in grado di eseguire la risoluzione dei conflitti ed entrambi jarsaranno inclusi come dipendenze risolte:

dipendenze risolte con più xml-apis

Classloader Hell

Come accennato in precedenza, il JRE viene fornito con Xerces nel RI JAXP. Mentre sarebbe bello contrassegnare tutte le dipendenze di Xerces Maven come <exclusion>s o as<provided>, il codice di terze parti da cui dipendi potrebbe non funzionare con la versione fornita in JAXP del JDK che stai utilizzando. Inoltre, hai i barattoli Xerces spediti nel tuo contenitore servlet con cui lottare. Questo ti lascia con una serie di scelte: elimini la versione servlet e speri che il tuo contenitore funzioni sulla versione JAXP? È meglio lasciare la versione servlet e sperare che i framework delle applicazioni vengano eseguiti sulla versione servlet? Se uno o due dei conflitti non risolti sopra descritti riescono a scivolare nel tuo prodotto (facile accadere in una grande organizzazione), ti ritrovi rapidamente all'inferno del classloader, chiedendoti quale versione di Xerces il classloader sta selezionando in fase di esecuzione e se no sceglierà lo stesso vaso in Windows e Linux (probabilmente no).

Soluzioni?

Abbiamo provato la marcatura di tutte le dipendenze Xerces Maven come <provided>o come <exclusion>, ma questo è difficile da applicare (in particolare con una grande squadra), dato che i manufatti hanno tanti pseudonimi ( xml-apis, xerces, xercesImpl, xmlParserAPIs, ecc). Inoltre, le nostre librerie / librerie di terze parti potrebbero non essere eseguite sulla versione JAXP o sulla versione fornita da un contenitore servlet.

Come possiamo affrontare al meglio questo problema con Maven? Dobbiamo esercitare un controllo così preciso sulle nostre dipendenze e quindi fare affidamento sul caricamento di classe a più livelli? C'è un modo per escludere globalmente tutte le dipendenze di Xerces e forzare tutti i nostri framework / librerie a usare la versione JAXP?


AGGIORNAMENTO : Joshua Spiewak ha caricato una versione patchata degli script di build di Xerces su XERCESJ-1454 che consente il caricamento su Maven Central. Vota / guarda / contribuisci a questo problema e risolviamo il problema una volta per tutte.


8
Grazie per questa domanda dettagliata. Non capisco la motivazione del team di xerces. Immagino che siano orgogliosi del loro prodotto e si divertano con altri che lo usano, ma lo stato attuale di xerces e malvagio vergognoso. Anche così, possono fare quello che vogliono anche se per me non ha senso. Mi chiedo se i ragazzi sonatype abbiano qualche suggerimento.
Travis Schneeberger,

35
Questo forse è fuori tema, ma questo è probabilmente il post migliore che abbia mai visto. Più legato alla domanda, quello che descrivi è uno dei problemi più dolorosi che possiamo incontrare. Grande iniziativa!
Jean-Rémy Revy,

2
@TravisSchneeberger Gran parte della complessità è dovuta al fatto che Sun ha scelto di usare Xerces nello stesso JRE. Difficilmente puoi incolpare la gente di Xerces per questo.
Thorbjørn Ravn Andersen,

Di solito proviamo a trovare una versione di Xerces che soddisfi tutte le librerie dipendenti per tentativi ed errori, se non è possibile quindi fare riferimento a WAR per dividere l'applicazione in WAR separati (caricatori di classi separati). Questo strumento (l'ho scritto) aiuta a capire cosa sta succedendo su jhades.org consentendo di interrogare il percorso di classe per vasi e classi - funziona anche nel caso in cui il server non si avvii ancora
Angular University

Solo un breve commento se ricevi questo errore durante l'avvio di servicemix da git bash in Windows: avviarlo invece da "normale" cmd.
Albert Hendriks,

Risposte:


112

Ci sono 2.11.0 JAR (e sorgenti JAR!) Di Xerces in Maven Central dal 20 febbraio 2013! Vedi Xerces in Maven Central . Mi chiedo perché non abbiano risolto https://issues.apache.org/jira/browse/XERCESJ-1454 ...

Ho usato:

<dependency>
    <groupId>xerces</groupId>
    <artifactId>xercesImpl</artifactId>
    <version>2.11.0</version>
</dependency>

e tutte le dipendenze si sono risolte bene, anche se proprio xml-apis-1.4.01!

E ciò che è più importante (e ciò che non era ovvio in passato): il JAR di Maven Central è lo stesso JAR della Xerces-J-bin.2.11.0.zipdistribuzione ufficiale .

Non riuscivo comunque a trovare la xml-schema-1.1-betaversione - non può essere una classifierversione di Maven a causa di dipendenze aggiuntive.


9
Anche se è molto confuso che xml-apis:xml-apis:1.4.01è più recente di xml-apis:xml-apis:2.0.2?? vedi search.maven.org/…
Hendy Irawan,

È confuso, ma è dovuto ai caricamenti di terze parti di barattoli Xerces senza versione, come diceva justingarrik nel suo post. xml-apis 2.9.1 è uguale a 1.3.04, quindi in questo senso 1.4.01 è più recente (e numericamente più grande) di 1.3.04.
liltitus27,

1
Se hai entrambi xercesImpl e xml-apis nel tuo pom.xml assicurati di eliminare la dipendenza xml-apis! Altrimenti 2.0.2 alleva la sua brutta testa.
MikeJRamsey56,

64

Francamente, praticamente tutto ciò che abbiamo incontrato funziona bene w / la versione JAXP, quindi abbiamo sempre escludiamo xml-apis e xercesImpl.


13
Potresti aggiungere uno snippet pom.xml per quello?
Chzbrgla,

10
Quando provo questo, ottengo JavaMelody e Spring che lanciano java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversalin fase di esecuzione.
David Moles,

Per aggiungere alla risposta di David Moles, ho visto che una mezza dozzina di dipendenze transitive hanno bisogno di ElementTraversal. Varie cose in primavera e Hadoop più comunemente.
Scott Carey,

2
Se ottieni java.lang.NoClassDefFoundError: org / w3c / dom / ElementTraversal prova ad aggiungere xml-apis 1.4.01 al tuo pom (ed escludi tutte le altre versioni dipendenti)
Justin Rowe

1
ElementTraversal è una nuova classe aggiunta in Xerces 11 e disponibile in xml-apis: xml-apis: 1.4.01 dipendenza. Quindi potrebbe essere necessario copiare la classe manualmente nel progetto o utilizzare l'intera dipendenza che causa le classi duplicate nel classloader. Ma in JDK9 questa classe è stata inclusa quindi nella funzione potrebbe essere necessario rimuovere il dep.
Sergey Ponomarev,

42

È possibile utilizzare il plug-in Maven Enforcer con la regola di dipendenza vietata. Ciò ti consentirebbe di vietare tutti gli alias che non desideri e consentire solo quello che desideri. Queste regole non riusciranno a costruire il tuo progetto in caso di violazione. Inoltre, se questa regola si applica a tutti i progetti in un'azienda, è possibile inserire la configurazione del plug-in in un pom principale dell'azienda.

vedere:


33

So che questo non risponde esattamente alla domanda, ma per i ppl che arrivano da Google che usano Gradle per la loro gestione delle dipendenze:

Sono riuscito a sbarazzarmi di tutti i problemi di xerces / Java8 con Gradle in questo modo:

configurations {
    all*.exclude group: 'xml-apis'
    all*.exclude group: 'xerces'
}

36
bello, con maven hai bisogno di circa 4000 linee di XML per farlo.
teknopaul,

che non ha risolto il problema. altri suggerimenti per le persone di livello Android?
Nyxee,

2
@teknopaul XML viene utilizzato esclusivamente per la configurazione. Groovy è un linguaggio di programmazione di alto livello. A volte potresti voler usare XML per la sua esplicitazione invece di groovy per la sua magia.
Dragas,

16

Immagino che ci sia una domanda a cui devi rispondere:

Esiste un xerces * .jar con cui tutto nella tua applicazione può convivere?

Altrimenti sei praticamente fregato e dovresti usare qualcosa come OSGI, che ti consente di caricare contemporaneamente diverse versioni di una libreria. Tieni presente che in sostanza sostituisce i problemi di versione del vaso con problemi del classloader ...

Se esiste una versione del genere, è possibile che il repository restituisca tale versione per tutti i tipi di dipendenze. È un brutto hack e finirebbe con la stessa implementazione di xerces nel tuo percorso di classe più volte ma meglio di avere più versioni diverse di xerces.

È possibile escludere ogni dipendenza da xerces e aggiungerne una alla versione che si desidera utilizzare.

Mi chiedo se puoi scrivere una sorta di strategia di risoluzione della versione come plugin per Maven. Questa sarebbe probabilmente la soluzione più bella ma, se possibile, necessita di alcune ricerche e codifiche.

Per la versione contenuta nel tuo ambiente di runtime, dovrai assicurarti che venga rimosso dal percorso di classe dell'applicazione o che i barattoli dell'applicazione vengano presi in considerazione per il caricamento di classe prima che la cartella lib del server venga presa in considerazione.

Quindi, per concludere: è un casino e questo non cambierà.


1
La stessa classe dallo stesso jar caricato da ClassLoaders diversi è ancora ClassCastException (in tutti i contenitori standard)
Ajax

3
Esattamente. Ecco perché ho scritto: Sii avvertito che sostanzialmente sostituisce i problemi di versione jar con problemi di caricamento di classe
Jens Schauder,

7

C'è un'altra opzione che non è stata esplorata qui: dichiarare le dipendenze di Xerces in Maven come opzionali :

<dependency>
   <groupId>xerces</groupId>
   <artifactId>xercesImpl</artifactId>
   <version>...</version>
   <optional>true</optional>
</dependency>

Fondamentalmente ciò che fa è forzare tutti i dipendenti a dichiarare la loro versione di Xerces o il loro progetto non verrà compilato. Se vogliono ignorare questa dipendenza, sono invitati a farlo, ma poi possederanno il potenziale problema.

Ciò crea un forte incentivo per i progetti a valle a:

  • Prendi una decisione attiva. Vanno con la stessa versione di Xerces o usano qualcos'altro?
  • Effettivamente testare la loro analisi (ad esempio attraverso unit test) e il caricamento di classe, nonché di non ingombrare il loro percorso di classe.

Non tutti gli sviluppatori tengono traccia delle dipendenze appena introdotte (ad es. Con mvn dependency:tree ). Questo approccio porterà immediatamente la questione alla loro attenzione.

Funziona abbastanza bene nella nostra organizzazione. Prima della sua introduzione, vivevamo nello stesso inferno che l'OP sta descrivendo.


Dovrei letteralmente usare punto-punto-punto all'interno dell'elemento versione o devo usare una versione reale come 2.6.2?
chrisinmtown,

3
@chrisinmtown La versione reale.
Daniel,

6

Ogni progetto maven dovrebbe fermarsi a seconda di xerces, probabilmente non lo fanno davvero. API XML e Impl fanno parte di Java dall'1.4. Non è necessario dipendere da API xerces o XML, è come dire che dipendi da Java o Swing. Questo è implicito.

Se fossi il capo di un repository maven, scriverei uno script per rimuovere in modo ricorsivo le dipendenze di xerces e scriverò una lettura che dice che questo repository richiede Java 1.4.

Tutto ciò che si rompe in realtà perché fa riferimento direttamente a Xerces tramite le importazioni org.apache è necessaria una correzione del codice per portarlo a livello di Java 1.4 (e lo ha fatto dal 2002) o una soluzione a livello di JVM tramite librerie approvate, non in Maven.


Quando si esegue il refactor dettagliato, è inoltre necessario cercare i nomi dei pacchetti e delle classi nel testo dei file e della configurazione Java. Scoprirai che gli sviluppatori hanno inserito l'FQN delle classi Impl in stringhe costanti che vengono utilizzate da Class.forName e dai costrutti simili.
Derek Bennett,

Ciò presuppone che tutte le implementazioni di SAX facciano la stessa cosa, il che non è vero. la libreria xercesImpl consente opzioni di configurazione che mancano nelle librerie java.xml.parser.
Amalgovinus

6

Dovresti prima eseguire il debug, per aiutarti a identificare il tuo livello di inferno XML. Secondo me, il primo passo è aggiungere

-Djavax.xml.parsers.SAXParserFactory=com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl
-Djavax.xml.transform.TransformerFactory=com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
-Djavax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl

alla riga di comando. Se funziona, inizia a escludere le librerie. In caso contrario, aggiungere

-Djaxp.debug=1

alla riga di comando.


2

Ciò che aiuterebbe, ad eccezione dell'esclusione, sono le dipendenze modulari.

Con un caricamento di classe flat (app standalone) o semi-gerarchico (JBoss AS / EAP 5.x) questo era un problema.

Ma con framework modulari come OSGi e JBoss Modules , questo non è più così tanto dolore. Le librerie possono usare qualunque libreria vogliano, indipendentemente.

Ovviamente, è comunque consigliabile attenersi a una sola implementazione e versione, ma se non c'è altro modo (utilizzando funzionalità extra da più librerie), la modularizzazione potrebbe salvarti.

Un buon esempio di moduli JBoss in azione è, naturalmente, JBoss AS 7 / EAP 6 / WildFly 8 , per il quale è stato principalmente sviluppato.

Esempio di definizione del modulo:

<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="org.jboss.msc">
    <main-class name="org.jboss.msc.Version"/>
    <properties>
        <property name="my.property" value="foo"/>
    </properties>
    <resources>
        <resource-root path="jboss-msc-1.0.1.GA.jar"/>
    </resources>
    <dependencies>
        <module name="javax.api"/>
        <module name="org.jboss.logging"/>
        <module name="org.jboss.modules"/>
        <!-- Optional deps -->
        <module name="javax.inject.api" optional="true"/>
        <module name="org.jboss.threads" optional="true"/>
    </dependencies>
</module>

In confronto con OSGi, i moduli JBoss sono più semplici e veloci. Anche se mancano alcune funzionalità, è sufficiente per la maggior parte dei progetti che sono (principalmente) sotto il controllo di un fornitore e consentono un avvio rapido sorprendente (a causa della risoluzione delle dipendenze paralizzate).

Si noti che è in corso uno sforzo di modularizzazione per Java 8 , ma AFAIK è principalmente quello di modularizzare JRE stesso, non sicuro se sarà applicabile alle app.


I moduli jboss riguardano la modularizzazione statica. Ha poco a che fare con la modularizzazione di runtime che OSGi ha da offrire: direi che si complimentano a vicenda. È un bel sistema però.
eis,

* complemento invece di complimento
Robert Mikes,

2

Apparentemente xerces:xml-apis:1.4.01non è più in centro maven, che è comunque cosaxerces:xercesImpl:2.11.0 riferimento.

Questo funziona per me:

<dependency>
  <groupId>xerces</groupId>
  <artifactId>xercesImpl</artifactId>
  <version>2.11.0</version>
  <exclusions>
    <exclusion>
      <groupId>xerces</groupId>
      <artifactId>xml-apis</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>xml-apis</groupId>
  <artifactId>xml-apis</artifactId>
  <version>1.4.01</version>
</dependency>

1

Amico mio è molto semplice, ecco un esempio:

<dependency>
    <groupId>xalan</groupId>
    <artifactId>xalan</artifactId>
    <version>2.7.2</version>
    <scope>${my-scope}</scope>
    <exclusions>
        <exclusion>
        <groupId>xml-apis</groupId>
        <artifactId>xml-apis</artifactId>
    </exclusion>
</dependency>

E se vuoi controllare nel terminale (console Windows per questo esempio) che il tuo albero Maven non abbia problemi:

mvn dependency:tree -Dverbose | grep --color=always '(.* conflict\|^' | less -r
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.