web-dev-qa-db-fra.com

Faire face à "l'enfer Xerces" en Java / Maven?

Dans mon bureau, la simple mention du mot Xerces suffit à inciter les développeurs à une rage meurtrière. Un rapide coup d’œil 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 largement utilisé dans l’écosystème Java. Presque toutes les bibliothèques ou tous les frameworks écrits en Java utilisent Xerces dans une certaine mesure (de manière transitoire, sinon directement).

  • Les pots Xerces inclus dans le fichiers binaires officiels ne sont, à ce jour, pas versionnés. Par exemple, le fichier d'implémentation de Xerces 2.11.0 s'appelle xercesImpl.jar et non xercesImpl-2.11.0.jar.

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

  • Xerces était publié sous forme de fichier jar (xerces.jar), mais était divisé en deux fichiers jar, 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 sur xerces.jar. Par le passé, Xerces a également été publié sous le nom de xmlParserAPIs.jar, dont dépendent également certains POM plus anciens.

  • Les versions attribuées aux fichiers jar xml-apis et xercesImpl par ceux qui déploient leurs fichiers dans des référentiels Maven sont souvent différentes. Par exemple, xml-apis peut recevoir la version 1.3.03 et xercesImpl, la version 2.8.0, même si les deux proviennent de Xerces 2.8.0. En effet, les utilisateurs associent souvent le fichier java xml-apis à la version des spécifications implémentée. Il y a une très belle, mais incomplète ventilation de ce ici .

  • Pour compliquer les choses, Xerces est l’analyseur XML utilisé dans l’implémentation de référence de l’API pour le traitement XML Java (JAXP), incluse dans le JRE. Les classes d'implémentation sont reconditionnées sous l'espace de noms com.Sun.*, 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 les API Java.* et javax.*; Par exemple, aucune API n'expose la sérialisation Xerces.

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

Problèmes

Résolution de conflit

Pour certaines - ou peut-être toutes - 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 utilisez une petite application et que vous utilisez uniquement Maven Central, mais cela devient rapidement un problème pour les logiciels d'entreprise où Artifactory ou Nexus utilise le proxy de plusieurs référentiels (JBoss, Hibernate, etc.):

xml-apis proxied by Artifactory

Par exemple, l'organisation A peut publier xml-apis sous la forme:

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

Pendant ce temps, l'organisation B peut publier le même jar en tant que:

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

Bien que jar de B soit une version inférieure à jar de A, Maven ne sait pas qu'il s'agit du même artefact car ils ont des groupIds différents. Ainsi, il ne peut pas résoudre les conflits et les deux jars seront inclus en tant que dépendances résolues:

resolved dependencies with multiple xml-apis

Enfer de classe

Comme mentionné ci-dessus, la JRE est livrée avec Xerces dans le JAXP RI. Bien qu'il serait agréable de marquer toutes les dépendances Xerces Maven comme <exclusion>s ou comme <provided>, le code tiers sur lequel vous dépendez peut ou non fonctionner avec la version fournie dans JAXP du JDK en utilisant. De plus, vous avez les jarres Xerces livrés dans votre conteneur de servlets à traiter. 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 infrastructures d’application s’exécutent sur la version du servlet? Si un ou deux des conflits non résolus décrits ci-dessus parviennent à se glisser dans votre produit (cela se produit facilement dans une grande entreprise), vous vous retrouvez rapidement dans l'enfer du chargeur de classe, en vous demandant quelle version de Xerces le programme choisi au moment de l'exécution et si oui ou non choisira le même fichier jar sous Windows et Linux (probablement pas).

Solutions?

Nous avons essayé de marquer toutes les dépendances Xerces Maven comme <provided> ou comme <exclusion>, mais il est difficile de les appliquer (en particulier avec une grande équipe), car les artefacts ont beaucoup d'alias (xml-apis, xerces__, xercesImpl, xmlParserAPIs, etc.). En outre, nos bibliothèques/infrastructures tierces ne peuvent pas s'exécuter sur la version JAXP ou celle fournie par un conteneur de servlets.

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


UPDATE: Joshua Spiewak a téléchargé une version corrigée des scripts de génération Xerces sur XERCESJ-1454 permettant le téléchargement sur Maven Central. Votez/regardez/contribuez à ce problème et réglons-le une fois pour toutes.

690
Justin Garrick

Il y a 2.11.0 fichiers JAR (et fichiers 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 ont résolu bien - même approprié 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 distribution officielle Xerces-J-bin.2.11.0.Zip.

Je ne pouvais cependant pas trouver la version xml-schema-1.1-beta - ce ne peut pas être une version Maven classifier- à cause de dépendances supplémentaires.

103
Grzegorz Grzybek

Franchement, pratiquement tout ce que nous avons rencontré fonctionne très bien avec la version JAXP, donc nous toujours excluonsxml-apis et xercesImpl.

62
jtahlborn

Vous pouvez utiliser le plug-in 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 seulement celui que vous voulez. Ces règles échoueront dans la construction maven de votre projet si elles sont violées. 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 de l'entreprise.

voir:

42
Travis Schneeberger

Je sais que cela ne répond pas exactement à la question, mais pour les personnes qui viennent de Google, elles utilisent Gradle pour gérer leurs dépendances:

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

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

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 de jar par des problèmes de chargeur de classe ...

S'il existe une telle version, vous pouvez faire en sorte que votre référentiel la renvoie pour tous les types de dépendances. C'est un vilain bidouillage qui aboutirait plusieurs fois à la même implémentation de xerces dans votre chemin de classe, mais mieux que d'avoir plusieurs versions différentes de xerces.

Vous pouvez exclure chaque dépendance à 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 solution la plus intéressante, mais nécessiterait, dans la mesure du possible, des recherches et un codage.

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

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

16
Jens Schauder

Vous devez commencer par déboguer pour vous aider à 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. Si non, alors ajoutez

-Djaxp.debug=1

à la ligne de commande.

6
Derek Bennett

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

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

En gros, cela oblige toutes les personnes à charge à déclarer leur version de Xerces ou leur projet ne sera pas compilé. S'ils veulent remplacer cette dépendance, ils sont les bienvenus, mais ils auront alors le problème potentiel.

Cela crée une forte incitation pour les projets en aval à:

  • Prendre une décision active. Est-ce qu'ils vont avec la même version de Xerces ou utilisent-ils autre chose?
  • En réalité, testez leur analyse (par exemple via des tests unitaires) et le chargement de classes, ainsi que pour ne pas encombrer leur chemin de classes.

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 décrit le PO.

6
Daniel

Chaque projet Maven devrait cesser de dépendre de xerces, probablement pas vraiment. Les API XML et un Impl font partie de Java depuis la 1.4. Il n’est pas nécessaire de dépendre des API xerces ou XML, c’est comme dire que vous dépendez de Java ou de Swing. Ceci est implicite.

Si j'étais le responsable d'un dépôt principal, j'écrirais un script pour supprimer de manière récursive les dépendances de xerces et écrire une lecture me disant que ce dépôt nécessite Java 1.4.

Tout ce qui se brise réellement car il fait directement référence à Xerces via les importations org.Apache nécessite un correctif de code pour le porter au niveau Java 1.4 (et le fait depuis 2002) ou une solution au niveau de la JVM via des bibliothèques approuvées, mais pas dans Maven.

5
teknopaul

Apparemment, xerces:xml-apis:1.4.01 n'est plus dans maven central, ce qui est cependant ce que xerces:xercesImpl:2.11.0 fait ré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>
2
thrau

Ce qui aiderait, sauf à exclure, 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 les frameworks modulaires tels que OSGi et JBoss Modules , cela ne pose plus tant de problèmes. Les bibliothèques peuvent utiliser la bibliothèque de leur choix, indépendamment des autres.

Bien sûr, il est toujours recommandé de ne conserver qu'une seule implémentation et version, mais s'il n'y a pas d'autre moyen (utiliser des fonctionnalités supplémentaires de plus de libs), la modularisation peut vous faire économiser.

Un bon exemple de module 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>

En comparaison avec OSGi, JBoss Modules est plus simple et plus rapide. Bien que certaines fonctionnalités ne soient pas disponibles, elles sont suffisantes pour la plupart des projets qui sont (principalement) sous le contrôle d'un fournisseur, et permettent un démarrage rapide stupéfiant (en raison de la résolution de dépendances parallèles).

Notez qu'il y a un effort de modularisation en cours pour Java 8 , mais d'après ce que je sais, c'est principalement de modulariser le JRE lui-même, je ne sais pas s'il sera applicable aux applications.

2
Ondra Žižka

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

Et si vous voulez vérifier dans le terminal (la console Windows pour cet exemple) que votre arbre maven n'a pas de problèmes:

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