Имеете дело с «Xerces ад» в Java / Maven?

732

В моем офисе простого упоминания слова Xerces достаточно, чтобы вызвать убийственную ярость от разработчиков. Беглый взгляд на другие вопросы Xerces по SO, похоже, указывает на то, что в какой-то момент почти все пользователи Maven «затронуты» этой проблемой. К сожалению, понимание проблемы требует немного знаний об истории Xerces ...

история

  • Xerces - наиболее широко используемый анализатор XML в экосистеме Java. Почти каждая библиотека или фреймворк, написанный на Java, в некоторой степени использует Xerces (транзитивно, если не напрямую).

  • Банки Xerces, включенные в официальные двоичные файлы , до настоящего времени не имеют версий. Например, jar реализации Xerces 2.11.0 назван xercesImpl.jarи нет xercesImpl-2.11.0.jar.

  • Команда Xerces не использует Maven , что означает, что они не загружают официальный релиз в Maven Central .

  • Раньше Xerces выпускался как один jar ( xerces.jar), но был разделен на два jar , один из которых содержал API ( xml-apis.jar), а другой содержал реализации этих API ( xercesImpl.jar). Многие старые POM Maven по-прежнему объявляют зависимость xerces.jar. В какой-то момент в прошлом был также выпущен Xerces xmlParserAPIs.jar, от которого также зависят некоторые старые POM.

  • Версии, назначаемые банкам xml-apis и xercesImpl теми, кто развертывает свои банки в репозиториях Maven, часто отличаются. Например, xml-apis может иметь версию 1.3.03, а xercesImpl - версию 2.8.0, даже если обе версии Xerces 2.8.0. Это связано с тем, что люди часто помечают банку xml-apis версией спецификаций, которые он реализует. Существует очень хороший, но неполный пробой этого здесь .

  • Чтобы усложнить ситуацию, Xerces - это анализатор XML, используемый в эталонной реализации Java API для обработки XML (JAXP), включенной в JRE. Классы реализации переупаковываются в com.sun.*пространстве имен, что делает опасным прямой доступ к ним, поскольку они могут быть недоступны в некоторых JRE. Однако не все функциональные возможности Xerces предоставляются через API java.*и javax.*API; например, нет API, который предоставляет сериализацию Xerces.

  • Помимо этого запутанного беспорядка, почти все контейнеры сервлетов (JBoss, Jetty, Glassfish, Tomcat и т. Д.) Поставляются с Xerces в одной или нескольких /libпапках.

Проблемы

Решение конфликта

По некоторым - или, возможно, по всем - причинам, изложенным выше, многие организации публикуют и используют пользовательские сборки Xerces в своих POM. На самом деле это не проблема, если у вас небольшое приложение и вы используете только Maven Central, но это быстро становится проблемой для корпоративного программного обеспечения, где Artifactory или Nexus проксирует несколько репозиториев (JBoss, Hibernate и т. Д.):

xml-apis, передаваемый Artifactory

Например, организация А может опубликовать xml-apisкак:

<groupId>org.apache.xerces</groupId>
<artifactId>xml-apis</artifactId>
<version>2.9.1</version>

Между тем, организация B может опубликовать то же самое, jarчто и:

<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
<version>1.3.04</version>

Хотя B jar- более низкая версия, чем A jar, Maven не знает, что это один и тот же артефакт, потому что у них разные groupIds. Таким образом, он не может выполнять разрешение конфликтов, и оба jarбудут включены как разрешенные зависимости:

разрешены зависимости с несколькими xml-apis

Classloader Hell

Как упоминалось выше, JRE поставляется с Xerces в JAXP RI. Хотя было бы неплохо отметить все зависимости Xerces Maven как <exclusion>s или как<provided>сторонний код, от которого вы зависите, может работать или не работать с версией, предоставленной в JAXP JDK, который вы используете. Кроме того, у вас есть контейнеры Xerces, отправленные в ваш контейнер сервлетов для борьбы. Это оставляет вам несколько вариантов: удаляете ли вы версию сервлета и надеетесь, что ваш контейнер работает на версии JAXP? Лучше ли оставить версию сервлета и надеяться, что фреймворки ваших приложений будут работать на версии сервлета? Если одному или двум из неразрешенных конфликтов, описанных выше, удастся проникнуть в ваш продукт (это легко случится в большой организации), вы быстро окажетесь в аду загрузчика классов, задаваясь вопросом, какую версию Xerces выбирает загрузчик классов во время выполнения и действительно ли она выберет одну и ту же банку в Windows и Linux (вероятно, нет).

Решения?

Мы пытались помечая все зависимости Xerces Maven , как <provided>или как <exclusion>, но это трудно реализовать (особенно с большой группой) , учитывая , что артефакты имеют так много псевдонимов ( xml-apis, xerces, xercesImpl, xmlParserAPIsи т.д.). Кроме того, наши сторонние библиотеки libs / frameworks могут не работать на версии JAXP или версии, предоставляемой контейнером сервлета.

Как мы можем лучше всего решить эту проблему с Maven? Должны ли мы осуществлять такой детальный контроль над нашими зависимостями, а затем полагаться на многоуровневую загрузку классов? Есть ли способ глобально исключить все зависимости Xerces и заставить все наши фреймворки / библиотеки использовать версию JAXP?


ОБНОВЛЕНИЕ : Джошуа Спивак загрузил исправленную версию сценариев сборки Xerces в XERCESJ-1454, которая позволяет загружать их в Maven Central. Проголосуйте / посмотрите / внесите свой вклад в эту проблему, и давайте решим эту проблему раз и навсегда.

Джастин гаррик
источник
8
Спасибо за этот подробный вопрос. Я не понимаю мотивацию команды xerces. Я предположил бы, что они гордятся там продуктом и получают удовольствие от других, использующих это, но текущее состояние xerces и maven позорное. Тем не менее, они могут делать то, что они хотят, даже если это не имеет смысла для меня. Интересно, есть ли какие-нибудь предложения у ребят из sonatype?
Трэвис Шнеебергер
35
Это может быть не по теме, но это, вероятно, лучший пост, который я когда-либо видел. Более того, вопрос, который вы описываете, является одной из самых болезненных проблем, с которыми мы можем столкнуться. Отличная инициатива!
Жан-Реми Реви
2
@TravisSchneeberger Большая часть сложности заключается в том, что Sun решила использовать Xerces в самой JRE. Вряд ли можно винить в этом людей Xerces.
Турбьёрн Равн Андерсен
Обычно мы пытаемся найти версию Xerces, которая бы удовлетворяла всем зависимым библиотекам методом проб и ошибок, если это невозможно, а затем реорганизовать WAR-файлы, чтобы разделить приложение на отдельные WAR (отдельные загрузчики классов). Этот инструмент (я написал его) помогает понять, что происходит на jhades.org , позволяя запрашивать путь к классам для jar-файлов и классов - он работает также в случае, когда сервер еще не запускается
Angular University
Просто быстрый комментарий, если вы получаете эту ошибку при запуске servicemix из git bash в windows: вместо этого запустите его из "обычного" cmd.
Альберт Хендрикс

Ответы:

112

С 20 февраля 2013 года в Maven Central есть 2.11.0 JAR (и исходных JAR!) Xerces! Смотрите Ксерсес в Maven Central . Интересно, почему они не решили https://issues.apache.org/jira/browse/XERCESJ-1454 ...

Я использовал:

<dependency>
    <groupId>xerces</groupId>
    <artifactId>xercesImpl</artifactId>
    <version>2.11.0</version>
</dependency>

и все зависимости разрешились нормально - даже правильно xml-apis-1.4.01!

И что самое важное (и что не было очевидно в прошлом) - JAR в Maven Central - это тот же JAR, что и в официальном Xerces-J-bin.2.11.0.zipдистрибутиве .

Однако я не смог найти xml-schema-1.1-betaверсию - это не может быть classifierверсия Maven из-за дополнительных зависимостей.

Grzegorz Grzybek
источник
9
Несмотря на то, что это очень смущает , что xml-apis:xml-apis:1.4.01это новее , чем xml-apis:xml-apis:2.0.2?? видеть search.maven.org/…
Хенди Ираван
Это сбивает с толку, но это происходит из-за сторонних загрузок не версированных банок Xerces, как говорил в своем посте justingarrik. xml-apis 2.9.1 аналогичен 1.3.04, поэтому в этом смысле 1.4.01 новее (и численно больше), чем 1.3.04.
liltitus27
1
Если в вашем файле pom.xml есть и xercesImpl, и xml-apis, обязательно удалите зависимость xml-apis! В противном случае 2.0.2 поднимает свою уродливую голову.
MikeJRamsey56
64

Честно говоря, почти все, с чем мы столкнулись, прекрасно работает с версией JAXP, поэтому мы всегда исключаем xml-apis и xercesImpl.

jtahlborn
источник
13
Не могли бы вы добавить для этого фрагмент pom.xml?
ЧЗБРГЛА
10
Когда я пытаюсь это сделать, я получаю JavaMelody и Spring бросать java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversalво время выполнения.
Дэвид Моулз
Чтобы добавить к ответу Дэвида Моулза - я видел полдюжины переходных зависимостей, нуждающихся в ElementTraversal. Разные вещи в Spring и Hadoop чаще всего.
Скотт Кэри
2
Если вы получаете java.lang.NoClassDefFoundError: org / w3c / dom / ElementTraversal, попробуйте добавить xml-apis 1.4.01 в ваш pom (и исключите все другие зависимые версии)
Джастин Роу
1
ElementTraversal - это новый класс, добавленный в Xerces 11 и доступный в зависимости xml-apis: xml-apis: 1.4.01. Таким образом, вам может потребоваться скопировать класс вручную в ваш проект или использовать всю зависимость, которая вызывает дублирование классов в загрузчике классов. Но в JDK9 этот класс был включен, поэтому в функции вам может понадобиться удалить dep.
Сергей Пономарев
42

Вы можете использовать плагин Maven Enforcer с правилом запрещенных зависимостей. Это позволит вам запретить все псевдонимы, которые вы не хотите, и разрешить только тот, который вы хотите. Эти правила не удастся собрать maven вашего проекта при нарушении. Кроме того, если это правило применяется ко всем проектам на предприятии, вы можете поместить конфигурацию плагина в корпоративный родительский модуль.

видеть:

Трэвис Шнеебергер
источник
33

Я знаю, что это не дает точного ответа на этот вопрос, но для пользователей из Google, которые используют Gradle для управления своими зависимостями:

Мне удалось избавиться от всех проблем xerces / Java8 с Gradle следующим образом:

configurations {
    all*.exclude group: 'xml-apis'
    all*.exclude group: 'xerces'
}
netmikey
источник
36
хорошо, с maven вам нужно около 4000 строк XML для этого.
Текнопаул
это не решило проблему. какие-нибудь другие советы для людей Android-Gradle?
укол
2
@teknopaul XML используется исключительно для конфигурации. Groovy - это язык программирования высокого уровня. Иногда вы можете захотеть использовать XML для его ясности вместо groovy для его волшебства.
Драгас
16

Я думаю, есть один вопрос, на который вам нужно ответить:

Существует ли xerces * .jar, с которым все в вашем приложении может жить?

Если нет, то вы в основном испорчены и должны использовать что-то вроде OSGI, что позволяет загружать разные версии библиотеки одновременно. Имейте в виду, что это в основном заменяет проблемы с jar-версиями на проблемы с загрузчиком классов ...

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

Вы можете исключить каждую зависимость от xerces и добавить ее в версию, которую хотите использовать.

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

Для версии, содержащейся в вашей среде выполнения, вы должны будете убедиться, что она либо будет удалена из пути к классам приложения, либо файлы jar приложения будут считаться первыми для загрузки классов, а затем папка lib на сервере.

Итак, чтобы подвести итог: это беспорядок, и это не изменится.

Йенс Шаудер
источник
1
Один и тот же класс из одного и того же jar-файла, загруженного разными ClassLoaders, все еще является ClassCastException (во всех стандартных контейнерах)
Ajax
3
Точно. Вот почему я написал: «Имейте в виду, что это в основном заменяет проблемы с jar-версиями на проблемы с загрузчиками классов»
Jens Schauder,
7

Есть еще один вариант, который здесь не рассматривался: объявление зависимостей Xerces в Maven как необязательных :

<dependency>
   <groupId>xerces</groupId>
   <artifactId>xercesImpl</artifactId>
   <version>...</version>
   <optional>true</optional>
</dependency>

По сути, это заставляет всех иждивенцев декларировать свои версию Xerces, иначе их проект не скомпилируется. Если они хотят переопределить эту зависимость, они могут это сделать, но тогда у них будет потенциальная проблема.

Это создает сильный стимул для последующих проектов:

  • Примите активное решение. Они идут с той же версией Xerces или используют что-то еще?
  • На самом деле тестируйте их анализ (например, с помощью модульного тестирования) и загрузку классов, а также не загромождайте их путь к классам.

Не все разработчики отслеживают вновь введенные зависимости (например, с mvn dependency:tree ). Такой подход немедленно доведет дело до их сведения.

Это хорошо работает в нашей организации. До его введения мы жили в том же аду, который описывает ОП.

Даниил
источник
Должен ли я буквально использовать точку-точку-точку внутри элемента версии, или мне нужно использовать реальную версию, например 2.6.2?
chrisinmtown
3
@chrisinmtown Настоящая версия.
Даниэль
6

Каждый проект maven должен прекратиться в зависимости от xerces, вероятно, нет. XML API и Impl были частью Java с 1.4. Нет необходимости зависеть от xerces или XML API, все равно что сказать, что вы зависите от Java или Swing. Это неявно.

Если бы я был боссом репозитория Maven, я бы написал скрипт для рекурсивного удаления зависимостей xerces и написал read me, который говорит, что для этого репо требуется Java 1.4.

Все, что действительно ломается, потому что оно ссылается на Xerces напрямую через импорт org.apache, нуждается в исправлении кода, чтобы довести его до уровня Java 1.4 (и сделал это с 2002 года), или решение на уровне JVM с помощью одобренных библиотек, а не в maven.

teknopaul
источник
При выполнении подробного рефакторинга вам также необходимо искать имена пакетов и классов в тексте ваших файлов Java и конфигурации. Вы обнаружите, что разработчики поместили FQN классов Impl в константные строки, которые используются Class.forName и аналогичными конструкциями.
Дерек Беннетт
Это предполагает, что все реализации SAX делают одно и то же, что не соответствует действительности. библиотека xercesImpl позволяет использовать параметры конфигурации, которых нет в библиотеках java.xml.parser.
Амальговинус
6

Вы должны сначала отладить, чтобы помочь определить свой уровень ада XML. На мой взгляд, первым шагом является добавление

-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

в командной строке. Если это работает, тогда начните исключать библиотеки. Если нет, то добавьте

-Djaxp.debug=1

в командной строке.

Дерек Беннетт
источник
2

Что поможет, кроме исключения, это модульные зависимости.

С одной плоской загрузкой классов (автономное приложение) или полу-иерархической (JBoss AS / EAP 5.x) это было проблемой.

Но с модульными структурами, такими как модули OSGi и JBoss , это больше не такая большая боль. Библиотеки могут использовать любую библиотеку, какую захотят, независимо друг от друга.

Конечно, все же наиболее рекомендуется придерживаться только одной реализации и версии, но если нет другого пути (использование дополнительных функций из большего количества библиотек), тогда модульность может спасти вас.

Хорошим примером модулей JBoss в действии, разумеется, является JBoss AS 7 / EAP 6 / WildFly 8 , для которого он был изначально разработан.

Пример определения модуля:

<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="org.jboss.msc">
    <main-class name="org.jboss.msc.Version"/>
    <properties>
        <property name="my.property" value="foo"/>
    </properties>
    <resources>
        <resource-root path="jboss-msc-1.0.1.GA.jar"/>
    </resources>
    <dependencies>
        <module name="javax.api"/>
        <module name="org.jboss.logging"/>
        <module name="org.jboss.modules"/>
        <!-- Optional deps -->
        <module name="javax.inject.api" optional="true"/>
        <module name="org.jboss.threads" optional="true"/>
    </dependencies>
</module>

По сравнению с OSGi, модули JBoss проще и быстрее. Несмотря на то, что некоторые функции отсутствуют, этого достаточно для большинства проектов, которые (в основном) находятся под управлением одного поставщика и позволяют выполнять потрясающую быструю загрузку (из-за разрешения парализованных зависимостей).

Обратите внимание, что в Java 8 ведется работа по модульности , но AFAIK предназначен прежде всего для модульности самой JRE, но не уверен, будет ли она применима к приложениям.

Ондра Жижка
источник
jboss modules - это статическая модульность. OSGi не имеет ничего общего с модульной средой исполнения - я бы сказал, что они дополняют друг друга. Это хорошая система, хотя.
Eis
* дополнение вместо комплимента
Роберт Майкс
2

По-видимому xerces:xml-apis:1.4.01, больше не в Maven Central, который, однако, чтоxerces:xercesImpl:2.11.0 ссылки.

Это работает для меня:

<dependency>
  <groupId>xerces</groupId>
  <artifactId>xercesImpl</artifactId>
  <version>2.11.0</version>
  <exclusions>
    <exclusion>
      <groupId>xerces</groupId>
      <artifactId>xml-apis</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<dependency>
  <groupId>xml-apis</groupId>
  <artifactId>xml-apis</artifactId>
  <version>1.4.01</version>
</dependency>
thrau
источник
1

Мой друг, это очень просто, вот пример:

<dependency>
    <groupId>xalan</groupId>
    <artifactId>xalan</artifactId>
    <version>2.7.2</version>
    <scope>${my-scope}</scope>
    <exclusions>
        <exclusion>
        <groupId>xml-apis</groupId>
        <artifactId>xml-apis</artifactId>
    </exclusion>
</dependency>

И если вы хотите проверить в терминале (в этом примере консоль Windows), что у вашего maven-дерева нет проблем:

mvn dependency:tree -Dverbose | grep --color=always '(.* conflict\|^' | less -r
Эдуардо
источник