Umgang mit "Xerces Hölle" in Java / Maven?


732

In meinem Büro reicht die bloße Erwähnung des Wortes Xerces aus, um Entwickler zu mörderischer Wut anzuregen. Ein flüchtiger Blick auf die anderen Xerces-Fragen zu SO scheint darauf hinzudeuten, dass fast alle Maven-Benutzer irgendwann von diesem Problem "berührt" werden. Leider erfordert das Verständnis des Problems ein wenig Wissen über die Geschichte von Xerces ...

Geschichte

  • Xerces ist der am häufigsten verwendete XML-Parser im Java-Ökosystem. Fast jede in Java geschriebene Bibliothek oder jedes in Java geschriebene Framework verwendet Xerces in irgendeiner Form (transitiv, wenn nicht direkt).

  • Die in den offiziellen Binärdateien enthaltenen Xerces-Gläser sind bis heute nicht versioniert. Beispielsweise wird das Implementierungsglas für Xerces 2.11.0 benannt xercesImpl.jarund nicht xercesImpl-2.11.0.jar.

  • Das Xerces-Team verwendet Maven nicht, dh es wird keine offizielle Version auf Maven Central hochgeladen .

  • Xerces wurde früher als einzelnes jar ( xerces.jar) veröffentlicht, wurde jedoch in zwei Jars aufgeteilt, von denen eines die API ( xml-apis.jar) und eines die Implementierungen dieser APIs ( xercesImpl.jar) enthielt . Viele ältere Maven POMs erklären immer noch eine Abhängigkeit von xerces.jar. Irgendwann in der Vergangenheit wurde Xerces auch als veröffentlicht xmlParserAPIs.jar, von dem auch einige ältere POMs abhängen.

  • Die Versionen, die den JARs xml-apis und xercesImpl von denjenigen zugewiesen werden, die ihre Jars in Maven-Repositorys bereitstellen, sind häufig unterschiedlich. Beispielsweise könnte xml-apis die Version 1.3.03 und xercesImpl die Version 2.8.0 erhalten, obwohl beide von Xerces 2.8.0 stammen. Dies liegt daran, dass Benutzer das xml-apis-Glas häufig mit der Version der von ihm implementierten Spezifikationen versehen. Es ist eine sehr schöne, aber unvollständige Aufteilung dieses hier .

  • Xerces ist der XML-Parser, der in der Referenzimplementierung der in der JRE enthaltenen Java-API für die XML-Verarbeitung (JAXP) verwendet wird. Die Implementierungsklassen werden unter dem com.sun.*Namespace neu gepackt, was den direkten Zugriff auf sie gefährlich macht, da sie in einigen JREs möglicherweise nicht verfügbar sind. Es wird jedoch nicht die gesamte Xerces-Funktionalität über die APIs java.*und javax.*bereitgestellt. Beispielsweise gibt es keine API, die die Xerces-Serialisierung verfügbar macht.

  • Zusätzlich zu dem verwirrenden Durcheinander werden fast alle Servlet-Container (JBoss, Jetty, Glassfish, Tomcat usw.) mit Xerces in einem oder mehreren ihrer /libOrdner ausgeliefert.

Probleme

Konfliktlösung

Aus einigen - oder vielleicht allen - der oben genannten Gründe veröffentlichen und verwenden viele Unternehmen benutzerdefinierte Builds von Xerces in ihren POMs. Dies ist kein wirkliches Problem, wenn Sie eine kleine Anwendung haben und nur Maven Central verwenden, aber es wird schnell zu einem Problem für Unternehmenssoftware, bei der Artifactory oder Nexus mehrere Repositorys (JBoss, Hibernate usw.) als Proxy verwenden:

xml-apis wird von Artifactory vertreten

Beispielsweise könnte Organisation A Folgendes veröffentlichen xml-apis:

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

In der Zwischenzeit könnte Organisation B dasselbe veröffentlichen jarwie:

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

Obwohl B jareine niedrigere Version als A ist jar, weiß Maven nicht, dass sie dasselbe Artefakt sind, weil sie unterschiedliche groupIds haben. Daher kann keine Konfliktlösung durchgeführt werden, und beide jars werden als gelöste Abhängigkeiten aufgenommen:

Abhängigkeiten mit mehreren XML-APIs wurden behoben

Klassenlader Hölle

Wie oben erwähnt, wird die JRE mit Xerces im JAXP RI ausgeliefert. Es wäre zwar schön, alle Xerces Maven-Abhängigkeiten als <exclusion>s oder als zu markieren<provided>Der Code eines Drittanbieters, von dem Sie abhängig sind, funktioniert möglicherweise nicht mit der in JAXP des von Ihnen verwendeten JDK bereitgestellten Version. Außerdem haben Sie die Xerces-Gläser in Ihrem Servlet-Container, um damit fertig zu werden. Sie haben also eine Reihe von Möglichkeiten: Löschen Sie die Servlet-Version und hoffen, dass Ihr Container auf der JAXP-Version ausgeführt wird? Ist es besser, die Servlet-Version zu verlassen und zu hoffen, dass Ihre Anwendungsframeworks auf der Servlet-Version ausgeführt werden? Wenn es einem oder zwei der oben beschriebenen ungelösten Konflikte gelingt, in Ihr Produkt einzudringen (was in einer großen Organisation leicht vorkommt), befinden Sie sich schnell in der Hölle der Klassenlader und fragen sich, welche Version von Xerces der Klassenlader zur Laufzeit auswählt und ob dies der Fall ist oder nicht wird das gleiche Glas in Windows und Linux auswählen (wahrscheinlich nicht).

Lösungen?

Wir haben versucht , alle Xerces Maven Abhängigkeiten als Markierung <provided>oder als ein <exclusion>, aber das ist schwer durchzusetzen (vor allem mit einem großen Team) gegeben , dass die Artefakte haben so viele Aliase ( xml-apis, xerces, xercesImpl, xmlParserAPIs, etc.). Darüber hinaus können unsere Bibliotheken / Frameworks von Drittanbietern möglicherweise nicht auf der JAXP-Version oder der von einem Servlet-Container bereitgestellten Version ausgeführt werden.

Wie können wir dieses Problem am besten mit Maven lösen? Müssen wir unsere Abhängigkeiten so genau kontrollieren und uns dann auf das gestufte Laden von Klassen verlassen? Gibt es eine Möglichkeit, alle Xerces-Abhängigkeiten global auszuschließen und alle unsere Frameworks / Bibliotheken zur Verwendung der JAXP-Version zu zwingen?


UPDATE : Joshua Spiewak hat eine gepatchte Version der Xerces-Build-Skripte auf XERCESJ-1454 hochgeladen , die das Hochladen auf Maven Central ermöglicht. Stimmen Sie ab / schauen Sie zu / tragen Sie zu diesem Problem bei und lassen Sie uns dieses Problem ein für alle Mal beheben.


8
Vielen Dank für diese ausführliche Frage. Ich verstehe die Motivation des xerces-Teams nicht. Ich würde mir vorstellen, dass sie stolz auf ihr Produkt sind und Freude daran haben, es zu verwenden, aber der aktuelle Zustand von Xerces und Maven ist eine Schande. Trotzdem können sie tun, was sie wollen, auch wenn es für mich keinen Sinn ergibt. Ich frage mich, ob die Sonatyp-Typen Vorschläge haben.
Travis Schneeberger

35
Dies ist vielleicht kein Thema, aber dies ist wahrscheinlich der bessere Beitrag, den ich je gesehen habe. Was Sie beschreiben, ist eines der schmerzhaftesten Probleme, denen wir begegnen können. Tolle Initiative!
Jean-Rémy Revy

2
@TravisSchneeberger Ein Großteil der Komplexität ist darauf zurückzuführen, dass Sun Xerces in der JRE selbst verwendet hat. Sie können die Xerces-Leute kaum dafür verantwortlich machen.
Thorbjørn Ravn Andersen

Normalerweise versuchen wir, eine Version von Xerces zu finden, die alle abhängigen Bibliotheken durch Ausprobieren erfüllt. Wenn dies nicht möglich ist, überarbeiten Sie WARs, um die Anwendung in separate WARs (separate Klassenlader) aufzuteilen. Dieses Tool (ich habe es geschrieben) hilft zu verstehen, was auf jhades.org vor sich geht, indem es ermöglicht, den Klassenpfad nach Jars und Klassen abzufragen - es funktioniert auch, wenn der Server noch nicht startet
Angular University

Nur ein kurzer Kommentar, wenn Sie diesen Fehler beim Starten von servicemix von git bash in Windows erhalten: Starten Sie ihn stattdessen von "normal" cmd.
Albert Hendriks

Antworten:


112

Seit dem 20. Februar 2013 gibt es in Maven Central 2.11.0 JARs (und Quell-JARs!) Von Xerces! Siehe Xerces in Maven Central . Ich frage mich, warum sie https://issues.apache.org/jira/browse/XERCESJ-1454 nicht gelöst haben ...

Ich habe verwendet:

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

und alle Abhängigkeiten haben sich gut aufgelöst - sogar richtig xml-apis-1.4.01!

Und was am wichtigsten ist (und was in der Vergangenheit nicht offensichtlich war) - die JAR in Maven Central ist dieselbe JAR wie in der offiziellen Xerces-J-bin.2.11.0.zipDistribution .

Ich konnte jedoch keine xml-schema-1.1-betaVersion finden - es kann classifieraufgrund zusätzlicher Abhängigkeiten keine Maven- Version sein.


9
Obwohl es sehr verwirrend ist, dass xml-apis:xml-apis:1.4.01es neuer ist als xml-apis:xml-apis:2.0.2?? siehe search.maven.org/…
Hendy Irawan

Es ist verwirrend, aber es liegt an den Uploads von nicht versionierten Xerces-Gläsern durch Dritte, wie justingarrik in seinem Beitrag sagte. xml-apis 2.9.1 ist dasselbe wie 1.3.04, daher ist 1.4.01 in diesem Sinne neuer (und numerisch größer) als 1.3.04.
Liltitus27

1
Wenn Sie sowohl xercesImpl als auch xml-apis in Ihrer pom.xml haben, müssen Sie die xml-apis-Abhängigkeit löschen! Ansonsten reckt 2.0.2 seinen hässlichen Kopf.
MikeJRamsey56

64

Ehrlich gesagt, so ziemlich alles , dass wir funktionieren gut w / die JAXP Version begegnet sind, so wir immer ausschließen xml-apis und xercesImpl.


13
Könnten Sie dafür ein pom.xml-Snippet hinzufügen?
Chzbrgla

10
Wenn ich das versuche, werden JavaMelody und Spring java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversalzur Laufzeit geworfen .
David Moles

Um die Antwort von David Moles zu ergänzen: Ich habe gesehen, dass ein halbes Dutzend transitiver Abhängigkeiten ElementTraversal benötigen. Verschiedene Dinge im Frühling und Hadoop am häufigsten.
Scott Carey

2
Wenn Sie java.lang.NoClassDefFoundError: org / w3c / dom / ElementTraversal erhalten, versuchen Sie, xml-apis 1.4.01 zu Ihrem pom hinzuzufügen (und schließen Sie alle anderen abhängigen Versionen aus)
Justin Rowe

1
ElementTraversal ist eine neue Klasse, die in Xerces 11 hinzugefügt wurde und in der Abhängigkeit xml-apis: xml-apis: 1.4.01 verfügbar ist. Daher müssen Sie die Klasse möglicherweise manuell in Ihr Projekt kopieren oder die gesamte Abhängigkeit verwenden, was zu doppelten Klassen im Klassenladeprogramm führt. In JDK9 war diese Klasse jedoch enthalten, sodass Sie in der Funktion möglicherweise die Dep entfernen müssen.
Sergey Ponomarev

42

Sie können das Maven Enforcer-Plugin mit der Regel für gesperrte Abhängigkeiten verwenden. Auf diese Weise können Sie alle Aliase sperren, die Sie nicht möchten, und nur den Aliase zulassen, den Sie möchten. Diese Regeln schlagen beim Erstellen des Maven-Builds Ihres Projekts fehl. Wenn diese Regel für alle Projekte in einem Unternehmen gilt, können Sie die Plugin-Konfiguration in ein übergeordnetes Unternehmen einfügen.

sehen:


33

Ich weiß, dass dies die Frage nicht genau beantwortet, aber für Leute, die von Google kommen und Gradle für ihr Abhängigkeitsmanagement verwenden:

Ich habe es geschafft, alle xerces / Java8-Probleme mit Gradle wie folgt zu beseitigen:

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

36
schön, mit maven brauchst du ungefähr 4000 Zeilen XML, um das zu tun.
Teknopaul

das hat das Problem nicht gelöst. Irgendwelche anderen Tipps für Android-Gradle-Leute?
Nyxee

2
@teknopaul XML wird ausschließlich zur Konfiguration verwendet. Groovy ist eine Programmiersprache auf hohem Niveau. Manchmal möchten Sie vielleicht XML für seine Aussagekraft verwenden, anstatt für seine Magie groovig zu sein.
Dragas

16

Ich denke, es gibt eine Frage, die Sie beantworten müssen:

Gibt es eine xerces * .jar, mit der alles in Ihrer Anwendung leben kann?

Wenn nicht, sind Sie im Grunde genommen geschraubt und müssten so etwas wie OSGI verwenden, wodurch Sie verschiedene Versionen einer Bibliothek gleichzeitig laden können. Seien Sie gewarnt, dass es im Grunde JAR-Versionsprobleme durch Classloader-Probleme ersetzt ...

Wenn es eine solche Version gibt, können Sie Ihr Repository veranlassen, diese Version für alle Arten von Abhängigkeiten zurückzugeben. Es ist ein hässlicher Hack und würde mehrmals dieselbe xerces-Implementierung in Ihrem Klassenpfad haben, aber besser als mehrere verschiedene Versionen von xerces.

Sie können jede Abhängigkeit von xerces ausschließen und eine zu der Version hinzufügen, die Sie verwenden möchten.

Ich frage mich, ob Sie eine Art Versionsauflösungsstrategie als Plugin für Maven schreiben können. Dies wäre wahrscheinlich die schönste Lösung, erfordert aber, wenn überhaupt, einige Nachforschungen und Codierungen.

Für die in Ihrer Laufzeitumgebung enthaltene Version müssen Sie sicherstellen, dass sie entweder aus dem Anwendungsklassenpfad entfernt wird oder die Anwendungsgläser zuerst für das Laden der Klasse berücksichtigt werden, bevor der lib-Ordner des Servers berücksichtigt wird.

Um es zusammenzufassen: Es ist ein Chaos und das wird sich nicht ändern.


1
Dieselbe Klasse aus demselben Glas, die von verschiedenen ClassLoadern geladen wurde, ist immer noch eine ClassCastException (in allen Standardcontainern)
Ajax

3
Genau. Deshalb habe ich geschrieben: Seien Sie gewarnt, dass es im Grunde JAR-Versionsprobleme durch Classloader-Probleme ersetzt
Jens Schauder

7

Es gibt eine andere Option, die hier nicht untersucht wurde: Deklarieren von Xerces-Abhängigkeiten in Maven als optional :

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

Im Grunde bedeutet dies, dass alle abhängigen Personen gezwungen werden, ihre Version von Xerces zu deklarieren, da sonst ihr Projekt nicht kompiliert wird. Wenn sie diese Abhängigkeit überschreiben möchten, können sie dies gerne tun, aber dann besitzen sie das potenzielle Problem.

Dies schafft einen starken Anreiz für nachgelagerte Projekte:

  • Treffen Sie eine aktive Entscheidung. Gehen sie mit der gleichen Version von Xerces oder verwenden sie etwas anderes?
  • Testen Sie tatsächlich das Parsen (z. B. durch Unit-Tests) und das Laden von Klassen, um den Klassenpfad nicht zu überladen.

Nicht alle Entwickler verfolgen neu eingeführte Abhängigkeiten (z mvn dependency:tree. B. mit ). Dieser Ansatz wird sie sofort auf die Angelegenheit aufmerksam machen.

In unserer Organisation funktioniert das ganz gut. Vor seiner Einführung lebten wir in derselben Hölle, die das OP beschreibt.


Sollte ich im Versionselement buchstäblich Punkt-Punkt-Punkt verwenden, oder muss ich eine echte Version wie 2.6.2 verwenden?
Chrisinmtown

3
@chrisinmtown Die echte Version.
Daniel

6

Jedes Maven-Projekt sollte abhängig von xerces aufhören, wahrscheinlich nicht wirklich. XML-APIs und ein Impl sind seit 1.4 Teil von Java. Es ist nicht erforderlich, sich auf Xerces oder XML-APIs zu verlassen. Dies bedeutet, dass Sie von Java oder Swing abhängig sind. Dies ist implizit.

Wenn ich Chef eines Maven-Repos wäre, würde ich ein Skript schreiben, um xerces-Abhängigkeiten rekursiv zu entfernen, und ein Read-Me schreiben, das besagt, dass für dieses Repo Java 1.4 erforderlich ist.

Alles, was tatsächlich kaputt geht, weil es direkt über org.apache-Importe auf Xerces verweist, benötigt einen Code-Fix, um es auf Java 1.4-Ebene (und seit 2002) oder eine Lösung auf JVM-Ebene über indossierte Bibliotheken zu bringen, nicht in Maven.


Wenn Sie den von Ihnen beschriebenen Refactor ausführen, müssen Sie auch im Text Ihrer Java-Dateien und -Konfigurationen nach den Paket- und Klassennamen suchen. Sie werden feststellen, dass Entwickler den FQN der Impl-Klassen in konstante Zeichenfolgen gesetzt haben, die von Class.forName und ähnlichen Konstrukten verwendet werden.
Derek Bennett

Dies setzt voraus, dass alle SAX-Implementierungen dasselbe tun, was nicht stimmt. Die xercesImpl-Bibliothek ermöglicht Konfigurationsoptionen, die den java.xml.parser-Bibliotheken fehlen.
Amalgovinus

6

Sie sollten zuerst debuggen, um Ihre XML-Hölle zu identifizieren. Meiner Meinung nach ist der erste Schritt das Hinzufügen

-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

zur Kommandozeile. Wenn dies funktioniert, schließen Sie Bibliotheken aus. Wenn nicht, dann hinzufügen

-Djaxp.debug=1

zur Kommandozeile.


2

Was helfen würde, außer auszuschließen, sind modulare Abhängigkeiten.

Bei einer flachen Klassenladung (eigenständige App) oder halbhierarchisch (JBoss AS / EAP 5.x) war dies ein Problem.

Bei modularen Frameworks wie OSGi und JBoss Modules ist dies jedoch nicht mehr so ​​schmerzhaft. Die Bibliotheken können unabhängig voneinander die gewünschte Bibliothek verwenden.

Natürlich ist es immer noch am empfehlenswertesten, sich nur an eine einzige Implementierung und Version zu halten. Wenn es jedoch keinen anderen Weg gibt (zusätzliche Funktionen aus mehr Bibliotheken verwenden), können Sie durch Modularisierung sparen.

Ein gutes Beispiel für JBoss-Module in Aktion ist natürlich JBoss AS 7 / EAP 6 / WildFly 8 , für das es hauptsächlich entwickelt wurde.

Beispiel für eine Moduldefinition:

<?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>

Im Vergleich zu OSGi sind JBoss-Module einfacher und schneller. Obwohl bestimmte Funktionen fehlen, ist dies für die meisten Projekte ausreichend, die (meistens) von einem Anbieter kontrolliert werden, und ermöglicht einen erstaunlich schnellen Start (aufgrund der Auflösung von parallelen Abhängigkeiten).

Beachten Sie, dass für Java 8 Modularisierungsbemühungen im Gange sind, AFAIK jedoch hauptsächlich die JRE selbst modularisiert und nicht sicher ist, ob sie für Apps anwendbar ist.


Bei jboss-Modulen geht es um statische Modularisierung. Es hat wenig mit der Laufzeitmodularisierung zu tun, die OSGi zu bieten hat - ich würde sagen, sie ergänzen sich gegenseitig. Es ist jedoch ein schönes System.
Eis

* Komplement statt Kompliment
Robert Mikes

2

Anscheinend xerces:xml-apis:1.4.01ist nicht mehr in Maven Central, was jedoch darauf xerces:xercesImpl:2.11.0verweist.

Das funktioniert bei mir:

<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

Mein Freund, das ist sehr einfach, hier ein Beispiel:

<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>

Und wenn Sie im Terminal (Windows-Konsole für dieses Beispiel) überprüfen möchten, ob Ihr Maven-Baum keine Probleme hat:

mvn dependency:tree -Dverbose | grep --color=always '(.* conflict\|^' | less -r
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.