765 votes

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

Dans mon bureau, la simple mention du mot Xerces suffit à susciter la rage meurtrière des développeurs. Un coup d'œil rapide aux autres questions sur Xerces sur SO semble indiquer que presque tous les utilisateurs de Maven sont "touchés" par ce problème à un moment ou à un autre. Malheureusement, pour comprendre le problème, il faut connaître un peu 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 frameworks écrits en Java utilisent Xerces d'une manière ou d'une autre (de manière transitive, sinon directement).

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

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

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

  • Les versions attribuées aux jars xml-apis et xercesImpl par ceux qui déploient leurs jars dans les dépôts Maven sont souvent différentes. Par exemple, on peut attribuer à xml-apis la version 1.3.03 et à xercesImpl la version 2.8.0, même si les deux sont issus de Xerces 2.8.0. Ceci est dû au fait que les gens étiquettent souvent le jar xml-apis avec la version des spécifications qu'il implémente. Il existe une très belle, mais incomplète, décomposition de ceci 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), incluse dans le JRE. Les classes d'implémentation sont repackagées sous le nom de com.sun.* ce qui rend dangereux le fait d'y accéder directement, car ils peuvent ne pas être disponibles dans certains JRE. Cependant, toute la fonctionnalité de Xerces n'est pas exposée via l'espace de nom java.* y javax.* Par exemple, il n'y a pas d'API qui expose la sérialisation Xerces.

  • Pour ajouter à la confusion, presque tous les conteneurs de servlets (JBoss, Jetty, Glassfish, Tomcat, etc.) sont livrés avec Xerces dans un ou plusieurs de leurs composants. /lib les dossiers.

Problèmes

Résolution des conflits

Pour certaines - ou peut-être pour toutes - les raisons ci-dessus, de nombreux organisations publient et consomment des constructions personnalisées de Xerces dans leurs POMs. 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 est le proxy de plusieurs référentiels (JBoss, Hibernate, etc.) :

xml-apis proxied by Artifactory

Par exemple, l'organisation A pourrait publier xml-apis comme :

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

Pendant ce temps, l'organisation B pourrait publier le même jar comme :

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

Bien que B jar est une version inférieure à celle de A jar Maven ne sait pas qu'il s'agit du même artefact parce qu'ils sont différents. groupId s. Ainsi, il ne peut pas effectuer de résolution de conflit et les deux jar seront inclus comme dépendances résolues :

resolved dependencies with multiple xml-apis

L'enfer des chargeurs de classe

Comme mentionné ci-dessus, le JRE est livré avec Xerces dans le JAXP RI. Bien qu'il serait agréable de marquer toutes les dépendances Xerces Maven comme <exclusion> ou comme <provided> En effet, le code tiers dont vous dépendez peut ou non fonctionner avec la version fournie dans JAXP du JDK que vous utilisez. En outre, vous devez faire face aux bocaux Xerces livrés avec votre conteneur de servlets. Vous avez donc un certain nombre de choix : Supprimez-vous la version du servlet et espérez que votre conteneur fonctionne avec la version JAXP ? Est-il préférable de laisser la version servlet et d'espérer que vos cadres d'application fonctionnent sur la version servlet ? Si un ou deux des conflits non résolus décrits ci-dessus parviennent à se glisser dans votre produit (ce qui est facile à faire dans une grande organisation), vous vous retrouvez rapidement dans l'enfer du classloader, à vous demander quelle version de Xerces le classloader choisit au moment de l'exécution et s'il choisira ou non le même jar sous Windows et sous Linux (probablement pas).

Des solutions ?

Nous avons essayé de marquer toutes les dépendances de Xerces Maven en tant que <provided> ou en tant que <exclusion> mais cela est difficile à appliquer (surtout avec une grande équipe) étant donné que les artefacts ont tellement d'alias ( xml-apis , xerces , xercesImpl , xmlParserAPIs etc.). En outre, nos librairies/frames de tiers peuvent ne pas fonctionner avec la version JAXP ou la version 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 fin sur nos dépendances, puis compter sur un chargement de classe à 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 de la construction de Xerces scripts à XERCESJ-1454 qui permet le téléchargement vers Maven Central. Votez/observez/contribuez à cette question et résolvons ce problème une fois pour toutes.

9 votes

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 à ce que d'autres l'utilisent, mais l'état actuel de xerces et de maven est honteux. Malgré tout, 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 à faire.

39 votes

C'est peut-être hors sujet, mais c'est probablement le meilleur message que j'ai jamais vu. Plus en rapport avec la question, ce que vous décrivez est l'un des problèmes les plus douloureux que l'on puisse rencontrer. Excellente initiative !

2 votes

@TravisSchneeberger Une grande partie de la complexité est due au fait que Sun a choisi d'utiliser Xerces dans le JRE lui-même. On peut difficilement blâmer les gens de Xerces pour cela.

118voto

Grzegorz Grzybek Points 2713

Il y a des JARs 2.11.0 (et les JARs sources !) 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 ...

Je l'ai utilisé :

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

et toutes les dépendances ont été résolues sans problème - même le bon 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 version officielle Xerces-J-bin.2.11.0.zip distribution .

Je n'ai cependant pas pu trouver xml-schema-1.1-beta il ne peut s'agir d'une version Maven classifier -ed en raison de dépendances supplémentaires.

9 votes

Bien que ce soit très déroutant que xml-apis:xml-apis:1.4.01 es plus récent que xml-apis:xml-apis:2.0.2 ? ? voir search.maven.org/

0 votes

C'est confus, mais c'est dû aux téléchargements par des tiers de jars Xerces non-versés, comme justingarrik le disait dans son post. xml-apis 2.9.1 est le même que 1.3.04, donc dans ce sens, 1.4.01 est plus récent (et numériquement plus grand) que 1.3.04.

1 votes

Si vous avez à la fois xercesImpl et xml-apis dans votre pom.xml, assurez-vous de supprimer la dépendance de xml-apis ! Sinon, la version 2.0.2 se manifestera.

65voto

jtahlborn Points 32515

Franchement, presque tout ce que nous avons rencontré fonctionne très bien avec la version JAXP, donc nous toujours exclure xml-apis y xercesImpl .

14 votes

Pourriez-vous ajouter un extrait de pom.xml pour cela ?

13 votes

Quand j'essaie ceci, j'obtiens JavaMelody et Spring qui jettent java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversal au moment de l'exécution.

0 votes

Pour ajouter à la réponse de David Moles -- j'ai vu une demi-douzaine de dépendances transitives nécessitant ElementTraversal. Diverses choses dans Spring et Hadoop le plus souvent.

44voto

Vous pouvez utiliser le plugin maven enforcer avec la règle de dépendance interdite. Cela vous permettrait d'interdire tous les alias que vous ne voulez pas et de n'autoriser que celui que vous voulez. En cas de violation de ces règles, la construction maven de votre projet échouera. En outre, si cette règle s'applique à tous les projets d'une entreprise, vous pourriez placer la configuration du plugin dans un pom parent de l'entreprise.

voir :

16voto

Jens Schauder Points 23468

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 ?

Si ce n'est pas le cas, vous êtes dans la merde et devez utiliser quelque chose comme OSGI, qui vous permet de charger différentes versions d'une bibliothèque en même temps. Attention, cela remplace essentiellement les problèmes de version de jar par des problèmes de classloader ...

S'il existe une telle version, vous pouvez faire en sorte que votre référentiel renvoie cette version pour toutes sortes de dépendances. C'est un hack moche et vous vous retrouverez avec la même implémentation de xerces dans votre classpath plusieurs fois mais c'est mieux que d'avoir plusieurs versions différentes de xerces.

Vous pourriez exclure toutes les dépendances à xerces et en ajouter une à la version que vous voulez utiliser.

Je me demande si vous pouvez écrire une sorte de stratégie de résolution de version comme un plugin pour maven. Ce serait probablement la plus belle solution, mais si elle est réalisable, elle nécessite un peu de recherche et de codage.

Pour la version contenue dans votre environnement d'exécution, vous devrez vous assurer qu'elle est supprimée du classpath de l'application ou que les jars de l'application sont pris en compte en premier pour le classloading avant le dossier lib du serveur.

Donc pour résumer : C'est le bordel et ça ne changera pas.

1 votes

La même classe du même jar chargée par différents ClassLoaders est toujours une ClassCastException (dans tous les conteneurs standard).

3 votes

Exactement. C'est pourquoi j'ai écrit : Sachez que cela remplace essentiellement les problèmes de version de jar par des problèmes de classloader.

2voto

Ondra Žižka Points 8262

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 des cadres modulaires comme OSGi y Modules JBoss ce n'est plus une si grande douleur. Les bibliothèques peuvent utiliser la bibliothèque qu'elles veulent, 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 les fonctionnalités supplémentaires de plusieurs librairies), la modularisation peut vous sauver.

Un bon exemple de JBoss Modules en action est, naturellement, JBoss AS 7 / PAE 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, JBoss Modules est plus simple et plus rapide. Bien qu'il manque certaines fonctionnalités, il est suffisant pour la plupart des projets qui sont (pour la plupart) sous le contrôle d'un seul fournisseur, et permet un démarrage rapide étonnant (grâce à la résolution des dépendances paralinéaires).

Notez qu'il y a un effort de modularisation en cours pour Java 8 mais, à ma connaissance, il s'agit principalement de modulariser le JRE lui-même, et je ne suis pas sûr qu'il soit applicable aux applications.

0 votes

Jboss modules concerne la modularisation statique. Il n'a pas grand-chose à voir avec la modularisation de l'exécution qu'OSGi a à offrir - je dirais qu'ils se complètent l'un l'autre. C'est un bon système.

0 votes

*complément au lieu de compliment

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X