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




classloader dependency-management (8)

Anscheinend xerces:xml-apis:1.4.01 ist nicht mehr in maven central, was aber xerces:xercesImpl:2.11.0 references.

Das funktioniert für mich:

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

In meinem Büro reicht die bloße Erwähnung des Wortes Xerces aus, um mörderische Wut von Entwicklern zu schüren. Ein flüchtiger Blick auf die anderen Xerces-Fragen zu SO scheint darauf hinzuweisen, dass fast alle Maven-Nutzer 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. Fast jede Bibliothek oder jedes Framework, die in Java geschrieben sind, verwendet Xerces in gewisser Weise (transitiv, wenn nicht direkt).

  • Die Xerces-Jars, die in den offiziellen Binärdateien enthalten sind, sind bis heute nicht versioniert. Beispiel: Das Xerces 2.11.0-Implementierungs-JAR heißt xercesImpl.jar und nicht xercesImpl-2.11.0.jar .

  • Das Xerces-Team verwendet Maven nicht , was bedeutet, dass sie keine offizielle Version zu Maven Central hochladen.

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

  • Die Versionen, die den Jmls von xml-apis und xercesImpl von denjenigen zugewiesen werden, die ihre Jars in Maven-Repositories deployen, unterscheiden sich oft. Zum Beispiel könnte xml-apis die Version 1.3.03 erhalten und xercesImpl könnte die Version 2.8.0 erhalten, obwohl beide von Xerces 2.8.0 stammen. Dies liegt daran, dass Personen das xml-apis-jar häufig mit der Version der Spezifikationen versehen, die es implementiert. Es gibt eine sehr schöne, aber unvollständige Aufschlüsselung here .

  • Um die Angelegenheit zu komplizieren, ist Xerces der XML-Parser, der in der Referenzimplementierung der Java-API für die XML-Verarbeitung (JAXP) verwendet wird, die in der JRE enthalten ist. Die Implementierungsklassen werden unter dem Namespace com.sun.* verpackt, was es gefährlich macht, direkt auf sie zuzugreifen, da sie in einigen JREs möglicherweise nicht verfügbar sind. Nicht alle Xerces-Funktionen werden jedoch über die APIs java.* Und javax.* Beispielsweise gibt es keine API, die Xerces-Serialisierung ermöglicht.

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

Probleme

Konfliktlösung

Aus einigen - oder vielleicht allen - Gründen veröffentlichen und konsumieren viele Organisationen benutzerdefinierte Builds von Xerces in ihren POMs. Dies ist nicht wirklich ein Problem, wenn Sie eine kleine Anwendung haben und nur Maven Central verwenden, aber es wird schnell zu einem Problem für Unternehmenssoftware, wo Artifactory oder Nexus mehrere Repositories (JBoss, Hibernate, etc.) proxybt:

Zum Beispiel könnte Organisation A xml-apis wie xml-apis veröffentlichen:

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

In der Zwischenzeit könnte Organisation B den gleichen jar wie jar :

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

Obwohl Bs jar eine niedrigere Version als der jar A ist, weiß Maven nicht, dass sie das gleiche Artefakt sind, da sie verschiedene groupId . Daher kann keine Konfliktlösung durchgeführt werden und beide jar Dateien werden als aufgelöste Abhängigkeiten einbezogen:

Klassenlader Hell

Wie bereits erwähnt, wird die JRE im JAXP RI mit Xerces ausgeliefert. Es wäre zwar nett, alle Abhängigkeiten von Xerces Maven als <exclusion> s oder als <provided> zu kennzeichnen, aber der von Ihnen verwendete Fremdanbietercode funktioniert möglicherweise nicht mit der in JAXP des von Ihnen verwendeten JDK bereitgestellten Version. Zusätzlich haben Sie die Xerces-Jars, die in Ihrem Servlet-Container ausgeliefert werden. Dies bietet Ihnen eine Reihe von Möglichkeiten: Löschen Sie die Servlet-Version und hoffen Sie, dass Ihr Container auf der JAXP-Version ausgeführt wird? Ist es besser, die Servlet-Version zu belassen und hoffen, dass Ihre Anwendungs-Frameworks auf der Servlet-Version ausgeführt werden? Wenn ein oder zwei ungelöste Konflikte in Ihr Produkt schlüpfen (was in einer großen Organisation leicht passieren kann), befinden Sie sich schnell in der Hölle des Klassenladers und fragen sich, welche Version von Xerces der Klassenlader zur Laufzeit auswählt und ob sie dies tut oder nicht wird das gleiche Glas in Windows und Linux auswählen (wahrscheinlich nicht).

Lösungen?

Wir haben versucht, alle Abhängigkeiten von Xerces Maven als <provided> oder als <exclusion> xerces , aber dies ist schwierig zu erzwingen (besonders bei einem großen Team), da die Artefakte so viele Aliase haben ( xml-apis , xerces , xercesImpl , xmlParserAPIs usw.). Außerdem 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 mit Maven am besten lösen? Müssen wir eine so fein abgestimmte Kontrolle über unsere Abhängigkeiten ausüben und uns dann auf ein abgestuftes Classloading 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 Build-Skripte zu XERCESJ-1454 hochgeladen, die den Upload zu Maven Central ermöglicht. Bewerten / beobachten / beitragen Sie zu diesem Problem und lassen Sie uns das Problem ein für alle Mal beheben.


Ehrlich gesagt funktioniert so ziemlich alles, was wir xercesImpl der JAXP-Version, also schließen wir immer xml-apis und xercesImpl .


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

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

Im Grunde bedeutet dies, dass alle Abhängigen gezwungen werden, ihre Version von Xerces oder ihr Projekt zu kompilieren. Wenn sie diese Abhängigkeit aufheben wollen, können sie das gerne tun, aber dann haben sie das potentielle Problem.

Dies schafft einen starken Anreiz für nachgelagerte Projekte:

  • Treffen Sie eine aktive Entscheidung. Verwenden sie die gleiche Version von Xerces oder verwenden Sie etwas anderes?
  • Testen Sie tatsächlich ihr Parsing (z. B. durch Komponententests) und Classloading sowie nicht ihren Klassenpfad zu überladen.

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

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


Ich denke, es gibt eine Frage, die du beantworten musst:

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

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

Wenn es eine solche Version gibt, könnte Ihr Repository diese Version für alle Arten von Abhängigkeiten zurückgeben. Es ist ein hässlicher Hack und würde die gleiche xerces-Implementierung in Ihrem Klassenpfad mehrmals ergeben, 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 beste Lösung, aber wenn überhaupt möglich, bedarf es einiger Forschung und Codierung.

Für die in Ihrer Laufzeitumgebung enthaltene Version müssen Sie sicherstellen, dass sie entweder aus dem Klassenpfad der Anwendung entfernt wird oder die Anwendungsgruppen zuerst für das Classloading berücksichtigt werden, bevor der lib-Ordner des Servers berücksichtigt wird.

Also, um es abzuschließen: Es ist ein Durcheinander und das wird sich nicht ändern.


Jedes Maven-Projekt sollte aufhören abhängig von Xerces, wahrscheinlich nicht wirklich. XML-APIs und ein Impl gehören seit 1.4 zu Java. Es besteht keine Notwendigkeit, sich auf Xerces oder XML-APIs zu verlassen, es ist so, als würde man sagen, dass Sie von Java oder Swing abhängig sind. Dies ist implizit.

Wenn ich der Boss eines Maven-Repos wäre, würde ich ein Skript schreiben, um Xerces-Abhängigkeiten rekursiv zu entfernen, und ein read me schreiben, dass dieses Repo Java 1.4 erfordert.

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


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) einchecken möchten, dass Ihr Maven-Baum keine Probleme hat:

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

Sie sollten zuerst debuggen, um Ihre Ebene der XML-Hölle zu identifizieren. Meiner Meinung nach ist der erste Schritt hinzuzufü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 Befehlszeile. Wenn das funktioniert, dann starten Sie den Ausschluss von Bibliotheken. Wenn nicht, dann füge hinzu

-Djaxp.debug=1

zur Befehlszeile.


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

Mit einem flachen Classloading (Standalone-App) oder halbhierarchischem (JBoss AS / EAP 5.x) war das ein Problem.

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

Natürlich ist es immer noch am besten, nur mit einer einzigen Implementierung und Version zu arbeiten, aber wenn es keine andere Möglichkeit gibt (mit zusätzlichen Funktionen aus mehr Bibliotheken), dann kann das Modularisieren Sie möglicherweise retten.

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.

Beispielmoduldefinition:

<?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. Während bestimmte Features fehlen, ist es für die meisten Projekte ausreichend, die (meistens) unter der Kontrolle eines einzigen Anbieters stehen und einen erstaunlichen schnellen Start ermöglichen (aufgrund der Auflösung von paralelisierten Abhängigkeiten).

Beachten Sie, dass für Java 8 eine Modularisierung angestrebt wird, aber AFAIK, die hauptsächlich die JRE selbst modularisiert, nicht sicher, ob sie auf Apps anwendbar ist.







xerces