it-swarm.com.ru

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

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

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

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

 Исключение в потоке "AWT-EventQueue-0" Java.lang.LinkageError: нарушение ограничения загрузчика при инициализации интерфейса itable: при разрешении метода "org.Apache.batik.dom.svg.SVGOMDocument.createAttribute (Ljava/lang/String ;) Lorg/w3c/дом/атр;» загрузчик классов (экземпляр org/Java/plugin/standard/StandardPluginClassLoader) текущего класса, org/Apache/batik/dom/svg/SVGOMDocument и загрузчик классов (экземпляр <bootloader>) для интерфейса org/w3c/У dom/Document есть разные объекты Class для типа org/w3c/dom/Attr, используемого в подписи 
 в 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.

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

54
Urs Reupke

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 - спросите родителя, затем меня)
  • пост-делегирование (распространено в плагинах, сервлетах и ​​местах, где вы хотите изоляцию - спросите меня, а затем родитель)
  • брат (обычно в моделях зависимости, таких как OSGi, Eclipse и т. д.)

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

58
Alex Miller

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

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 для загрузки класса, applover/lib загрузчик классов загрузит этот класс и попытается загрузить зависимый класс, и потерпит неудачу, так как не может найти этот класс в appserver/lib или javahome/Lib.

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

15
matt b

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

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

  • Ошибка выполнения метода: 
  • 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
  • иметь разные объекты Class для типа taticLoggerBinder.getLoggerFactory () Lorg/slf4j/ILoggerFactory; используется в подписи

  1. Смотрите два выделенных класса. Google ищет их как "загрузка jar StaticLoggerBinder.class" и "загрузка jar 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

6
vineetv2821993

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

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

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

5
Adam Crume

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

У меня была такая же проблема при использовании библиотеки Batik в среде RCP Netbeans, библиотека Batik была включена в качестве «модуля оболочки библиотеки». Если какой-то другой модуль использует XML-API, и для этого модуля не требуется и не устанавливается зависимость от Batik, возникает проблема нарушения ограничения загрузчика классов с аналогичными сообщениями об ошибках.

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

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

3
Anselm Schuster

Как указано в этот вопрос , включение -verbose:class сделает журнал JVM информацией обо всех загружаемых классах, что может быть невероятно полезно для понимания, откуда берутся классы в более сложных сценариях и приложениях.

Вывод, который вы получаете, выглядит примерно так (скопировано с этого вопроса):

[Opened /usr/Java/j2sdk1.4.1/jre/lib/rt.jar]
[Opened /usr/Java/j2sdk1.4.1/jre/lib/sunrsasign.jar]
[Opened /usr/Java/j2sdk1.4.1/jre/lib/jsse.jar]
[Opened /usr/Java/j2sdk1.4.1/jre/lib/jce.jar]
[Opened /usr/Java/j2sdk1.4.1/jre/lib/charsets.jar]
[Loaded Java.lang.Object from /usr/Java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded Java.io.Serializable from /usr/Java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded Java.lang.Comparable from /usr/Java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded Java.lang.CharSequence from /usr/Java/j2sdk1.4.1/jre/lib/rt.jar]
[Loaded Java.lang.String from /usr/Java/j2sdk1.4.1/jre/lib/rt.jar]
0
Per Lundberg