Как бороться с LinkageErrors в Java?
Разрабатывая Java-приложение на основе XML, я недавно столкнулся с интересной проблемой в Ubuntu Linux.
Мое приложение, использующее платформу Java Plugin Framework, оказалось не в состоянии преобразовать документ XML, созданный dom4j, в реализацию спецификации 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 и загрузчик классов (экземпляр) для интерфейса org / w3c / У dom/Document есть разные объекты Class для типа org/w3c/dom/Attr, используемого в подписи в org.apache.batik.dom.svg.SVGDOMImplementation.createDocument(SVGDOMImplementation.java:149) в org.dom4j.io.DOMWriter.createDomDocument(DOMWriter.java:361) в org.dom4j.io.DOMWriter.write(DOMWriter.java:138)
Я полагаю, что проблема вызвана конфликтом между исходным загрузчиком классов из JVM и загрузчиком классов, развернутым платформой плагинов.
Насколько мне известно, невозможно указать загрузчик классов для используемой платформы. Возможно, это можно будет взломать, но я бы предпочел менее агрессивный подход к решению этой проблемы, поскольку (по любой причине) это происходит только в системах Linux.
Кто-нибудь из вас сталкивался с такой проблемой и знает, как ее исправить или, по крайней мере, понять суть проблемы?
6 ответов
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, или что-то еще.
Похоже, проблема иерархии загрузчика классов. Я не могу сказать, в какой среде развертывается ваше приложение, но иногда эта проблема может возникать в веб-среде - где сервер приложений создает иерархию загрузчиков классов, напоминающую что-то вроде:
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 делает что-то вроде магии загрузчика классов, чтобы иметь возможность реализовать свои функции плагина?
Может быть, это кому-то поможет, потому что у меня это хорошо получается. Проблема может быть решена путем интеграции ваших собственных зависимостей. Следуйте этим простым шагам
Сначала проверьте ошибку, которая должна быть такой:
- Ошибка выполнения метода:
- 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; используется в подписи
Смотрите два выделенных класса. Google ищет их как "загрузка jar StaticLoggerBinder.class" и "загрузка jar LoggeraFactory.class". Это покажет вам первую или в некоторых случаях вторую ссылку (сайт http://www.java2s.com/), которая является одной из версий jar, которые вы включили в свой проект. Вы можете определить это самостоятельно, но мы зависим от Google;)
После этого вы узнаете имя файла jar, в моем случае это похоже на slf4j-log4j12-1.5.6.jar & slf4j-api-1.5.8
- Теперь последняя версия этого файла доступна здесь http://mvnrepository.com/ (фактически, все версии до даты, это сайт, откуда maven получает ваши зависимости).
- Теперь добавьте оба файла как зависимости с последней версией (или оставьте обе версии файлов одинаковыми, либо выбранная версия устарела). Ниже приведена зависимость, которую вы должны включить в 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>
Можете ли вы указать загрузчик классов? Если нет, попробуйте указать загрузчик класса контекста следующим образом:
Thread thread = Thread.currentThread();
ClassLoader contextClassLoader = thread.getContextClassLoader();
try {
thread.setContextClassLoader(yourClassLoader);
callDom4j();
} finally {
thread.setContextClassLoader(contextClassLoader);
}
Я не знаком с платформой Java Plugin Framework, но я пишу код для Eclipse и время от времени сталкиваюсь с подобными проблемами. Я не гарантирую, что это исправит это, но это, вероятно, стоит попробовать.
Ответы Алекса и Мэтта очень полезны. Я мог бы извлечь выгоду из их анализа тоже.
У меня была такая же проблема при использовании библиотеки Batik в среде RCP Netbeans, библиотека Batik была включена в качестве "модуля оболочки библиотеки". Если какой-то другой модуль использует XML-API, и для этого модуля не требуется и не устанавливается зависимость от Batik, возникает проблема нарушения ограничения загрузчика классов с аналогичными сообщениями об ошибках.
В Netbeans отдельные модули используют выделенные загрузчики классов, а зависимость между модулями подразумевает подходящую маршрутизацию делегирования загрузчика классов.
Я мог бы решить эту проблему, просто пропустив файл jar xml-apis из пакета библиотеки Batik.
Я считаю, что этот класс загружается дважды. Причина в том, что parallelWebappClassLoader сначала загружает класс сам по себе, а не использует его родительский classLoader.
Как указано в этом вопросе, включение -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]