Faire face à "Xerces hell" dans Java / Maven?

732

Dans mon bureau, la simple mention du mot Xerces suffit pour inciter les développeurs à la rage meurtrière. Un coup d'œil rapide aux autres questions de Xerces sur SO semble indiquer que presque tous les utilisateurs de Maven sont "touchés" par ce problème à un moment donné. Malheureusement, comprendre le problème nécessite un peu de connaissance sur l'histoire de Xerces ...

Histoire

  • Xerces est l'analyseur XML le plus utilisé dans l'écosystème Java. Presque toutes les bibliothèques ou frameworks écrits en Java utilisent Xerces dans une certaine mesure (de manière transitoire, sinon directement).

  • Les bocaux Xerces inclus dans les binaires officiels ne sont pas, à ce jour, versionnés. Par exemple, le pot d'implémentation Xerces 2.11.0 est nommé xercesImpl.jaret non xercesImpl-2.11.0.jar.

  • L'équipe de Xerces n'utilise pas Maven , ce qui signifie qu'elle ne télécharge pas de version officielle sur Maven Central .

  • Xerces était auparavant publié sous la forme d'un seul pot ( xerces.jar), mais était divisé en deux pots, l'un contenant l'API ( xml-apis.jar) et l'autre contenant les implémentations de ces API (xercesImpl.jar ). De nombreux POM Maven plus anciens déclarent toujours une dépendance xerces.jar. À un moment donné dans le passé, Xerces a également été publié en tant que xmlParserAPIs.jar, dont certains POM plus anciens dépendent également.

  • Les versions attribuées aux fichiers xml-apis et xercesImpl par ceux qui déploient leurs fichiers dans les référentiels Maven sont souvent différentes. Par exemple, xml-apis peut recevoir la version 1.3.03 et xercesImpl peut recevoir la version 2.8.0, même si les deux proviennent de Xerces 2.8.0. C'est parce que les gens marquent souvent le pot xml-apis avec la version des spécifications qu'il implémente. Il y a une ventilation très agréable, mais incomplète de cette ici .

  • Pour compliquer les choses, Xerces est l'analyseur XML utilisé dans l'implémentation de référence de l'API Java pour le traitement XML (JAXP), inclus dans le JRE. Les classes d'implémentation sont reconditionnées sous l' com.sun.*espace de noms, ce qui rend leur accès direct dangereux, car elles peuvent ne pas être disponibles dans certains JRE. Cependant, toutes les fonctionnalités de Xerces ne sont pas exposées via lejava.*javax.* API et ; par exemple, il n'y a pas d'API qui expose la sérialisation de Xerces.

  • Ajoutant au désordre déroutant, presque tous les conteneurs de servlets (JBoss, Jetty, Glassfish, Tomcat, etc.), sont expédiés avec Xerces dans un ou plusieurs de leurs /lib dossiers.

Problèmes

Résolution de conflit

Pour certaines - ou peut-être toutes - des raisons ci-dessus, de nombreuses organisations publient et utilisent des versions personnalisées de Xerces dans leurs POM. Ce n'est pas vraiment un problème si vous avez une petite application et que vous n'utilisez que Maven Central, mais cela devient rapidement un problème pour les logiciels d'entreprise où Artifactory ou Nexus assure le proxy de plusieurs référentiels (JBoss, Hibernate, etc.):

xml-apis mandaté par Artifactory

Par exemple, l'organisation A peut publier en xml-apistant que:

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

Pendant ce temps, l'organisation B pourrait publier les mêmes jarque:

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

Bien que les B jarsoient une version inférieure aux A jar, Maven ne sait pas qu'ils sont le même artefact car ils ont des groupIds différents . Ainsi, il ne peut pas effectuer la résolution des conflits et les deux jars seront inclus en tant que dépendances résolues:

dépendances résolues avec plusieurs xml-apis

Classloader Hell

Comme mentionné ci-dessus, le JRE est livré avec Xerces dans le JAXP RI. Bien qu'il serait bien de marquer toutes les dépendances de Xerces Maven comme<exclusion> s ou comme<provided>, le code tiers dont vous dépendez peut ou non fonctionner avec la version fournie dans JAXP du JDK que vous utilisez. De plus, vous avez les pots Xerces expédiés dans votre conteneur de servlet pour faire face. Cela vous laisse un certain nombre de choix: supprimez-vous la version du servlet et espérez-vous que votre conteneur fonctionne sur la version JAXP? Est-il préférable de laisser la version du servlet et d'espérer que vos frameworks d'application fonctionnent sur la version du servlet? Si un ou deux des conflits non résolus décrits ci-dessus parviennent à se glisser dans votre produit (facile à produire dans une grande organisation), vous vous retrouvez rapidement dans l'enfer du chargeur de classe, en vous demandant quelle version de Xerces le chargeur de classe choisit au moment de l'exécution et si oui ou non il choisira le même pot sous Windows et Linux (probablement pas).

Solutions?

Nous avons essayé de marquer toutes les dépendances Xerces Maven <provided>ou comme <exclusion>, mais cela est difficile à appliquer ( en particulier avec une grande équipe) étant donné que les objets ont tant d'alias ( xml-apis, xerces, xercesImpl,xmlParserAPIs , etc.). De plus, nos bibliothèques / frameworks tiers peuvent ne pas fonctionner sur la version JAXP ou la version fournie par un conteneur de servlet.

Comment pouvons-nous résoudre au mieux ce problème avec Maven? Doit-on exercer un contrôle aussi fin sur nos dépendances, puis s'appuyer sur un chargement de classe à plusieurs niveaux? Existe-t-il un moyen d'exclure globalement toutes les dépendances de Xerces et de forcer tous nos frameworks / bibliothèques à utiliser la version JAXP?


MISE À JOUR : Joshua Spiewak a téléchargé une version corrigée des scripts de construction de Xerces sur XERCESJ-1454 qui permet le téléchargement sur Maven Central. Votez / regardez / contribuez à ce problème et résolvons ce problème une fois pour toutes.

Justin Garrick
la source
8
Merci pour cette question détaillée. Je ne comprends pas la motivation de l'équipe xerces. J'imagine qu'ils sont fiers de leur produit et prennent plaisir à les utiliser, mais l'état actuel des xerces et des maven est honteux. Même ainsi, ils peuvent faire ce qu'ils veulent même si cela n'a aucun sens pour moi. Je me demande si les gars de sonatype ont des suggestions.
Travis Schneeberger
35
C'est peut-être hors sujet, mais c'est probablement le meilleur article que j'ai jamais vu. Plus lié à la question, ce que vous décrivez est l'un des problèmes les plus douloureux que nous puissions rencontrer. Belle initiative!
Jean-Rémy Revy
2
@TravisSchneeberger Une grande partie de la complexité est due au fait que Sun a choisi d'utiliser Xerces dans le JRE lui-même. Vous pouvez difficilement blâmer les gens de Xerces pour cela.
Thorbjørn Ravn Andersen
Habituellement, nous essayons de trouver une version de Xerces qui satisfait toutes les bibliothèques dépendantes par essais et erreurs, si ce n'est pas possible, alors refactorisez les fichiers WAR pour diviser l'application en fichiers WAR distincts (chargeurs de classe distincts). Cet outil (je l'ai écrit) aide à comprendre ce qui se passe sur jhades.org en permettant d'interroger le chemin de classe pour les fichiers JAR et les classes - il fonctionne également dans le cas où le serveur ne démarre pas encore
Angular University
Juste un petit commentaire si vous obtenez cette erreur lors du démarrage de servicemix à partir de git bash dans Windows: démarrez-le plutôt à partir de cmd "normal".
Albert Hendriks

Réponses:

112

Il existe des JAR 2.11.0 (et des JAR source!) De Xerces dans Maven Central depuis le 20 février 2013! Voir Xerces dans Maven Central . Je me demande pourquoi ils n'ont pas résolu https://issues.apache.org/jira/browse/XERCESJ-1454 ...

J'ai utilisé:

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

et toutes les dépendances se sont bien résolues - même correctement xml-apis-1.4.01!

Et ce qui est le plus important (et ce qui n'était pas évident dans le passé) - le JAR dans Maven Central est le même JAR que dans la Xerces-J-bin.2.11.0.zipdistribution officielle .

Je n'ai cependant pas pu trouver de xml-schema-1.1-betaversion - ce ne peut pas être une classifierversion Maven à cause de dépendances supplémentaires.

Grzegorz Grzybek
la source
9
Bien qu'il soit très confus qui xml-apis:xml-apis:1.4.01est plus récente que xml-apis:xml-apis:2.0.2?? voir search.maven.org/…
Hendy Irawan
C'est déroutant, mais cela est dû aux téléchargements tiers de pots Xerces non versionnés, comme le disait justingarrik dans son post. xml-apis 2.9.1 est identique à 1.3.04, donc en ce sens, 1.4.01 est plus récent (et numériquement plus grand) que 1.3.04.
liltitus27
1
Si vous avez à la fois xercesImpl et xml-apis dans votre pom.xml, assurez-vous de supprimer la dépendance xml-apis! Sinon, le 2.0.2 fait dresser sa tête laide.
MikeJRamsey56
64

Franchement, à peu près tout ce que nous avons rencontré fonctionne très bien avec la version JAXP, donc nous excluons toujours xml-apis et xercesImpl.

jtahlborn
la source
13
Pourriez-vous ajouter un extrait pom.xml pour cela?
chzbrgla
10
Lorsque j'essaie, j'obtiens JavaMelody et Spring java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversalà l'exécution.
David Moles,
Pour ajouter à la réponse de David Moles - j'ai vu une demi-douzaine de dépendances transitives nécessiter ElementTraversal. Diverses choses au printemps et Hadoop le plus souvent.
Scott Carey
2
Si vous obtenez java.lang.NoClassDefFoundError: org / w3c / dom / ElementTraversal, essayez d'ajouter xml-apis 1.4.01 à votre pom (et excluez toutes les autres versions dépendantes)
Justin Rowe
1
ElementTraversal est une nouvelle classe ajoutée dans Xerces 11 et disponible dans les dépendances xml-apis: xml-apis: 1.4.01. Vous devrez donc peut-être copier la classe manuellement dans votre projet ou utiliser toute la dépendance qui provoque des classes dupliquées dans le chargeur de classe. Mais dans JDK9, cette classe était incluse, donc dans la fonctionnalité, vous devrez peut-être supprimer le dep.
Sergey Ponomarev
42

Vous pouvez utiliser le plugin mavenforcer avec la règle de dépendance interdite. Cela vous permettrait d'interdire tous les alias que vous ne voulez pas et d'autoriser uniquement celui que vous voulez. Ces règles échoueront la construction maven de votre projet en cas de violation. De plus, si cette règle s'applique à tous les projets d'une entreprise, vous pouvez placer la configuration du plug-in dans un pom parent d'entreprise.

voir:

Travis Schneeberger
la source
33

Je sais que cela ne répond pas exactement à la question, mais pour les personnes venant de google qui utilisent Gradle pour leur gestion des dépendances:

J'ai réussi à me débarrasser de tous les problèmes xerces / Java8 avec Gradle comme ceci:

configurations {
    all*.exclude group: 'xml-apis'
    all*.exclude group: 'xerces'
}
netmikey
la source
36
bien, avec maven vous avez besoin d'environ 4000 lignes XML pour cela.
teknopaul
cela n'a pas résolu le problème. d'autres conseils pour les personnes Android-Gradle?
nyxee
2
@teknopaul XML est utilisé uniquement pour la configuration. Groovy est un langage de programmation de haut niveau. Parfois, vous voudrez peut-être utiliser XML pour son explication au lieu de groovy pour sa magie.
Dragas
16

Je suppose qu'il y a une question à laquelle vous devez répondre:

Existe-t-il un xerces * .jar avec lequel tout dans votre application peut vivre?

Sinon, vous êtes fondamentalement foutu et devez utiliser quelque chose comme OSGI, qui vous permet d'avoir différentes versions d'une bibliothèque chargées en même temps. Soyez averti qu'il remplace essentiellement les problèmes de version jar par des problèmes de chargeur de classe ...

S'il existe une telle version, vous pouvez demander à votre référentiel de renvoyer cette version pour toutes sortes de dépendances. C'est un hack laid et se retrouverait avec la même implémentation de xerces dans votre chemin de classe plusieurs fois mais mieux que d'avoir plusieurs versions différentes de xerces.

Vous pouvez exclure toutes les dépendances de xerces et en ajouter une à la version que vous souhaitez utiliser.

Je me demande si vous pouvez écrire une sorte de stratégie de résolution de version en tant que plugin pour maven. Ce serait probablement la meilleure solution, mais si cela est possible, cela nécessite des recherches et un codage.

Pour la version contenue dans votre environnement d'exécution, vous devez vous assurer qu'elle soit supprimée du chemin de classe de l'application ou que les fichiers JAR d'application sont considérés en premier pour le chargement de classe avant que le dossier lib du serveur ne soit pris en compte.

Donc, pour résumer: c'est un gâchis et cela ne changera pas.

Jens Schauder
la source
1
La même classe à partir du même pot chargé par différents ClassLoaders est toujours une exception ClassCastException (dans tous les conteneurs standard)
Ajax
3
Exactement. C'est pourquoi j'ai écrit: Soyez averti qu'il remplace essentiellement les problèmes de version de jar par des problèmes de chargeur de classe
Jens Schauder
7

Il existe une autre option qui n'a pas été explorée ici: déclarer les dépendances de Xerces dans Maven comme facultatives :

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

Fondamentalement, cela oblige toutes les personnes à charge à déclarer que leur version de Xerces ou leur projet ne sera pas compilé. S'ils veulent outrepasser cette dépendance, ils sont invités à le faire, mais ils seront alors propriétaires du problème potentiel.

Cela incite fortement les projets en aval à:

  • Prenez une décision active. Vont-ils avec la même version de Xerces ou utilisent-ils autre chose?
  • Testez réellement leur analyse (par exemple par des tests unitaires) et le chargement de classe ainsi que de ne pas encombrer leur chemin de classe.

Tous les développeurs ne suivent pas les dépendances nouvellement introduites (par exemple avec mvn dependency:tree). Cette approche portera immédiatement la question à leur attention.

Cela fonctionne assez bien dans notre organisation. Avant son introduction, nous vivions dans le même enfer que l'OP décrit.

Daniel
la source
Dois-je utiliser littéralement point-point-point dans l'élément version, ou dois-je utiliser une version réelle comme 2.6.2?
chrisinmtown
3
@chrisinmtown La vraie version.
Daniel
6

Chaque projet maven devrait s'arrêter en fonction des xerces, ce n'est probablement pas vraiment le cas. Les API XML et un Impl font partie de Java depuis 1.4. Il n'est pas nécessaire de dépendre de xerces ou d'API XML, c'est comme dire que vous dépendez de Java ou de Swing. C'est implicite.

Si j'étais le patron d'un dépôt maven, j'écrirais un script pour supprimer récursivement les dépendances xerces et j'écrirais un read me qui dit que ce dépôt nécessite Java 1.4.

Tout ce qui casse réellement car il fait référence à Xerces directement via les importations org.apache a besoin d'un correctif de code pour l'amener au niveau Java 1.4 (et c'est le cas depuis 2002) ou d'une solution au niveau JVM via des bibliothèques approuvées, pas dans maven.

teknopaul
la source
Lors de la refactorisation que vous avez détaillée, vous devez également rechercher les noms de package et de classe dans le texte de vos fichiers Java et de votre configuration. Vous constaterez que les développeurs ont placé le FQN des classes Impl dans des chaînes constantes qui sont utilisées par Class.forName et les constructions similaires.
Derek Bennett
Cela suppose que toutes les implémentations SAX font la même chose, ce qui n'est pas vrai. la bibliothèque xercesImpl permet des options de configuration qui manquent aux bibliothèques java.xml.parser.
Amalgovinus
6

Vous devez d'abord déboguer pour identifier votre niveau d'enfer XML. À mon avis, la première étape consiste à ajouter

-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

à la ligne de commande. Si cela fonctionne, commencez à exclure les bibliothèques. Sinon, ajoutez

-Djaxp.debug=1

à la ligne de commande.

Derek Bennett
la source
2

Ce qui aiderait, à l'exception de l'exclusion, ce sont les dépendances modulaires.

Avec un chargement de classe plat (application autonome) ou semi-hiérarchique (JBoss AS / EAP 5.x), c'était un problème.

Mais avec des frameworks modulaires comme les modules OSGi et JBoss , ce n'est plus tant la peine. Les bibliothèques peuvent utiliser la bibliothèque de leur choix, indépendamment.

Bien sûr, il est toujours recommandé de s'en tenir à une seule implémentation et une seule version, mais s'il n'y a pas d'autre moyen (en utilisant des fonctionnalités supplémentaires de plus de bibliothèques), la modularisation pourrait vous sauver.

Un bon exemple de modules JBoss en action est, naturellement, JBoss AS 7 / EAP 6 / WildFly 8 , pour lequel il a été principalement développé.

Exemple de définition de module:

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

Par rapport à OSGi, les modules JBoss sont plus simples et plus rapides. Bien qu'il manque certaines fonctionnalités, cela suffit pour la plupart des projets qui sont (principalement) sous le contrôle d'un fournisseur, et permettent un démarrage rapide époustouflant (en raison de la résolution des dépendances parallèles).

Notez qu'il y a un effort de modularisation en cours pour Java 8 , mais AFAIK qui vise principalement à modulariser le JRE lui-même, je ne sais pas s'il sera applicable aux applications.

Ondra Žižka
la source
jboss modules concerne la modularisation statique. Cela n'a pas grand-chose à voir avec la modularisation du runtime qu'OSGi a à offrir - je dirais qu'ils se complètent mutuellement. C'est un bon système cependant.
eis
* complément au lieu de compliment
Robert Mikes
2

Apparemment xerces:xml-apis:1.4.01n'est plus dans maven central, ce qui est pourtant ce qui fait xerces:xercesImpl:2.11.0référence.

Cela fonctionne pour moi:

<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>
thrau
la source
1

Mon ami c'est très simple, voici un exemple:

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

Et si vous voulez vérifier dans le terminal (console Windows pour cet exemple) que votre arbre Maven n'a aucun problème:

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