¿Tratando con el "infierno de Xerces" en Java / Maven?

732

En mi oficina, la mera mención de la palabra Xerces es suficiente para incitar la ira asesina de los desarrolladores. Una mirada superficial a las otras preguntas de Xerces sobre SO parece indicar que casi todos los usuarios de Maven están "tocados" por este problema en algún momento. Desafortunadamente, comprender el problema requiere un poco de conocimiento sobre la historia de Xerces ...

Historia

  • Xerces es el analizador XML más utilizado en el ecosistema Java. Casi todas las bibliotecas o frameworks escritos en Java usan Xerces en cierta capacidad (transitivamente, si no directamente).

  • Los frascos Xerces incluidos en los binarios oficiales no están, hasta el día de hoy, versionados. Por ejemplo, el jar de implementación Xerces 2.11.0 se nombra xercesImpl.jary no xercesImpl-2.11.0.jar.

  • El equipo de Xerces no usa Maven , lo que significa que no sube un lanzamiento oficial a Maven Central .

  • Xerces solía lanzarse como un solo jar ( xerces.jar), pero se dividió en dos tarros, uno que contenía la API ( xml-apis.jar) y otro que contenía las implementaciones de esas API ( xercesImpl.jar). Muchos POM Maven más antiguos todavía declaran una dependencia xerces.jar. En algún momento en el pasado, Xerces también se lanzó como xmlParserAPIs.jar, de lo que también dependen algunos POM más antiguos.

  • Las versiones asignadas a los frascos xml-apis y xercesImpl por aquellos que implementan sus frascos en los repositorios de Maven a menudo son diferentes. Por ejemplo, xml-apis podría tener la versión 1.3.03 y xercesImpl podría tener la versión 2.8.0, aunque ambas son de Xerces 2.8.0. Esto se debe a que la gente suele etiquetar el xml-apis jar con la versión de las especificaciones que implementa. Hay un desglose muy agradable, pero incompleto de esto aquí .

  • Para complicar las cosas, Xerces es el analizador XML utilizado en la implementación de referencia de la API Java para el procesamiento XML (JAXP), incluido en el JRE. Las clases de implementación se vuelven a empaquetar bajo el com.sun.*espacio de nombres, lo que hace que sea peligroso acceder a ellas directamente, ya que pueden no estar disponibles en algunos JRE. Sin embargo, no toda la funcionalidad de Xerces se expone a través de las API java.*y javax.*; por ejemplo, no hay API que exponga la serialización de Xerces.

  • Además del confuso desorden, casi todos los contenedores de servlets (JBoss, Jetty, Glassfish, Tomcat, etc.) se envían con Xerces en una o más de sus /libcarpetas.

Problemas

La resolución de conflictos

Por algunas, o quizás por todas, las razones anteriores, muchas organizaciones publican y consumen compilaciones personalizadas de Xerces en sus POM. Esto no es realmente un problema si tiene una aplicación pequeña y solo está utilizando Maven Central, pero rápidamente se convierte en un problema para el software empresarial donde Artifactory o Nexus está representando múltiples repositorios (JBoss, Hibernate, etc.):

xml-apis representada por Artifactory

Por ejemplo, la organización A podría publicar xml-apiscomo:

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

Mientras tanto, la organización B podría publicar lo mismo jarque:

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

Aunque B's jares una versión más baja que A's jar, Maven no sabe que son el mismo artefacto porque tienen diferentes groupIds. Por lo tanto, no puede realizar la resolución de conflictos y ambos jars se incluirán como dependencias resueltas:

dependencias resueltas con múltiples xml-apis

Infierno del cargador de clases

Como se mencionó anteriormente, el JRE se envía con Xerces en el JAXP RI. Mientras que sería bueno para marcar todas las dependencias de Maven como Xerces <exclusion>s o como<provided>, el código de terceros del que depende puede o no funcionar con la versión proporcionada en JAXP del JDK que está utilizando. Además, tiene los frascos Xerces enviados en su contenedor de servlets con los que lidiar. Esto le deja con una serie de opciones: ¿Elimina la versión del servlet y espera que su contenedor se ejecute en la versión JAXP? ¿Es mejor dejar la versión de servlet y esperar que los marcos de sus aplicaciones se ejecuten en la versión de servlet? Si uno o dos de los conflictos no resueltos descritos anteriormente logran deslizarse en su producto (fácil de suceder en una organización grande), rápidamente se encuentra en el infierno del cargador de clases, preguntándose qué versión de Xerces está eligiendo el cargador de clases en el tiempo de ejecución y si elegirá el mismo jar en Windows y Linux (probablemente no).

Soluciones?

Hemos intentado marcar todas las dependencias de Maven como Xerces <provided>o como una <exclusion>, pero esto es difícil de hacer cumplir (especialmente con un equipo grande) dado que los artefactos tienen tantos alias ( xml-apis, xerces, xercesImpl, xmlParserAPIs, etc.). Además, nuestros libs / frameworks de terceros pueden no ejecutarse en la versión JAXP o la versión proporcionada por un contenedor de servlet.

¿Cómo podemos abordar mejor este problema con Maven? ¿Tenemos que ejercer un control tan preciso sobre nuestras dependencias y luego confiar en la carga de clases por niveles? ¿Hay alguna forma de excluir globalmente todas las dependencias de Xerces y obligar a todos nuestros frameworks / libs a usar la versión JAXP?


ACTUALIZACIÓN : Joshua Spiewak ha subido una versión parcheada de los scripts de compilación de Xerces a XERCESJ-1454 que permite la carga en Maven Central. Vota / mira / contribuye a este problema y solucionemos este problema de una vez por todas.

Justin Garrick
fuente
8
Gracias por esta pregunta detallada. No entiendo la motivación del equipo de xerces. Me imagino que están orgullosos de su producto y se complacen en usarlo, pero el estado actual de xerces y maven es vergonzoso. Aun así, pueden hacer lo que quieran aunque no tenga sentido para mí. Me pregunto si los chicos de sonatype tienen alguna sugerencia.
Travis Schneeberger
35
Esto puede estar fuera de tema, pero esta es probablemente la mejor publicación que he visto. Más relacionado con la pregunta, lo que usted describe es uno de los problemas más dolorosos que podemos encontrar. ¡Gran iniciativa!
Jean-Rémy Revy
2
@TravisSchneeberger Gran parte de la complejidad se debe a que Sun eligió usar Xerces en el JRE. Difícilmente se puede culpar a la gente de Xerces por eso.
Thorbjørn Ravn Andersen
Por lo general, tratamos de encontrar una versión de Xerces que satisfaga todas las bibliotecas dependientes por prueba y error, si no es posible, refactorice a WAR para dividir la aplicación en WAR separados (cargadores de clases separados). Esta herramienta (lo escribí) ayuda a entender lo que está pasando jhades.org al permitir consultar la ruta de clase para los botes, y las clases - que funciona también en el caso de que el servidor no se inicia todavía
Universidad angular
Solo un comentario rápido si obtiene este error al iniciar servicemix desde git bash en windows: en su lugar, inícielo desde cmd "normal".
Albert Hendriks

Respuestas:

112

¡Hay 2.11.0 JAR (y JAR fuente) de Xerces en Maven Central desde el 20 de febrero de 2013! Ver Xerces en Maven Central . Me pregunto por qué no han resuelto https://issues.apache.org/jira/browse/XERCESJ-1454 ...

He usado:

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

y todas las dependencias se resolvieron bien, ¡incluso las correctas xml-apis-1.4.01!

Y lo que es más importante (y lo que no era obvio en el pasado): el JAR en Maven Central es el mismo JAR que en la Xerces-J-bin.2.11.0.zipdistribución oficial .

Sin embargo, no pude encontrar la xml-schema-1.1-betaversión, no puede ser una classifierversión de Maven debido a dependencias adicionales.

Grzegorz Grzybek
fuente
99
Aunque es muy confuso que xml-apis:xml-apis:1.4.01sea más nuevo que xml-apis:xml-apis:2.0.2?? ver search.maven.org/…
Hendy Irawan
Es confuso, pero se debe a las subidas de terceros de frascos Xerces no versionados, como decía justingarrik en su publicación. xml-apis 2.9.1 es lo mismo que 1.3.04, por lo que, en ese sentido, 1.4.01 es más nuevo (y numéricamente más grande) que 1.3.04.
liltitus27
1
Si tiene xercesImpl y xml-apis en su pom.xml, ¡asegúrese de eliminar la dependencia xml-apis! De lo contrario, 2.0.2 levanta su cabeza fea.
MikeJRamsey56
64

Francamente, casi todo lo que hemos encontrado funciona bien con la versión JAXP, por lo que siempre excluimos xml-apis y xercesImpl.

jtahlborn
fuente
13
¿Podría agregar un fragmento de pom.xml para eso?
chzbrgla
10
Cuando intento esto, obtengo JavaMelody y Spring lanzando java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversalen tiempo de ejecución.
David Moles
Para agregar a la respuesta de David Moles: he visto que media docena de dependencias transitivas necesitan ElementTraversal. Varias cosas en Spring y Hadoop son más comunes.
Scott Carey
2
Si obtiene java.lang.NoClassDefFoundError: org / w3c / dom / ElementTraversal intente agregar xml-apis 1.4.01 a su pom (y excluya todas las demás versiones dependientes)
Justin Rowe
1
ElementTraversal es una nueva clase agregada en Xerces 11 y disponible en xml-apis: xml-apis: 1.4.01 dependencia. Por lo tanto, es posible que deba copiar la clase manualmente en su proyecto o usar una dependencia completa que cause clases duplicadas en el cargador de clases. Pero en JDK9 esta clase se incluyó, por lo que en la función puede que necesite eliminar el dep.
Sergey Ponomarev
42

Puede usar el complemento maven enforcer con la regla de dependencia prohibida. Esto le permitiría prohibir todos los alias que no desea y permitir solo el que sí desea. Estas reglas fallarán la construcción maven de su proyecto cuando se violen. Además, si esta regla se aplica a todos los proyectos en una empresa, puede poner la configuración del complemento en un pom padre corporativo.

ver:

Travis Schneeberger
fuente
33

Sé que esto no responde la pregunta exactamente, pero para las personas que vienen de Google que utilizan Gradle para su gestión de dependencia:

Logré deshacerme de todos los problemas de xerces / Java8 con Gradle de esta manera:

configurations {
    all*.exclude group: 'xml-apis'
    all*.exclude group: 'xerces'
}
netmikey
fuente
36
bueno, con Maven necesitas alrededor de 4000 líneas de XML para hacer eso.
teknopaul
eso no resolvió el problema. ¿Alguna otra pista para la gente de Android-Gradle?
nyxee
2
@teknopaul XML se usa exclusivamente para la configuración. Groovy es un lenguaje de programación de alto nivel. A veces, es posible que desee utilizar XML por su carácter explícito en lugar de maravilloso por su magia.
Dragas
16

Supongo que hay una pregunta que debes responder:

¿Existe un xerces * .jar con el que todo en su aplicación pueda vivir?

Si no, básicamente estás jodido y tendrías que usar algo como OSGI, que te permite tener diferentes versiones de una biblioteca cargadas al mismo tiempo. Tenga en cuenta que básicamente reemplaza los problemas de la versión jar con problemas del cargador de clases ...

Si existe una versión de este tipo, puede hacer que su repositorio devuelva esa versión para todo tipo de dependencias. Es un truco feo y terminaría con la misma implementación de xerces en su classpath varias veces, pero mejor que tener múltiples versiones diferentes de xerces.

Puede excluir todas las dependencias de xerces y agregar una a la versión que desea usar.

Me pregunto si puedes escribir algún tipo de estrategia de resolución de versión como complemento para Maven. Esta sería probablemente la mejor solución, pero si es posible, necesita algo de investigación y codificación.

Para la versión contenida en su entorno de tiempo de ejecución, deberá asegurarse de que se elimine de la ruta de clase de la aplicación o de que los frascos de la aplicación se consideren primero para la carga de clases antes de considerar la carpeta lib del servidor.

Para concluir: es un desastre y eso no cambiará.

Jens Schauder
fuente
1
La misma clase del mismo contenedor cargado por diferentes ClassLoaders sigue siendo una ClassCastException (en todos los contenedores estándar)
Ajax
3
Exactamente. Es por eso que escribí: Tenga en cuenta que básicamente reemplaza los problemas de la versión jar con problemas de carga de clases
Jens Schauder
7

Hay otra opción que no se ha explorado aquí: declarar las dependencias de Xerces en Maven como opcionales :

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

Básicamente, lo que hace es obligar a todos los dependientes a declarar su versión de Xerces o su proyecto no se compilará. Si quieren anular esta dependencia, pueden hacerlo, pero serán dueños del problema potencial.

Esto crea un fuerte incentivo para proyectos posteriores para:

  • Toma una decisión activa. ¿Van con la misma versión de Xerces o usan otra cosa?
  • Realmente pruebe su análisis (por ejemplo, a través de pruebas unitarias) y carga de clases, así como para no saturar su classpath.

No todos los desarrolladores realizan un seguimiento de las dependencias recién introducidas (por ejemplo, con mvn dependency:tree). Este enfoque llamará inmediatamente la atención sobre el asunto.

Funciona bastante bien en nuestra organización. Antes de su introducción, vivíamos en el mismo infierno que describe el OP.

Daniel
fuente
¿Debería usar literalmente punto-punto-punto dentro del elemento de versión, o necesito usar una versión real como 2.6.2?
chrisinmtown
3
@chrisinmtown La versión real.
Daniel
6

Cada proyecto de Maven debería detenerse dependiendo de xerces, probablemente no lo hagan realmente. Las API XML y un Impl han sido parte de Java desde 1.4. No es necesario depender de xerces o API XML, es como decir que depende de Java o Swing. Esto es implícito.

Si fuera el jefe de un repositorio de Maven, escribiría un script para eliminar de forma recursiva las dependencias de xerces y escribiría un mensaje de lectura que diga que este repositorio requiere Java 1.4.

Cualquier cosa que realmente se rompa porque hace referencia a Xerces directamente a través de las importaciones org.apache necesita una corrección de código para llevarlo al nivel Java 1.4 (y lo ha hecho desde 2002) o una solución a nivel JVM a través de bibliotecas respaldadas, no en Maven.

teknopaul
fuente
Al realizar el refactor que detalló, también debe buscar el paquete y los nombres de clase en el texto de sus archivos Java y configuración. Encontrará que los desarrolladores han puesto el FQN de las clases Impl en cadenas constantes que Class.forName y las construcciones similares utilizan.
Derek Bennett
Esto supone que todas las implementaciones de SAX hacen lo mismo, lo cual no es cierto. La biblioteca xercesImpl permite las opciones de configuración de las que carecen las bibliotecas java.xml.parser.
Amalgovinus
6

Primero debe depurar, para ayudar a identificar su nivel de infierno XML. En mi opinión, el primer paso es agregar

-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

a la línea de comando. Si eso funciona, entonces comience a excluir bibliotecas. Si no, entonces agregue

-Djaxp.debug=1

a la línea de comando.

Derek Bennett
fuente
2

Lo que ayudaría, a excepción de excluir, son las dependencias modulares.

Con una carga de clase plana (aplicación independiente) o semi-jerárquica (JBoss AS / EAP 5.x) esto fue un problema.

Pero con marcos modulares como los módulos OSGi y JBoss , esto ya no es tanto dolor. Las bibliotecas pueden usar la biblioteca que deseen, independientemente.

Por supuesto, es más recomendable seguir con una sola implementación y versión, pero si no hay otra forma (usando características adicionales de más bibliotecas), la modularización podría salvarlo.

Un buen ejemplo de los módulos JBoss en acción es, naturalmente, JBoss AS 7 / EAP 6 / WildFly 8 , para lo cual se desarrolló principalmente.

Definición de módulo de ejemplo:

<?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 comparación con OSGi, los módulos JBoss son más simples y rápidos. Si bien faltan ciertas características, es suficiente para la mayoría de los proyectos que (en su mayoría) están bajo el control de un proveedor, y permiten un arranque rápido impresionante (debido a la resolución de dependencias paralelizadas).

Tenga en cuenta que hay un esfuerzo de modularización en curso para Java 8 , pero AFAIK es principalmente para modularizar el JRE en sí mismo, no estoy seguro de si será aplicable a las aplicaciones.

Ondra Žižka
fuente
jboss modules trata de modularización estática. Tiene poco que ver con la modularización en tiempo de ejecución que OSGi tiene para ofrecer, diría que se complementan entre sí. Sin embargo, es un buen sistema.
EIS
* complemento en lugar de cumplido
Robert Mikes
2

Aparentemente xerces:xml-apis:1.4.01ya no está en Maven Central, que sin embargo es lo que hace xerces:xercesImpl:2.11.0referencia.

Esto funciona para mi:

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

Mi amigo eso es muy simple, aquí un ejemplo:

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

Y si desea verificar en la terminal (consola de Windows para este ejemplo) que su árbol maven no tiene problemas:

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