¿Cómo lidiar con LinkageErrors en Java?

Desarrollando una aplicación Java fuertemente basada en XML, recientemente encontré un problema interesante en Ubuntu Linux.

Mi aplicación, utilizando el Marco de complementos de Java , parece incapaz de convertir un documento XML creado por dom4j en la implementación de Batik de la especificación SVG.

En la consola, me entero de que se produce un error:

 Excepción en el hilo "AWT-EventQueue-0" java.lang.LinkageError: violación de la restricción del cargador en la inicialización de la interfaz itable: al resolver el método "org.apache.batik.dom.svg.SVGOMDocument.createAttribute (Ljava / lang / String;) Lorg / w3c / dom / Attr; "  el cargador de clases (instancia de org / java / plugin / standard / StandardPluginClassLoader) de la clase actual, org / apache / batik / dom / svg / SVGOMDocument, y el cargador de clases (instancia de ) para la interfaz org / w3c / dom / Document tiene diferentes objetos Class para el tipo org / w3c / dom / Attr utilizado en la firma
     en org.apache.batik.dom.svg.SVGDOMImplementation.createDocument (SVGDOMImplementation.java:149)
     en org.dom4j.io.DOMWriter.createDomDocument (DOMWriter.java:361)
     en org.dom4j.io.DOMWriter.write (DOMWriter.java:138)

Me imagino que el problema está causado por un conflicto entre el cargador de clases original de la JVM y el cargador de clases implementado por el marco de plugins.

Que yo sepa, no es posible especificar un cargador de clases para el marco de trabajo. Podría ser posible piratearlo, pero preferiría un enfoque menos agresivo para resolver este problema, ya que (por cualquier razón) solo ocurre en sistemas Linux.

¿Alguno de ustedes se ha encontrado con ese problema y tiene alguna idea de cómo solucionarlo o, al menos, llegar al meollo del problema?

LinkageError es lo que obtendrás en un caso clásico en el que tienes una clase C cargada por más de un cargador de clases y esas clases se usan juntas en el mismo código (comparado, emitido, etc.). No importa si es el mismo nombre de Clase o incluso si se carga desde el contenedor idéntico: una Clase de un cargador de clases siempre se trata como una Clase diferente si se carga desde otro cargador de clases.

El mensaje (que ha mejorado mucho a lo largo de los años) dice:

Exception in thread "AWT-EventQueue-0" java.lang.LinkageError: loader constraint violation in interface itable initialization: when resolving method "org.apache.batik.dom.svg.SVGOMDocument.createAttribute(Ljava/lang/String;)Lorg/w3c/dom/Attr;" the class loader (instance of org/java/plugin/standard/StandardPluginClassLoader) of the current class, org/apache/batik/dom/svg/SVGOMDocument, and the class loader (instance of ) for interface org/w3c/dom/Document have different Class objects for the type org/w3c/dom/Attr used in the signature 

Entonces, aquí el problema está en resolver el método SVGOMDocument.createAttribute (), que usa org.w3c.dom.Attr (parte de la biblioteca DOM estándar). Sin embargo, la versión de Attr cargada con Batik se cargó desde un cargador de clases diferente de la instancia de Attr que está pasando al método.

Verás que la versión de Batik parece estar cargada desde el complemento de Java. Y el suyo se está cargando desde “”, que probablemente sea uno de los cargadores JVM incorporados (ruta de clases de arranque, ESOM o classpath).

Los tres modelos de cargador de clases prominentes son:

  • delegación (el valor predeterminado en el JDK – pregunte al padre, luego a mí)
  • post-delegación (común en complementos, servlets y lugares donde desea aislamiento – pregúnteme, luego padre)
  • hermano (común en modelos de dependencia como OSGi, Eclipse, etc.)

No sé qué estrategia de delegación utiliza el cargador de clases JPF, pero la clave es que usted quiere que se cargue una versión de la biblioteca dom y que todos puedan obtener esa clase desde la misma ubicación. Eso puede significar eliminarlo de la ruta de clase y cargarlo como un complemento, o evitar que Batik lo cargue, o alguna otra cosa.

Suena como un problema de jerarquía de cargador de clases. No puedo decir en qué tipo de entorno se implementa su aplicación, pero a veces este problema puede ocurrir en un entorno web, donde el servidor de aplicaciones crea una jerarquía de cargadores de clases, que se asemeja a algo como:

javahome / lib – como root
appserver / lib – como hijo de root
webapp / WEB-INF / lib – como hijo de un hijo de root
etc

Por lo general, los cargadores de clases delegan la carga a su cargador de clases padre (esto se conoce como ” parent-first “), y si ese cargador de clases no puede encontrar la clase, entonces el cargador de clases hijo intenta hacerlo. Por ejemplo, si una clase implementada como JAR en webapp / WEB-INF / lib intenta cargar una clase, primero le pide al cargador de clases correspondiente a appserver / lib que cargue la clase (que a su vez le pregunta al cargador de clases correspondiente a javahome / lib para cargar la clase), y si esta búsqueda falla, entonces se busca una coincidencia con esta clase en WEB-INF / lib.

En un entorno web, puede tener problemas con esta jerarquía. Por ejemplo, un error / problema con el que me he encontrado antes era cuando una clase en WEB-INF / lib dependía de una clase implementada en appserver / lib, que a su vez dependía de una clase desplegada en WEB-INF / lib. Esto causó fallas porque aunque los cargadores de clases pueden delegar en el cargador de clases principal, no pueden delegar en el árbol. Por lo tanto, el cargador de clases WEB-INF / lib le preguntaría a un servidor de aplicaciones / lib classloader por una clase, el servidor de aplicaciones / lib cargaría esa clase y trataría de cargar la clase dependiente, y fallaría ya que no podría encontrar esa clase en appserver / lib o javahome / lib.

Por lo tanto, aunque es posible que no implemente su aplicación en un entorno de servidor web / de aplicaciones, mi explicación demasiado extensa podría aplicarse a usted si su entorno tiene configurada una jerarquía de cargadores de clases. ¿Lo hace? ¿JPF está haciendo algún tipo de magia de cargador de clase para poder implementar sus funciones de complemento?

Puede ser que esto ayude a alguien porque funciona bastante bien para mí. El problema se puede resolver integrando sus propias dependencias. Sigue estos sencillos pasos

Primero verifica el error que debería ser así:

  • La ejecución del método falló:
  • java.lang.LinkageError: violación de restricción de cargador:
  • al resolver el método “org.slf4j.impl. StaticLoggerBinder .getLoggerFactory () Lorg / slf4j / ILoggerFactory;”
  • el cargador de clases (instancia de org / openmrs / module / ModuleClassLoader) de la clase actual, org / slf4j / LoggerFactory ,
  • y el cargador de clases (instancia de org / apache / catalina / loader / WebappClassLoader) para la clase resuelta, org / slf4j / impl / StaticLoggerBinder ,
  • tener diferentes objetos de Clase para el tipo taticLoggerBinder.getLoggerFactory () Lorg / slf4j / ILoggerFactory; utilizado en la firma

  1. Ver las dos clases resaltadas. Google los busca como “Descarga de jar de StaticLoggerBinder.class” y “Descarga de jar de LoggeraFactory.class”. Esto le mostrará primero o en algún caso, un segundo enlace (El sitio es http://www.java2s.com ) que es una de las versiones de jar que ha incluido en su proyecto. Puedes identificarlo inteligentemente, pero somos adictos a google;)

  2. Después de eso sabrá el nombre del archivo jar, en mi caso es como slf4j-log4j12-1.5.6.jar & slf4j-api-1.5.8

  3. Ahora la última versión de este archivo está disponible aquí http://mvnrepository.com/ (en realidad, toda la versión hasta la fecha, este es el sitio desde donde maven obtiene sus dependencias).
  4. Ahora agregue ambos archivos como dependencias con la última versión (o mantenga ambas versiones de archivo iguales, la versión elegida es antigua). A continuación está la dependencia que debe incluir en pom.xml

  org.slf4j slf4j-api 1.7.7   org.slf4j slf4j-log4j12 1.7.7  

Cómo obtener definición de dependencia de Maven Site

¿Puedes especificar un cargador de clases? De lo contrario, intente especificar el cargador de clases de contexto de la siguiente manera:

 Thread thread = Thread.currentThread(); ClassLoader contextClassLoader = thread.getContextClassLoader(); try { thread.setContextClassLoader(yourClassLoader); callDom4j(); } finally { thread.setContextClassLoader(contextClassLoader); } 

No estoy familiarizado con el Marco de complementos de Java, pero escribo código para Eclipse, y de vez en cuando me encuentro con problemas similares. No garantizo que lo arregle, pero probablemente valga la pena intentarlo.

Las respuestas de Alex y Matt son muy útiles. Me podría beneficiar de su análisis también.

Tuve el mismo problema al usar la biblioteca de Batik en un marco de RCP de Netbeans, la biblioteca de Batik se incluyó como un “Módulo de Contenedor de Biblioteca”. Si algún otro módulo hace uso de XML apis, y no se necesita y se establece ninguna dependencia de Batik para ese módulo, el problema de violación de la restricción del cargador de clase surge con mensajes de error similares.

En Netbeans, los módulos individuales usan cargadores de clases dedicados, y la relación de dependencia entre los módulos implica un enrutamiento de delegación de cargador de clases adecuado.

Podría resolver el problema simplemente omitiendo el archivo jar xml-apis del paquete de la biblioteca Batik.