Radzenie sobie z „Xerces hell” w Javie / Maven?

732

W moim biurze sama wzmianka o słowie Xerces wystarczy, aby wywołać morderczą furię ze strony deweloperów. Pobieżne spojrzenie na inne pytania Xerces dotyczące SO wydaje się wskazywać, że prawie wszyscy użytkownicy Maven są „dotknięci” tym problemem w pewnym momencie. Niestety zrozumienie problemu wymaga trochę wiedzy na temat historii Xerces ...

Historia

  • Xerces to najczęściej używany parser XML w ekosystemie Java. Prawie każda biblioteka lub środowisko napisane w Javie wykorzystuje Xerces w pewnym stopniu (przejściowo, jeśli nie bezpośrednio).

  • Słoiki Xerces zawarte w oficjalnych plikach binarnych nie są do tej pory wersjonowane. Na przykład jar implementacji Xerces 2.11.0 ma nazwę, xercesImpl.jara nie nazwę xercesImpl-2.11.0.jar.

  • Zespół Xerces nie korzysta z Maven , co oznacza, że ​​nie przesyłają oficjalnej wersji do Maven Central .

  • Xerces był kiedyś wydawany jako pojedynczy jar ( xerces.jar), ale został podzielony na dwa słoiki, jeden zawierający API ( xml-apis.jar) i drugi zawierający implementacje tych API ( xercesImpl.jar). Wiele starszych POM Maven nadal deklaruje zależność xerces.jar. W pewnym momencie w przeszłości wydano także Xerces as xmlParserAPIs.jar, od którego zależą również niektóre starsze POM.

  • Wersje przypisane do słoików xml-apis i xercesImpl przez tych, którzy wdrażają swoje słoiki w repozytoriach Maven, są często różne. Na przykład xml-apis może otrzymać wersję 1.3.03, a xercesImpl może otrzymać wersję 2.8.0, mimo że oba pochodzą z Xerces 2.8.0. Wynika to z faktu, że ludzie często oznaczają słoik xml-apis wersją specyfikacji, które implementuje. Jest bardzo ładny, ale niepełny podział ten tutaj .

  • Aby komplikować sprawy, Xerces to parser XML używany w referencyjnej implementacji Java API for XML Processing (JAXP), zawartej w JRE. Klasy implementacji są ponownie pakowane w com.sun.*przestrzeni nazw, co sprawia, że ​​dostęp do nich jest niebezpieczny, ponieważ mogą nie być dostępne w niektórych środowiskach JRE. Jednak nie wszystkie funkcje Xerces są udostępniane za pośrednictwem interfejsów API java.*i javax.*; na przykład nie ma interfejsu API, który ujawniałby serializację Xerces.

  • Dodając do mylącego bałaganu, prawie wszystkie pojemniki serwletów (JBoss, Jetty, Glassfish, Tomcat itp.) Są dostarczane z Xerces w jednym lub kilku /libfolderach.

Problemy

Rozwiązanie konfliktu

Z niektórych - a może wszystkich - z powyższych powodów, wiele organizacji publikuje i konsumuje niestandardowe wersje Xerces w swoich POM. Nie jest to tak naprawdę problemem, jeśli masz małą aplikację i używasz tylko Maven Central, ale szybko staje się to problemem dla oprogramowania korporacyjnego, w którym Artifactory lub Nexus pośredniczy w wielu repozytoriach (JBoss, Hibernacja itp.):

xml-apis pośredniczony przez Artifactory

Na przykład organizacja A może publikować xml-apisjako:

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

Tymczasem organizacja B może opublikować to samo, jarco:

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

Chociaż B's jarjest niższą wersją niż A jar, Maven nie wie, że są tym samym artefaktem, ponieważ mają różne groupIds. W związku z tym nie może wykonać rozwiązania konfliktu i oba jarzostaną uwzględnione jako rozwiązane zależności:

rozwiązane zależności z wieloma xml-apis

Classloader Hell

Jak wspomniano powyżej, środowisko JRE jest dostarczane z Xerces w JAXP RI. Przydałoby się zaznaczyć wszystkie zależności Xerces Maven jako <exclusion>s lub as<provided>, kod innej firmy, od którego zależy, może, ale nie musi, współpracować z wersją podaną w JAXP używanego JDK. Ponadto masz słoiki Xerces wysłane w pojemniku serwletu, z którymi możesz się zmagać. To pozostawia wiele możliwości: Czy usuwasz wersję serwletu i masz nadzieję, że Twój kontener działa w wersji JAXP? Czy lepiej opuścić wersję serwletu i mieć nadzieję, że ramy aplikacji będą działać w wersji serwletu? Jeśli jeden lub dwa z opisanych powyżej nierozwiązanych konfliktów zdołają wślizgnąć się do twojego produktu (łatwo zdarzyć się w dużej organizacji), szybko znajdujesz się w piekle Classloadera, zastanawiając się, którą wersję Xerces wybiera moduł ładujący w czasie wykonywania i czy nie wybierze ten sam słoik w systemie Windows i Linux (prawdopodobnie nie).

Rozwiązania?

Staraliśmy oznakowanie wszystkich zależności Xerces Maven jako <provided>lub jako <exclusion>, ale to jest trudne do wyegzekwowania (zwłaszcza z dużym zespołem), zważywszy, że artefakty mają tak wiele aliasów ( xml-apis, xerces, xercesImpl, xmlParserAPIs, itd.) Ponadto nasze biblioteki / frameworki innych firm mogą nie działać w wersji JAXP lub wersji dostarczanej przez kontener serwletu.

Jak najlepiej rozwiązać ten problem za pomocą Maven? Czy musimy sprawować tak drobiazgową kontrolę nad naszymi zależnościami, a następnie polegać na wielopoziomowym obciążeniu klas? Czy jest jakiś sposób, aby globalnie wykluczyć wszystkie zależności Xerces i zmusić wszystkie nasze frameworki / biblioteki do korzystania z wersji JAXP?


AKTUALIZACJA : Joshua Spiewak przesłał poprawioną wersję skryptów kompilacji Xerces do XERCESJ-1454, która pozwala na przesłanie do Maven Central. Głosuj / oglądaj / przyczyniaj się do tego problemu i naprawmy ten problem raz na zawsze.

Justin Garrick
źródło
8
Dziękuję za to szczegółowe pytanie. Nie rozumiem motywacji zespołu Xerces. Wyobrażam sobie, że są dumni z tego produktu i czerpią przyjemność z korzystania z niego, ale obecny stan kłamstw i raju jest niesławny. Mimo to mogą robić, co chcą, nawet jeśli nie ma to dla mnie sensu. Zastanawiam się, czy chłopaki z sonatype mają jakieś sugestie.
Travis Schneeberger,
35
To może nie na temat, ale jest to prawdopodobnie lepszy post, jaki kiedykolwiek widziałem. Bardziej związane z pytaniem, co opisujesz, jest jednym z najbardziej bolesnych problemów, jakie możemy napotkać. Świetna inicjatywa!
Jean-Rémy Revy
2
@TravisSchneeberger Duża część złożoności wynika z faktu, że firma Sun zdecydowała się na użycie Xerces w samym środowisku JRE. Nie można za to winić ludzi Xerces.
Thorbjørn Ravn Andersen
Zwykle staramy się znaleźć wersję Xerces, która spełnia wszystkie zależne biblioteki metodą prób i błędów, jeśli nie jest to możliwe, przejdź do WAR, aby podzielić aplikację na osobne WAR (osobne moduły ładujące). To narzędzie (napisałem to) pomaga zrozumieć, co dzieje się na jhades.org , pozwalając na zapytanie o ścieżkę klasy dla słoików i klas - działa również w przypadku, gdy serwer jeszcze się nie uruchamia
Angular University
Tylko krótki komentarz, jeśli pojawia się ten błąd podczas uruchamiania servicemix z git bash w Windows: zamiast tego uruchom go z „normalnego” cmd.
Albert Hendriks

Odpowiedzi:

112

Od 20 lutego 2013 r. W Maven Central dostępnych jest 2.11.0 plików JAR (i źródłowe pliki JAR !) Xerces! Zobacz Xerces w Maven Central . Zastanawiam się, dlaczego nie rozwiązali https://issues.apache.org/jira/browse/XERCESJ-1454 ...

Użyłem:

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

i wszystkie zależności rozwiązały się dobrze - nawet poprawnie xml-apis-1.4.01!

I co najważniejsze (a co nie było oczywiste w przeszłości) - JAR w Maven Central jest tym samym JAR, co w oficjalnej Xerces-J-bin.2.11.0.zipdystrybucji .

Nie mogłem jednak znaleźć xml-schema-1.1-betawersji - nie może to być classifierwersja Maven z powodu dodatkowych zależności.

Grzegorz Grzybek
źródło
9
Chociaż jest to bardzo mylące, że xml-apis:xml-apis:1.4.01jest nowszy niż xml-apis:xml-apis:2.0.2? patrz search.maven.org/…
Hendy Irawan
Jest to mylące, ale wynika to z przesłania przez strony trzecie słoików Xerces nie wersjonowanych, jak powiedział justingarrik w swoim poście. xml-apis 2.9.1 jest taki sam jak 1.3.04, więc w tym sensie 1.4.01 jest nowszy (i liczbowo większy) niż 1.3.04.
liltitus27,
1
Jeśli w pom.xml masz zarówno xercesImpl, jak i xml-apis, pamiętaj o usunięciu zależności xml-apis! W przeciwnym razie 2.0.2 wyrzuca swoją brzydką głowę.
MikeJRamsey56
64

Szczerze mówiąc, prawie wszystko, co mamy napotkał działa dobrze w / w wersji JAXP, więc my zawsze wyłączyć xml-apis i xercesImpl.

jtahlborn
źródło
13
Czy możesz dodać do tego fragment pom.xml?
chzbrgla,
10
Gdy próbuję tego, JavaMelody i Spring rzucają java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversalw czasie wykonywania.
David Moles
Aby dodać odpowiedź Davida Molesa - widziałem, że pół tuzina zależności przechodnich potrzebuje ElementTraversal. Najczęściej różne rzeczy w Spring i Hadoop.
Scott Carey
2
Jeśli otrzymasz java.lang.NoClassDefFoundError: org / w3c / dom / ElementTraversal, spróbuj dodać xml-apis 1.4.01 do swojej pom (i wyklucz wszystkie inne wersje zależne)
Justin Rowe
1
ElementTraversal to nowa klasa dodana w Xerces 11 i dostępna w zależności xml-apis: xml-apis: 1.4.01. Może być więc konieczne ręczne skopiowanie klasy do projektu lub użycie całej zależności, która powoduje zduplikowane klasy w module ładującym klasy. Ale w JDK9 ta klasa została uwzględniona, więc w funkcji może być konieczne usunięcie dep.
Sergey Ponomarev,
42

Możesz użyć wtyczki maven enforcer z zakazaną regułą zależności. Pozwoliłoby to zablokować wszystkie aliasy, których nie chcesz, i zezwolić tylko na ten, którego chcesz. Reguły te zawiodą kompilację maven twojego projektu, gdy zostaną naruszone. Ponadto, jeśli ta reguła dotyczy wszystkich projektów w przedsiębiorstwie, można umieścić konfigurację wtyczki w korporacyjnym pom-pom.

widzieć:

Travis Schneeberger
źródło
33

Wiem, że to nie odpowiada dokładnie na pytanie, ale dla ppl przychodzących z Google, które akurat używają Gradle do zarządzania zależnościami:

Udało mi się pozbyć wszystkich problemów Xerces / Java8 z Gradle w następujący sposób:

configurations {
    all*.exclude group: 'xml-apis'
    all*.exclude group: 'xerces'
}
Netmikey
źródło
36
fajnie, z maven potrzebujesz około 4000 linii XML, aby to zrobić.
teknopaul
to nie rozwiązało problemu. jakieś inne wskazówki dla osób z Androidem Gradle?
nyxee
2
@teknopaul XML służy wyłącznie do konfiguracji. Groovy to język programowania wysokiego poziomu. Czasami możesz chcieć użyć XML dla jego jawności zamiast groovy dla jego magii.
Dragas,
16

Sądzę, że musisz odpowiedzieć na jedno pytanie:

Czy istnieje xerces * .jar, z którym wszystko w twojej aplikacji może żyć?

Jeśli nie, jesteś po prostu wkręcony i musiałbyś użyć czegoś takiego jak OSGI, co pozwala na jednoczesne ładowanie różnych wersji biblioteki. Ostrzegamy, że w zasadzie zastępuje problemy z wersją jar problemami z modułem ładującym klasy ...

Jeśli istnieje taka wersja, możesz sprawić, że repozytorium zwróci tę wersję dla wszystkich rodzajów zależności. Jest to brzydki hack i skończyłby się z tą samą implementacją xerces w ścieżce klasy wiele razy, ale lepiej niż posiadanie wielu różnych wersji xerces.

Możesz wykluczyć każdą zależność od kserokopii i dodać ją do wersji, której chcesz użyć.

Zastanawiam się, czy możesz napisać strategię rozwiązywania wersji jako wtyczkę do maven. To prawdopodobnie najpiękniejsze rozwiązanie, ale jeśli w ogóle wykonalne wymaga pewnych badań i kodowania.

W przypadku wersji zawartej w środowisku wykonawczym musisz upewnić się, że zostanie ona usunięta ze ścieżki klas aplikacji lub słoiki aplikacji zostaną wzięte pod uwagę jako pierwsze podczas ładowania klas, zanim folder lib serwera zostanie wzięty pod uwagę.

Podsumowując: bałagan i to się nie zmieni.

Jens Schauder
źródło
1
Ta sama klasa z tego samego słoika załadowanego przez różne ClassLoaders jest nadal ClassCastException (we wszystkich standardowych kontenerach)
Ajax
3
Dokładnie. Dlatego napisałem: Pamiętaj, że to w zasadzie zastępuje problemy Wersja słoik z kwestiami ClassLoader
Jens Schauder
7

Jest jeszcze jedna opcja, która nie została tutaj zbadana: zadeklarowanie zależności Xerces w Maven jako opcjonalne :

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

Zasadniczo polega to na zmuszeniu wszystkich osób zależnych do zadeklarowania swojej wersji Xerces, w przeciwnym razie ich projekt nie zostanie skompilowany. Jeśli chcą zastąpić tę zależność, mogą to zrobić, ale będą właścicielami potencjalnego problemu.

Stwarza to silną zachętę dla projektów końcowych do:

  • Podejmij aktywną decyzję. Czy używają tej samej wersji Xerces, czy używają czegoś innego?
  • Właściwie przetestuj ich parsowanie (np. Poprzez testy jednostkowe) i ładowanie klas, a także nie zaśmiecaj ich ścieżki klas.

Nie wszyscy programiści śledzą nowo wprowadzone zależności (np. Z mvn dependency:tree). Takie podejście natychmiast zwróci ich uwagę.

Działa całkiem dobrze w naszej organizacji. Przed jego wprowadzeniem mieszkaliśmy w tym samym piekle, które opisuje OP.

Daniel
źródło
Czy dosłownie powinienem używać kropka-kropka-kropka w elemencie wersji, czy też muszę używać prawdziwej wersji, takiej jak 2.6.2?
chrisinmtown
3
@chrisinmtown Prawdziwa wersja.
Daniel
6

Każdy projekt maven powinien zakończyć się w zależności od kserokopii, prawdopodobnie tak naprawdę nie jest. Interfejsy API XML i Impl są częścią Java od 1.4. Nie ma potrzeby polegać na xerces lub interfejsach API XML, to tak, jakby powiedzieć, że zależy się od Java lub Swing. To jest dorozumiane.

Gdybym był szefem repozytorium maven, napisałbym skrypt, aby rekurencyjnie usuwać zależności xerces i napisał mi przeczytaną informację, że to repo wymaga Java 1.4.

Wszystko, co faktycznie psuje się, ponieważ odwołuje się do Xerces bezpośrednio za pośrednictwem importu org.apache, wymaga poprawki kodu, aby doprowadzić ją do poziomu Java 1.4 (i zrobiło to od 2002 r.) Lub rozwiązania na poziomie JVM poprzez zatwierdzone biblioteki lib, nie w maven.

teknopaul
źródło
Podczas przeprowadzania szczegółowego refaktora należy również wyszukać nazwy pakietów i klas w tekście plików Java i konfiguracji. Przekonasz się, że programiści umieścili nazwę FQN klas Impl w ciągach, które są używane przez Class.forName i podobne konstrukcje.
Derek Bennett
Zakłada się, że wszystkie implementacje SAX robią to samo, co nie jest prawdą. biblioteka xercesImpl pozwala na opcje konfiguracji, których brakuje w bibliotekach java.xml.parser.
Amalgovinus
6

Najpierw powinieneś debugować, aby zidentyfikować swój poziom piekła XML. Moim zdaniem pierwszym krokiem jest dodanie

-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

do wiersza poleceń. Jeśli to zadziała, zacznij wykluczać biblioteki. Jeśli nie, dodaj

-Djaxp.debug=1

do wiersza poleceń.

Derek Bennett
źródło
2

Pomogłoby, oprócz wykluczenia, zależności modułowe.

Z jednym płaskim ładowaniem klas (samodzielna aplikacja) lub półhierarchicznym (JBoss AS / EAP 5.x) był to problem.

Ale dzięki modułowym frameworkom, takim jak OSGi i JBoss Modules , nie jest to już tak bardzo bolesne. Biblioteki mogą korzystać z dowolnej biblioteki niezależnie.

Oczywiście nadal najlepiej jest trzymać się tylko jednej implementacji i wersji, ale jeśli nie ma innego sposobu (użycie dodatkowych funkcji z większej liczby bibliotek), modularyzacja może cię uratować.

Dobrym przykładem działających modułów JBoss jest oczywiście JBoss AS 7 / EAP 6 / WildFly 8 , dla którego został pierwotnie opracowany.

Przykładowa definicja modułu:

<?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>

W porównaniu z OSGi, moduły JBoss są prostsze i szybsze. Brakuje niektórych funkcji, ale wystarcza dla większości projektów, które (głównie) są kontrolowane przez jednego dostawcę i umożliwiają oszałamiające szybkie uruchamianie (ze względu na rozwiązywanie równoległych zależności).

Zauważ, że trwają prace nad modularyzacją Java 8 , ale AFAIK ma przede wszystkim na celu modularyzację samego środowiska JRE, nie jestem pewien, czy będzie on miał zastosowanie do aplikacji.

Ondra Žižka
źródło
Moduły jboss dotyczą modularyzacji statycznej. Ma niewiele wspólnego z modularyzacją środowiska uruchomieniowego, które OSGi ma do zaoferowania - powiedziałbym, że wzajemnie się uzupełniają. To jednak niezły system.
eis
* uzupełnienie zamiast komplementu
Robert Mikes,
2

Najwyraźniej xerces:xml-apis:1.4.01nie jest już w centrach maven, co jednak jest xerces:xercesImpl:2.11.0odniesieniem.

To działa dla mnie:

<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
źródło
1

Mój przyjacielu, to bardzo proste, oto przykład:

<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>

A jeśli chcesz sprawdzić w terminalu (w tym przykładzie konsolę Windows), czy twoje drzewo maven nie ma problemów:

mvn dependency:tree -Dverbose | grep --color=always '(.* conflict\|^' | less -r
Eduardo
źródło