¿Tratar con “Xerces hell” en Java / Maven?

En mi oficina, la mera mención de la palabra Xerces es suficiente para incitar a 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 de Java. Casi todas las bibliotecas o marcos escritos en Java usan Xerces en cierta capacidad (de manera transitiva, si no directamente).

  • Las jarras Xerces incluidas en los binarios oficiales son, hasta el día de hoy, no versionadas. Por ejemplo, el archivo jar de implementación Xerces 2.11.0 se llama xercesImpl.jar y no xercesImpl-2.11.0.jar .

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

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

  • Las versiones asignadas a los archivos xml-apis y xercesImpl por aquellos que implementan sus archivos jar en los repositorys 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 ambos sean de Xerces 2.8.0. Esto se debe a que las personas suelen etiquetar el contenedor xml-apis con la versión de las especificaciones que implementa. Hay un desglose muy bonito, pero incompleto de esto aquí .

  • Para complicar las cosas, Xerces es el analizador XML utilizado en la implementación de referencia de la API de Java para Procesamiento XML (JAXP), incluido en el JRE. Las clases de implementación se com.sun.* empaquetar en el espacio de nombres com.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 todas las funciones de Xerces están expuestas a través de las API java.* Y javax.* ; por ejemplo, no hay API que expone 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 carpetas /lib .

Problemas

La resolución de conflictos

Para algunos (o tal vez todos) de los motivos anteriores, muchas organizaciones publican y consumen comstackciones 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 se convierte rápidamente en un problema para el software empresarial donde Artifactory o Nexus está aprovisionando múltiples repositorys (JBoss, Hibernate, etc.):

xml-apis representado por Artifactory

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

org.apache.xerces
xml-apis
2.9.1

Mientras tanto, la organización B podría publicar el mismo jar como:

xml-apis
xml-apis
1.3.04

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 s se incluirán como dependencias resueltas:

dependencias resueltas con múltiples xml-apis

Classloader Hell

Como se mencionó anteriormente, el JRE se envía con Xerces en JAXP RI. Si bien sería bueno marcar todas las dependencias de Xerces Maven como o como , el código de terceros del que dependa puede o no funcionar con la versión provista en JAXP del JDK que está utilizando. Además, tiene los jarros Xerces enviados en su contenedor de servlet con los que lidiar. Esto le deja con varias opciones: ¿elimina la versión de servlet y espera que su contenedor se ejecute en la versión de JAXP? ¿Es mejor dejar la versión del servlet y esperar que sus frameworks de aplicaciones se ejecuten en la versión del servlet? Si uno o dos de los conflictos no resueltos descritos anteriormente logran introducirse en su producto (es fácil de lograr en una organización grande), rápidamente se encuentra en el infierno de los cargadores de clases, preguntándose qué versión de Xerces está buscando el ejecutor de clases en tiempo de ejecución y si elegirá el mismo jar en Windows y Linux (probablemente no).

Soluciones?

Hemos intentado marcar todas las dependencias de Xerces Maven como o como , pero esto es difícil de aplicar (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 de JAXP o en la versión proporcionada por un contenedor de servlet.

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


ACTUALIZACIÓN : Joshua Spiewak ha subido una versión parcheada de los guiones de construcción de Xerces a XERCESJ-1454 que permite su carga en Maven Central. Vote / observe / contribuya a este problema y solucionemos este problema de una vez por todas.

¡Hay 2.11.0 JAR (y JARs de origen!) 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:

  xerces xercesImpl 2.11.0  

y todas las dependencias se han resuelto bien, ¡incluso el xml-apis-1.4.01 !

Y lo 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.

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

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 desea. Estas reglas no podrán generar la construcción de su proyecto cuando se viole. 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:

Sé que esto no responde la pregunta exactamente, pero para las personas que vienen de Google que utilizan Gradle para su gestión de la 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' } 

Supongo que hay una pregunta que debes responder:

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

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 los problemas del cargador de clases …

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

Puede excluir cada dependencia 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 un complemento para maven. Esta sería probablemente la mejor solución, pero si es factible, necesita alguna investigación y encoding.

Para la versión contenida en su entorno de tiempo de ejecución, deberá asegurarse de que sea eliminada de la ruta de clases de la aplicación o que los archivos jar de la aplicación sean considerados primero para la carga de clases antes de que se considere la carpeta lib del servidor.

Entonces, para concluir: es un desastre y eso no cambiará.

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

  xerces xercesImpl ... true  

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

Esto crea un fuerte incentivo para que los proyectos posteriores:

  • Toma una decisión activa. ¿Van con la misma versión de Xerces o usan algo más?
  • En realidad, pruebe su análisis sintáctico (por ejemplo, a través de pruebas unitarias) y la carga de clases, así como también no obstruya su ruta de clase.

No todos los desarrolladores mvn dependency:tree un seguimiento de las dependencias recientemente introducidas (por ejemplo, con mvn dependency:tree ). Este enfoque inmediatamente traerá el asunto a su atención.

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

Debes depurar primero, para ayudar a identificar tu 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, comience a excluir bibliotecas. Si no, agrega

 -Djaxp.debug=1 

a la línea de comandos.

Lo que ayudaría, excepto para excluir, son las dependencias modulares.

Con una carga de clases plana (aplicación independiente), o semijerá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 la biblioteca que deseen, de forma independiente.

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

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:

                   

En comparación con OSGi, JBoss Modules es más simple y más rápido. 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 increíblemente rápido (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 modularizar el JRE en sí mismo, no estoy seguro de si será aplicable a las aplicaciones.

Cada proyecto de maven debería dejar de depender de xerces, probablemente no lo hagan realmente. Las API XML y un Impl han sido parte de Java desde 1.4. No hay necesidad de depender de xerces o XML API, es como decir que dependes de Java o Swing. Esto es implícito

Si yo fuera el jefe de un repository maven, escribiría un script para eliminar de forma recursiva las dependencias de xerces y escribir una lectura que diga que este repository requiere Java 1.4.

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

Aparentemente xerces:xml-apis:1.4.01 ya no está en maven central, que sin embargo es lo que xerces:xercesImpl:2.11.0 referencias.

Esto funciona para mí:

  xerces xercesImpl 2.11.0   xerces xml-apis     xml-apis xml-apis 1.4.01  

Mi amigo es muy simple, aquí un ejemplo:

  xalan xalan 2.7.2 ${my-scope}   xml-apis xml-apis    

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