Как работать с LinkageErrors в Java?

Разработка Java-приложения на основе XML, я недавно столкнулся с интересной проблемой в Ubuntu Linux.

Мое приложение, использующее Java Plugin Framework, кажется неспособным преобразовать dom4j - созданный XML-документ в реализация Batik спецификации SVG.

На консоли я узнаю, что произошла ошибка:

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 <bootloader>) for interface org/w3c/dom/Document have different Class objects for the type org/w3c/dom/Attr used in the signature
    at org.apache.batik.dom.svg.SVGDOMImplementation.createDocument(SVGDOMImplementation.java:149)
    at org.dom4j.io.DOMWriter.createDomDocument(DOMWriter.java:361)
    at org.dom4j.io.DOMWriter.write(DOMWriter.java:138)

Я полагаю, что проблема вызвана конфликтом между исходным загрузчиком классов от JVM и загрузчиком классов, развернутым в рамках плагина.

Насколько мне известно, невозможно определить загрузчик классов для используемой структуры. Возможно, это можно будет взломать, но я бы предпочел менее агрессивный подход к решению этой проблемы, поскольку (по какой-либо причине) это происходит только в системах Linux.

Кто-нибудь из вас столкнулся с такой проблемой и имеет какое-либо представление о том, как ее исправить или, по крайней мере, решить эту проблему?

Ответ 1

LinkageError - это то, что вы получите в классическом случае, когда у вас есть класс C, загруженный более чем одним загрузчиком классов, и эти классы используются вместе в одном и том же коде (сравнение, литье и т.д.). Не имеет значения, является ли это одно и то же имя класса или даже если оно загружено из идентичного jar - класс из одного загрузчика классов всегда рассматривается как другой класс, если он загружен из другого загрузчика классов.

Сообщение (которое много лет улучшалось) говорит:

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

Итак, здесь проблема заключается в разрешении метода SVGOMDocument.createAttribute(), который использует org.w3c.dom.Attr(часть стандартной библиотеки DOM). Но версия Attr, загруженная Batik, была загружена из другого загрузчика классов, чем экземпляр Attr, который вы передаете методу.

Вы увидите, что версия Batik, похоже, загружается из плагина Java. И ваш загружается из "", что, скорее всего, является одним из встроенных загрузчиков JVM (boot classpath, ESOM или classpath).

Три выдающихся класса загрузчика:

  • делегирование (по умолчанию в JDK - запрос родителя, затем меня)
  • пост-делегирование (общее в плагинах, сервлетах и ​​местах, где вы хотите изолировать - спросите меня, потом родителя)
  • sibling (общий в моделях зависимостей, таких как OSGi, Eclipse и т.д.)

Я не знаю, какую стратегию делегирования использует загрузчик классов JPF, но ключ заключается в том, что вы хотите, чтобы одна версия библиотеки dom была загружена, и все, чтобы указать этот класс из того же места. Это может означать удаление его из пути к классам и загрузку в виде плагина или предотвращение загрузки Батика или что-то еще.

Ответ 2

Звучит как проблема иерархии загрузчика классов. Я не могу определить, в какой среде развертывается ваше приложение, но иногда эта проблема может возникать в веб-среде - где сервер приложений создает иерархию загрузчиков классов, напоминающую что-то вроде:

javahome/lib - как root
appserver/lib - как дочерний элемент root

webapp/WEB-INF/lib - как дочерний элемент дочернего элемента root и т.д.

Обычно загрузчики классов делегируют загрузку в свой родительский загрузчик классов (это называется "parent-first" ), и если этот загрузчик классов не может найти класс, то пытается выполнить загрузчик классов. Например, если класс, развернутый как JAR в webapp/WEB-INF/lib, пытается загрузить класс, сначала он запрашивает загрузчик классов, соответствующий загрузке класса appserver/lib (который, в свою очередь, запрашивает загрузчик классов, соответствующий javahome/lib для загрузки класса), и если этот поиск завершился неудачно, то в WEB-INF/lib выполняется поиск соответствия этому классу.

В веб-среде вы можете столкнуться с проблемами с этой иерархией. Например, одна ошибка/проблема, с которой я столкнулся раньше, заключалась в том, что класс в WEB-INF/lib зависел от класса, развернутого в appserver/lib, что, в свою очередь, зависело от класса, развернутого в WEB-INF/lib. Это вызвало сбои, поскольку, поскольку загрузчики классов могут делегировать родительскому загрузчику классов, они не могут делегировать обратно дерево. Таким образом, загрузчик классов WEB-INF/lib задал бы класс загрузчика классов appserver/lib, загрузчик классов appserver/lib загрузил бы этот класс и попытался загрузить зависимый класс и потерпел неудачу, так как не смог найти этот класс в appserver/lib или javahome/Lib.

Итак, хотя вы не можете развертывать свое приложение в среде веб-сервера или приложения, мое слишком длинное объяснение может быть применимо к вам, если ваша среда имеет иерархию классов загрузчиков. Имеет ли это? Является ли JPF чем-то вроде магии загрузчика классов, чтобы иметь возможность реализовать его функции плагина?

Ответ 3

Может быть, это поможет кому-то, потому что это очень хорошо для меня. Проблема может быть решена путем интеграции ваших собственных зависимостей. Выполните следующие простые шаги.

Сначала проверьте ошибку, которая должна быть такой:

  • Ошибка выполнения метода:
  • java.lang.LinkageError: нарушение ограничения загрузчика:
  • при разрешении метода "org.slf4j.impl. StaticLoggerBinder.getLoggerFactory() Lorg/slf4j/ILoggerFactory;"
  • загрузчик классов (экземпляр org/openmrs/module/ModuleClassLoader) текущего класса, org/slf4j/ LoggerFactory,
  • и загрузчик классов (экземпляр org/apache/catalina/loader/WebappClassLoader) для разрешенного класса, org/slf4j/impl/ StaticLoggerBinder,
  • имеют разные объекты класса для типа taticLoggerBinder.getLoggerFactory() Lorg/slf4j/ILoggerFactory; используемый в сигнатуре

  1. Посмотрите на два выделенных класса. Google ищет их, например, "Загрузка файла StaticLoggerBinder.class" и "Загрузка файла LoggeraFactory.class". Это покажет вам первую или в некоторых случаях вторую ссылку (сайт http://www.java2s.com), который является одной из версий jar, которые вы включили в свою проект. Вы можете легко идентифицировать его самостоятельно, но мы зависим от google;)

  2. После этого вы узнаете имя файла jar, в моем случае это похоже на slf4j-log4j12-1.5.6.jar и slf4j-api-1.5.8

  3. Теперь последняя версия этого файла доступна здесь http://mvnrepository.com/ (фактически вся версия до даты, это сайт, откуда maven получить ваши зависимости).
  4. Теперь добавьте оба файла в зависимости от последней версии (или сохраните обе версии файла одинаково, либо выбранная версия устарела). Ниже приведена зависимость, которую вы должны включить в pom.xml
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.7</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.7</version>
</dependency>

How to get dependecy definition from Maven Site

Ответ 4

Можно ли указать загрузчик классов? Если нет, попробуйте указать загрузчик класса контекста следующим образом:

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

Я не знаком с платформой Java Plugin Framework, но я пишу код для Eclipse, и время от времени я сталкиваюсь с подобными проблемами. Я не гарантирую, что это исправит, но это, вероятно, стоит сделать.

Ответ 5

Ответы от Алекса и Мэтта очень полезны. Я мог бы воспользоваться их анализом.

У меня была такая же проблема при использовании библиотеки Batik в инфраструктуре RCP NetBeans, а в библиотеке Батика в качестве "модуля библиотеки библиотеки". Если в каком-либо другом модуле используется XML-apis, и никакая зависимость от Batik не требуется и не установлена ​​для этого модуля, проблема нарушения ограничений класса-загрузчика возникает с аналогичными сообщениями об ошибках.

В Netbeans отдельные модули используют специализированные загрузчики классов, а зависимость между модулями подразумевает подходящую маршрутизацию делегирования класса.

Я мог бы решить проблему, просто опуская файл jml-apis jar из пакета библиотеки Batik.