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

614

В моем офисе простого упоминания слова 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. Это связано с тем, что люди часто помечают jar xml-apis версией спецификаций, которые он реализует. Существует очень хороший, но неполный пробой этого здесь.

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

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

Проблемы

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

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

Изображение 47

Например, организация A может опубликовать 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 не знает, что это один и тот же артефакт, поскольку у них разные groupId. Таким образом, он не может выполнить разрешение конфликта, и оба jar будут включены как разрешенные зависимости:

Изображение 48

Classloader Hell

Как упоминалось выше, JRE поставляется с Xerces в JAXP RI. Хотя было бы неплохо пометить все зависимости Xerces Maven как <exclusion> или как <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. Проголосуйте/посмотрите/внесите свой вклад в решение этой проблемы и дайте решить эту проблему раз и навсегда.

  • 7
    Спасибо за этот подробный вопрос. Я не понимаю мотивацию команды xerces. Я предположил бы, что они гордятся этим продуктом и получают удовольствие от других, используя его, но текущее состояние xerces и maven позорное. Тем не менее, они могут делать то, что они хотят, даже если это не имеет смысла для меня. Интересно, есть ли какие-нибудь предложения у парней с сонатыми?
  • 27
    Это может быть не по теме, но это, вероятно, лучший пост, который я когда-либо видел. Более того, вопрос, который вы описываете, является одной из самых болезненных проблем, с которыми мы можем столкнуться. Отличная инициатива!
Показать ещё 5 комментариев
Теги:
maven
dependency-management
classloader
xerces

11 ответов

102

С 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 - даже правильный xml-apis-1.4.01 !

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

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

  • 9
    Хотя очень странно, что xml-apis:xml-apis:1.4.01 новее, чем xml-apis:xml-apis:2.0.2 ?? см. search.maven.org/…
  • 0
    Это сбивает с толку, но это происходит из-за сторонних загрузок не версированных банок Xerces, как говорил в своем посте justingarrik. xml-apis 2.9.1 аналогичен 1.3.04, поэтому в этом смысле 1.4.01 новее (и численно больше), чем 1.3.04.
Показать ещё 2 комментария
56

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

  • 14
    +1: код для интерфейсов, а не для реализаций.
  • 13
    Не могли бы вы добавить для этого фрагмент pom.xml?
Показать ещё 5 комментариев
43

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

см

24

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

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

configurations {
    all*.exclude group: 'xml-apis'
    all*.exclude group: 'xerces'
}
  • 25
    хорошо, с Maven вам нужно около 4000 строк XML для этого.
  • 0
    это не решило проблему. какие-нибудь другие советы для людей Android-Gradle?
Показать ещё 1 комментарий
16

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

Есть ли xerces *.jar, что все в вашем приложении может жить?

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

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

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

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

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

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

  • 1
    Один и тот же класс из одного и того же фляги, загруженного различными ClassLoaders, все еще является ClassCastException (во всех стандартных контейнерах)
  • 3
    Именно так. Вот почему я написал: имейте в виду, что он в основном заменяет проблемы версии jar проблемами загрузчика классов
6

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

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

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

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

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

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

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

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

Сначала вы должны отлаживать, чтобы помочь определить ваш уровень адского 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

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

  • 0
    Спасибо, это решило мою проблему с несколькими файлами xercesImpl (вне моего контроля).
2

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

<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>
            </exclusions>
        </dependency>

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

mvn dependency:tree -Dverbose | grep --color=always '(.* conflict\|^' | less -r
2

По-видимому, xerces:xml-apis:1.4.01 больше не находится в центральной точке, что, однако, означает 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>
2

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

Если бы я был боссом maven repo, я бы написал script, чтобы рекурсивно удалить зависимости xerces и написать чтение, в котором говорится, что для этого репо требуется Java 1.4.

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

  • 0
    При выполнении подробного рефакторинга вам также необходимо искать имена пакетов и классов в тексте ваших файлов Java и конфигурации. Вы обнаружите, что разработчики поместили FQN классов Impl в константные строки, которые используются Class.forName и аналогичными конструкциями.
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, не уверен, будет ли она применима для приложений.

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню