Tratar con "Xerces hell" en Java/Maven?
classloader dependency-management (11)
¡Hay 2.11.0 JARs (y de origen JARs!) De xerces en Maven Central desde el 20 de febrero de 2013! Ver Xerces en Maven Central . Me pregunto por qué no se han resuelto 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 un 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 distribución oficial Xerces-J-bin.2.11.0.zip
.
Sin embargo, no pude encontrar xml-schema-1.1-beta
versión xml-schema-1.1-beta
; no puede ser una versión classifier
Maven debido a dependencias adicionales.
En mi oficina, la mera mención de la palabra Xerces es suficiente para incitar a la rabia asesina de los desarrolladores. Una mirada rápida 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, entender el problema requiere un poco de conocimiento sobre la historia de Xerces ...
Historia
Xerces es el analizador XML más utilizado en el ecosistema de Java. Casi todas las bibliotecas o marcos escritos en Java usan Xerces en alguna capacidad (transitivamente, si no directamente).
Los frascos de Xerces incluidos en los binarios oficiales no están versionados hasta la fecha. Por ejemplo, el jar de implementación Xerces 2.11.0 se llama
xercesImpl.jar
y noxercesImpl-2.11.0.jar
.El equipo de Xerces no usa Maven , lo que significa que no suben un lanzamiento oficial a Maven Central .
Xerces solía ser lanzado como un solo jar (
xerces.jar
), pero se dividía en dosxerces.jar
jar , uno que contenía la API (xml-apis.jar
) y otro que contenía las implementaciones de esas API (xercesImpl.jar
). Muchos POM de Maven más antiguos todavía declaran una dependencia enxerces.jar
. En algún momento en el pasado, Xerces también fue lanzado comoxmlParserAPIs.jar
, del cual también dependen algunosxmlParserAPIs.jar
más antiguos.Las versiones asignadas a los archivos xml-apis y xercesImpl por aquellos que implementan sus archivos jar en los repositorios de Maven a menudo son diferentes. Por ejemplo, a xml-apis se le puede dar la versión 1.3.03 y a xercesImpl se le puede dar la versión 2.8.0, aunque ambas sean de Xerces 2.8.0. Esto se debe a que la gente suele etiquetar el tarro xml-apis con la versión de las especificaciones que implementa. Hay un desglose muy bonito, pero incompleto de esto here .
Para complicar las cosas, Xerces es el analizador XML utilizado en la implementación de referencia de la API de Java para el procesamiento de XML (JAXP), incluido en el JRE. Las clases de implementación se
com.sun.*
empaquetar en el espacio de nombrescom.sun.*
, Lo que hace que sea peligroso acceder a ellas directamente, ya que es posible que no estén disponibles en algunos JRE. Sin embargo, no toda la funcionalidad de Xerces está expuesta a través de las APIjava.*
Yjavax.*
; por ejemplo, no hay una API que exponga la serialización de Xerces.Además del confuso lío, casi todos los contenedores de servlets (JBoss, Jetty, Glassfish, Tomcat, etc.) se envían con Xerces en una o más de sus carpetas
/lib
.
Problemas
La resolución de conflictos
Por alguna o muchas de 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á usando Maven Central, pero rápidamente se convierte en un problema para el software empresarial donde Artifactory o Nexus está procesando varios repositorios (JBoss, Hibernate, etc.):
Por ejemplo, la organización A podría publicar xml-apis
como:
<groupId>org.apache.xerces</groupId>
<artifactId>xml-apis</artifactId>
<version>2.9.1</version>
Mientras tanto, la organización B podría publicar el mismo jar
como:
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
<version>1.3.04</version>
Aunque el jar
de B es una versión más baja que el jar
de A, Maven no sabe que son el mismo artefacto porque tienen diferentes groupId
. Por lo tanto, no puede realizar la resolución de conflictos y ambos jar
se incluirán como dependencias resueltas:
Classloader Hell
Como se mencionó anteriormente, el JRE se envía con Xerces en el JAXP RI. Si bien sería bueno marcar todas las dependencias de Xerces Maven como <exclusion>
o como <provided>
, el código de terceros del que depende puede o no funcionar con la versión provista en JAXP del JDK que está usando. Además, tiene los frascos de Xerces enviados en su contenedor de servlets para enfrentarlos. Esto le deja con una serie de opciones: ¿Borra la versión del servlet y espera que su contenedor se ejecute en la versión JAXP? ¿Es mejor dejar la versión del servlet y esperar que los marcos de las aplicaciones se ejecuten en la versión del servlet? Si uno o dos de los conflictos no resueltos descritos anteriormente logran deslizarse en su producto (es fácil de pasar en una gran organización), se encontrará rápidamente en el infierno del cargador de clases, preguntándose qué versión de Xerces está seleccionando el cargador de clases en tiempo de ejecución y si es o no Escogerá el mismo jar en Windows y Linux (probablemente no).
Soluciones?
Hemos intentado marcar todas las dependencias de Xerces Maven como <provided>
o como <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, nuestras bibliotecas / marcos de terceros pueden no ejecutarse en la versión de JAXP o en la versión proporcionada por un contenedor de servlets.
¿Cómo podemos abordar mejor este problema con Maven? ¿Debemos 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 forzar a todos nuestros marcos / 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 a Maven Central. Vote / vea / contribuya a este problema y solucionemos este problema de una vez por todas.
Aparentemente, xerces:xml-apis:1.4.01
ya no está en maven central, que sin embargo es lo que xerces:xercesImpl:2.11.0
referencia.
Esto funciona para mí:
<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>
Debería depurar primero, 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 linea de comando. Si eso funciona, entonces comienza a excluir las bibliotecas. Si no, entonces agregue
-Djaxp.debug=1
a la linea de comando.
Francamente, casi todo lo que hemos encontrado funciona bien con la versión JAXP, por lo que siempre excluimos xml-apis
y xercesImpl
.
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 esto hace es obligar a todos los dependientes a declarar su versión de Xerces o su proyecto no se compilará. Si desean anular esta dependencia, pueden hacerlo, pero luego serán responsables del problema potencial.
Esto crea un fuerte incentivo para los proyectos posteriores a:
- Tomar una decisión activa. ¿Van con la misma versión de Xerces o usan otra cosa?
- En realidad, pruebe su análisis (por ejemplo, a través de pruebas unitarias) y la carga de clases, así como para no saturar su classpath.
No todos los desarrolladores mvn dependency:tree
un seguimiento de las nuevas dependencias 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, solíamos vivir en el mismo infierno que describe el OP.
Lo que ayudaría, excepto excluir, son las dependencias modulares.
Con una carga de clases plana (aplicación independiente) o semi-jerárquica (JBoss AS / EAP 5.x) esto fue un problema.
Pero con marcos modulares como OSGi y JBoss Modules , esto ya no es tanto dolor. Las bibliotecas pueden usar cualquier biblioteca que quieran, independientemente.
Por supuesto, aún es más recomendable seguir con una sola implementación y versión, pero si no hay otra forma (usar funciones adicionales de más librerías), entonces la modularización podría salvarlo.
Un buen ejemplo de JBoss Modules en acción es, naturalmente, JBoss AS 7 / EAP 6 / WildFly 8 , para el cual se desarrolló principalmente.
Ejemplo de definición de módulo:
<?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 más rápidos. Si bien faltan ciertas funciones, 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 sorprendente (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í, no estoy seguro de si será aplicable a las aplicaciones.
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>
</exclusions>
</dependency>
Y si desea verificar en el terminal (consola de Windows para este ejemplo) que su árbol experto no tiene problemas:
mvn dependency:tree -Dverbose | grep --color=always ''(.* conflict/|^'' | less -r
Podría usar el complemento de Maven Forcer con la regla de dependencia prohibida. Esto le permitiría prohibir todos los alias que no desea y permitir solo el que desea. Estas reglas fallarán en la construcción de Maven de su proyecto cuando se viole. Además, si esta regla se aplica a todos los proyectos en una empresa, podría poner la configuración del complemento en un pom principal corporativo.
ver:
Sé que esto no responde exactamente a la pregunta, pero para las personas que vienen de Google, es posible que usen Gradle para su administración de dependencias:
Logré deshacerme de todos los problemas de xerces / Java8 con Gradle de esta manera:
configurations {
all*.exclude group: ''xml-apis''
all*.exclude group: ''xerces''
}
Supongo que hay una pregunta que debes responder:
¿Existe un xerces * .jar con el que pueda vivir todo en su aplicación?
Si no está básicamente atornillado y tendría que usar algo como OSGI, que le permite tener cargadas diferentes versiones de una biblioteca al mismo tiempo. Tenga en cuenta que básicamente reemplaza los problemas de la versión jar con los 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 ruta de clase varias veces, pero es mejor que tener varias versiones diferentes de xerces.
Podría excluir todas las dependencias de xerces y agregar una a la versión que desea usar.
Me pregunto si puede escribir algún tipo de estrategia de resolución de versión como un complemento para maven. Esta sería probablemente la mejor solución, pero si es factible, 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 que los archivos jar de la aplicación se consideren primero para la carga de clases antes de que se considere la carpeta lib del servidor.
Así que para terminar: es un desastre y eso no cambiará.
Todos los proyectos de expertos deberían detenerse dependiendo de xerces, probablemente en realidad no. Las API XML y un Impl han sido parte de Java desde 1.4. No es necesario depender de xerces o de las API XML, es como decir que dependes 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 escribirme un léame que diga que este repositorio requiere Java 1.4.
Todo lo que realmente se rompe porque hace referencia a Xerces directamente a través de las importaciones de org.apache necesita una corrección de código para llevarlo a nivel de Java 1.4 (y lo ha hecho desde 2002) o una solución a nivel de JVM a través de librerías aprobadas, no en Maven.