it-swarm.com.de

Mit "Xerces Hell" in Java / Maven umgehen?

In meinem Büro reicht die bloße Erwähnung von Word Xerces aus, um mörderische Wut von Entwicklern auszulösen. 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" sind. 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. Nahezu jede in Java geschriebene Bibliothek oder jedes Framework verwendet Xerces in gewisser Weise (transitiv, wenn nicht direkt).

  • Die in den offiziellen Binärdateien enthaltenen Xerces-Gläser sind bis heute nicht versioniert. Das Implementierungs-JAR für Xerces 2.11.0 heißt beispielsweise xercesImpl.jar und nicht xercesImpl-2.11.0.jar.

  • Das Xerces-Team verwendet keinen Maven , was bedeutet, dass es keine offizielle Veröffentlichung auf Maven Central hochlädt.

  • Xerces war als einzelnes JAR veröffentlicht (xerces.jar), wurde jedoch in zwei JARs aufgeteilt, von denen eines die API (xml-apis.jar) und eines die Implementierungen dieser APIs enthielt (xercesImpl.jar). Viele ältere Maven-POMs deklarieren immer noch eine Abhängigkeit von xerces.jar. Zu irgendeinem Zeitpunkt in der Vergangenheit wurde Xerces auch als xmlParserAPIs.jar veröffentlicht, von dem auch einige ältere POMs abhängen.

  • Die Versionen, die den JARs xml-apis und xercesImpl von denjenigen zugewiesen wurden, die ihre JARs in Maven-Repositorys bereitstellen, sind häufig unterschiedlich. Zum Beispiel könnten xml-apis die Version 1.3.03 und xercesImpl die Version 2.8.0 zugewiesen bekommen, obwohl beide von Xerces 2.8.0 stammen. Dies liegt daran, dass die Leute die xml-apis-JAR-Datei häufig mit der Version der Spezifikationen versehen, die sie implementiert. Es gibt eine sehr schöne, aber unvollständige Aufschlüsselung dieses hier .

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

  • Fast alle Servlet-Container (JBoss, Jetty, Glassfish, Tomcat usw.) werden mit Xerces in einem oder mehreren ihrer /lib -Ordner ausgeliefert.

Probleme

Konfliktlösung

Aus einigen oder 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.) proxysetzen:

xml-apis proxied by Artifactory

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

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

In der Zwischenzeit veröffentlicht Organisation B möglicherweise das gleiche jar wie:

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

Obwohl Bs jar eine niedrigere Version als As jar ist, weiß Maven nicht, dass es sich um dasselbe Artefakt handelt, da sie unterschiedliche groupIds haben. Daher kann keine Konfliktlösung durchgeführt werden und beide jars werden als aufgelöste Abhängigkeiten einbezogen:

resolved dependencies with multiple xml-apis

Classloader Hölle

Wie oben erwähnt, wird die JRE mit Xerces im JAXP RI ausgeliefert. Es ist zwar hilfreich, alle Xerces Maven-Abhängigkeiten als <exclusion>s oder <provided> zu kennzeichnen, der Code von Drittanbietern, von dem Sie abhängig sind, funktioniert jedoch möglicherweise mit der in JAXP des JDK, den Sie verwenden, bereitgestellten Version mit. Außerdem haben Sie die Xerces-Gläser in Ihrem Servlet-Container, mit denen Sie fertig werden müssen. 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 Anwendungs-Frameworks auf der Servlet-Version ausgeführt werden? Wenn einer oder zwei der oben beschriebenen ungelösten Konflikte in Ihr Produkt eindringen (was in einer großen Organisation leicht vorkommt), befinden Sie sich schnell in der Hölle der Klassenladeprogramme und fragen sich, welche Version von Xerces das Klassenladeprogramm zur Laufzeit auswählt und ob es dies ist oder nicht wird das gleiche jar in Windows und Linux auswählen (wahrscheinlich nicht).

Lösungen?

Wir haben versucht, alle Xerces Maven-Abhängigkeiten als <provided> oder als <exclusion> zu kennzeichnen, aber dies ist schwierig durchzusetzen (insbesondere bei einem großen Team), da die Artefakte so viele Aliase haben (xml-apis, xerces, xercesImpl, xmlParserAPIs usw.). Darüber hinaus können unsere Libs/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 eine so genaue Kontrolle über unsere Abhängigkeiten ausüben 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/Libs zur Verwendung der JAXP-Version zu zwingen?


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

690
Justin Garrick

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

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 der richtige 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.Zip -Distribution .

Ich konnte jedoch die xml-schema-1.1-beta -Version nicht finden - es kann sich wegen zusätzlicher Abhängigkeiten nicht um eine von Maven classifier -Version handeln.

103

Ehrlich gesagt funktioniert so ziemlich alles, was uns begegnet ist, mit der JAXP-Version einwandfrei, also wir immer ausschließenxml-apis und xercesImpl.

62
jtahlborn

Sie können das Maven-Enforcer-Plugin mit der verbotenen Abhängigkeitsregel verwenden. Auf diese Weise können Sie alle Aliase, die Sie nicht möchten, sperren und nur denjenigen zulassen, den Sie möchten. Diese Regeln schlagen fehl, wenn der Maven-Build Ihres Projekts verletzt wird. Wenn diese Regel für alle Projekte in einem Unternehmen gilt, können Sie die Plug-in-Konfiguration auch in einem übergeordneten Unternehmens-POM ablegen.

sehen:

42

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

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

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

Gibt es ein xerces * .jar, mit dem 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 grundsätzlich Probleme mit der Jar-Version durch Probleme mit dem Classloader 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 am Ende mehrere Male dieselbe xerces-Implementierung in Ihrem Klassenpfad haben, aber besser, als mehrere verschiedene Versionen von xerces zu haben.

Sie können jede Abhängigkeit von xerces ausschließen und der gewünschten Version eine hinzufügen.

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, aber wenn dies überhaupt machbar ist, muss nachgeforscht und programmiert werden.

Für die in Ihrer Laufzeitumgebung enthaltene Version müssen Sie sicherstellen, dass sie entweder aus dem Klassenpfad der Anwendung entfernt wird oder dass die Anwendungs-JAR-Dateien beim Laden der Klasse zuerst berücksichtigt werden, bevor der lib-Ordner des Servers berücksichtigt wird.

Also zum Abschluss: Es ist ein Chaos und das wird sich nicht ändern.

16
Jens Schauder

Sie sollten zuerst das Debuggen durchführen, um den Grad Ihrer XML-Hölle zu ermitteln. 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 zunächst Bibliotheken aus. Wenn nicht, fügen Sie hinzu

-Djaxp.debug=1

zur Kommandozeile.

6
Derek Bennett

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, oder dass ihr Projekt nicht kompiliert wird. Wenn sie diese Abhängigkeit überschreiben möchten, können sie dies gerne tun, aber dann wird ihnen das potenzielle Problem gehören.

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 sowie, um den Klassenpfad nicht zu überfrachten.

Nicht alle Entwickler verfolgen neu eingeführte Abhängigkeiten (z. B. mit mvn dependency:tree). Dieser Ansatz wird die Angelegenheit sofort zur Kenntnis bringen.

Bei uns funktioniert das ganz gut. Vor seiner Einführung lebten wir in der gleichen Hölle, die das OP beschreibt.

6
Daniel

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 besteht keine Notwendigkeit, sich auf Xerces oder XML-APIs zu verlassen. Das heißt, Sie sind abhängig von Java oder Swing. Das ist implizit.

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

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

5
teknopaul

Anscheinend ist xerces:xml-apis:1.4.01 nicht mehr in der Maven-Zentrale, worauf jedoch xerces:xercesImpl:2.11.0 verweist.

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

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

Mit einem Flat Classloading (Standalone-App) oder semi-hierarchisch (JBoss AS/EAP 5.x) war dies ein Problem.

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

Natürlich ist es immer noch am empfehlenswertesten, nur eine einzige Implementierung und Version beizubehalten, aber wenn es keine andere Möglichkeit gibt (zusätzliche Funktionen aus mehr Bibliotheken zu 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 in erster Linie 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 ist JBoss Modules einfacher und schneller. Obwohl bestimmte Funktionen fehlen, reicht dies für die meisten Projekte aus, die (meistens) von einem Hersteller gesteuert werden, und ermöglicht einen erstaunlich schnellen Start (aufgrund der parallelen Auflösung von Abhängigkeiten).

Beachten Sie, dass für Java 8 Modularisierungsbemühungen laufen , aber AFAIK dient in erster Linie der Modularisierung der JRE selbst und ist nicht sicher, ob sie anwendbar sein wird zu Apps.

2
Ondra Žižka

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>
            </exclusions>
        </dependency>

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

mvn dependency:tree -Dverbose | grep --color=always '(.* conflict\|^' | less -r
1
Eduardo