Berurusan dengan "Xerces hell" di Java / Maven?

732

Di kantor saya, hanya menyebutkan kata Xerces sudah cukup untuk memicu kemarahan mematikan dari pengembang. Pandangan sekilas pada pertanyaan Xerces lainnya pada SO tampaknya menunjukkan bahwa hampir semua pengguna Maven "tersentuh" ​​oleh masalah ini di beberapa titik. Sayangnya, memahami masalah ini membutuhkan sedikit pengetahuan tentang sejarah Xerces ...

Sejarah

  • Xerces adalah parser XML yang paling banyak digunakan di ekosistem Java. Hampir setiap pustaka atau framework yang ditulis dalam Java menggunakan Xerces dalam kapasitas tertentu (secara transitif, jika tidak secara langsung).

  • Guci Xerces yang termasuk dalam binari resmi , sampai hari ini, tidak diversi. Misalnya, topler implementasi Xerces 2.11.0 dinamai xercesImpl.jardan tidak xercesImpl-2.11.0.jar.

  • Tim Xerces tidak menggunakan Maven , yang berarti mereka tidak mengunggah rilis resmi ke Maven Central .

  • Xerces dulu dirilis sebagai toples tunggal ( xerces.jar), tetapi dibagi menjadi dua toples, satu berisi API ( xml-apis.jar) dan satu berisi implementasi API tersebut ( xercesImpl.jar). Banyak POM Maven yang lebih tua masih menyatakan ketergantungan xerces.jar. Di beberapa titik di masa lalu, Xerces juga dirilis sebagai xmlParserAPIs.jar, yang tergantung pada beberapa POM lama.

  • Versi yang ditugaskan untuk guci xml-apis dan xercesImpl oleh mereka yang menggunakan guci mereka ke repositori Maven seringkali berbeda. Misalnya, xml-apis mungkin diberikan versi 1.3.03 dan xercesImpl mungkin diberikan versi 2.8.0, meskipun keduanya berasal dari Xerces 2.8.0. Ini karena orang sering menandai tabung xml-apis dengan versi spesifikasi yang diterapkannya. Ada gangguan yang sangat bagus, tetapi tidak lengkap di sini .

  • Untuk memperumit masalah, Xerces adalah parser XML yang digunakan dalam implementasi referensi API Java untuk Pemrosesan XML (JAXP), termasuk dalam JRE. Kelas implementasi dipaket ulang di bawah com.sun.*namespace, yang membuatnya berbahaya untuk mengaksesnya secara langsung, karena mereka mungkin tidak tersedia di beberapa JRE. Namun, tidak semua fungsionalitas Xerces diekspos melalui API java.*dan javax.*; misalnya, tidak ada API yang memaparkan serialisasi Xerces.

  • Menambah kekacauan yang membingungkan, hampir semua kontainer servlet (JBoss, Jetty, Glassfish, Tomcat, dll.), Dikirim bersama Xerces di satu atau lebih /libfolder mereka .

Masalah

Resolusi konflik

Untuk beberapa - atau mungkin semua - alasan di atas, banyak organisasi mempublikasikan dan mengonsumsi build kustom Xerces di POM mereka. Ini tidak benar-benar masalah jika Anda memiliki aplikasi kecil dan hanya menggunakan Maven Central, tetapi dengan cepat menjadi masalah bagi perangkat lunak perusahaan di mana Artifactory atau Nexus mem-proxy beberapa repositori (JBoss, Hibernate, dll.):

xml-apis diproksi oleh Artifactory

Misalnya, organisasi A dapat menerbitkan xml-apissebagai:

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

Sementara itu, organisasi B mungkin menerbitkan yang sama jardengan:

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

Meskipun B jaradalah versi yang lebih rendah dari A jar, Maven tidak tahu bahwa mereka adalah artefak yang sama karena mereka memiliki groupIds yang berbeda . Dengan demikian, tidak dapat melakukan resolusi konflik dan keduanya jarakan dimasukkan sebagai dependensi yang diselesaikan:

dependensi teratasi dengan multiple xml-apis

Neraka Classloader

Seperti disebutkan di atas, JRE dikirimkan bersama Xerces di JAXP RI. Meskipun akan menyenangkan untuk menandai semua dependensi Xerces Maven sebagai <exclusion>s atau sebagai<provided>, kode pihak ketiga yang Anda andalkan mungkin atau mungkin tidak berfungsi dengan versi yang disediakan di JAXP JDK yang Anda gunakan. Selain itu, Anda memiliki botol-botol Xerces yang dikirimkan dalam wadah servlet untuk bersaing. Ini memberi Anda sejumlah pilihan: Apakah Anda menghapus versi servlet dan berharap bahwa wadah Anda berjalan pada versi JAXP? Apakah lebih baik meninggalkan versi servlet, dan berharap bahwa kerangka kerja aplikasi Anda berjalan pada versi servlet? Jika satu atau dua konflik yang tidak terselesaikan yang diuraikan di atas berhasil masuk ke produk Anda (mudah terjadi di organisasi besar), Anda dengan cepat menemukan diri Anda di neraka classloader, bertanya-tanya versi Xerces mana yang diambil classloader saat runtime dan apakah itu akan memilih tabung yang sama di Windows dan Linux (mungkin tidak).

Solusi?

Kami sudah mencoba menandai semua dependensi Xerces Maven sebagai <provided>atau sebagai <exclusion>, tapi ini sulit untuk menegakkan (terutama dengan tim besar) mengingat bahwa artefak memiliki begitu banyak alias ( xml-apis, xerces, xercesImpl, xmlParserAPIs, dll). Selain itu, libs / kerangka kerja pihak ketiga kami tidak dapat berjalan pada versi JAXP atau versi yang disediakan oleh wadah servlet.

Bagaimana kita bisa mengatasi masalah ini dengan Maven? Apakah kita harus melakukan kontrol yang halus atas ketergantungan kita, dan kemudian bergantung pada pemuatan kelas berjenjang? Apakah ada cara untuk secara global mengecualikan semua dependensi Xerces, dan memaksa semua kerangka / lib kita untuk menggunakan versi JAXP?


PEMBARUAN : Joshua Spiewak telah mengunggah versi tambalan Xerces build skrip ke XERCESJ-1454 yang memungkinkan untuk diunggah ke Maven Central. Pilih / tonton / berkontribusi untuk masalah ini dan mari kita selesaikan masalah ini untuk selamanya.

Justin Garrick
sumber
8
Terima kasih atas pertanyaan terperinci ini. Saya tidak mengerti motivasi tim xerces. Saya akan membayangkan mereka bangga dengan produk di sana dan menikmati yang lain menggunakannya tetapi keadaan saat ini xerces dan maven memalukan. Meski begitu, mereka dapat melakukan apa yang mereka inginkan walaupun itu tidak masuk akal bagi saya. Aku ingin tahu apakah sonatype guys punya saran.
Travis Schneeberger
35
Ini mungkin di luar topik, tapi ini mungkin posting yang lebih baik yang pernah saya lihat. Lebih terkait dengan pertanyaan, apa yang Anda gambarkan adalah salah satu masalah paling menyakitkan yang bisa kita temui. Inisiatif hebat!
Jean-Rémy Revy
2
@ TravisSchneeberger Banyak kerumitannya adalah karena Sun memilih untuk menggunakan Xerces di JRE itu sendiri. Anda tidak dapat menyalahkan orang-orang Xerces untuk itu.
Thorbjørn Ravn Andersen
Biasanya kami mencoba untuk menemukan versi Xerces yang memuaskan semua pustaka tergantung secara coba-coba, jika tidak memungkinkan maka refactor ke WARs untuk membagi aplikasi menjadi WARs yang terpisah (loader kelas terpisah). Alat ini (saya menulisnya) membantu memahami apa yang sedang terjadi di jhades.org dengan memungkinkan untuk meminta classpath untuk guci, dan kelas - ini berfungsi juga dalam kasus ketika server belum mulai
Angular University
Hanya komentar cepat jika Anda mendapatkan kesalahan ini saat memulai servicemix dari git bash di windows: mulailah dari cmd "normal".
Albert Hendriks

Jawaban:

112

Ada 2.11.0 JAR (dan sumber JAR!) Dari Xerces di Maven Central sejak 20 Februari 2013! Lihat Xerces di Maven Central . Saya bertanya-tanya mengapa mereka belum menyelesaikan https://issues.apache.org/jira/browse/XERCESJ-1454 ...

Saya telah menggunakan:

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

dan semua dependensi telah diselesaikan dengan baik - bahkan layak xml-apis-1.4.01!

Dan apa yang paling penting (dan apa yang tidak jelas di masa lalu) - JAR di Maven Central adalah JAR yang sama dengan Xerces-J-bin.2.11.0.zipdistribusi resmi .

Namun saya tidak dapat menemukan xml-schema-1.1-betaversi - itu tidak bisa menjadi classifierversi Maven karena ketergantungan tambahan.

Grzegorz Grzybek
sumber
9
Meskipun itu sangat membingungkan bahwa xml-apis:xml-apis:1.4.01adalah lebih baru daripada xml-apis:xml-apis:2.0.2?? lihat search.maven.org/...
Hendy Irawan
Itu membingungkan, tapi itu karena unggahan pihak ketiga dari botol Xerces yang tidak berversi, seperti yang dikatakan justingarrik di posnya. xml-apis 2.9.1 sama dengan 1.3.04, jadi dalam arti itu, 1.4.01 lebih baru (dan secara numerik lebih besar) dari 1.3.04.
liltitus27
1
Jika Anda memiliki xercesImpl dan xml-apis di pom.xml Anda, pastikan untuk menghapus dependensi xml-apis! Kalau tidak, 2.0.2 memunculkan kepalanya yang jelek.
MikeJRamsey56
64

Terus terang, hampir semua yang kami temui berfungsi dengan baik dengan versi JAXP, jadi kami selalu mengecualikan xml-apis dan xercesImpl.

jtahlborn
sumber
13
Bisakah Anda menambahkan potongan pom.xml untuk itu?
chzbrgla
10
Ketika saya mencoba ini, saya mendapatkan JavaMelody dan Spring melempar java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversalsaat runtime.
David Moles
Untuk menambah respons David Moles - Saya telah melihat setengah lusin dependensi transitif perlu ElementTraversal. Berbagai hal di Spring dan Hadoop paling umum.
Scott Carey
2
Jika Anda mendapatkan java.lang.NoClassDefFoundError: org / w3c / dom / ElementTraversal, coba tambahkan xml-apis 1.4.01 ke pom Anda (dan singkirkan semua versi dependen lainnya)
Justin Rowe
1
ElementTraversal adalah kelas baru yang ditambahkan dalam Xerces 11 dan tersedia dalam xml-apis: xml-apis: dependensi 1.4.01. Jadi, Anda mungkin perlu menyalin kelas secara manual ke proyek Anda atau menggunakan seluruh ketergantungan yang menyebabkan duplikasi kelas di classloader. Tetapi dalam JDK9 kelas ini dimasukkan jadi dalam fitur Anda mungkin perlu menghapus dep.
Sergey Ponomarev
42

Anda bisa menggunakan plugin penegak pakar dengan aturan ketergantungan yang dilarang. Ini akan memungkinkan Anda untuk melarang semua alias yang tidak Anda inginkan dan hanya mengizinkan yang Anda inginkan. Aturan-aturan ini akan gagal membangun proyek Anda ketika dilanggar. Selain itu, jika aturan ini berlaku untuk semua proyek di perusahaan Anda bisa meletakkan konfigurasi plugin di pom induk perusahaan.

Lihat:

Travis Schneeberger
sumber
33

Saya tahu ini tidak menjawab pertanyaan dengan tepat, tetapi untuk ppl datang dari google yang kebetulan menggunakan Gradle untuk manajemen ketergantungan mereka:

Saya berhasil menyingkirkan semua masalah xerces / Java8 dengan Gradle seperti ini:

configurations {
    all*.exclude group: 'xml-apis'
    all*.exclude group: 'xerces'
}
netmikey
sumber
36
bagus, dengan pakar Anda membutuhkan sekitar 4000 baris XML untuk melakukan itu.
teknopaul
itu tidak menyelesaikan masalah. ada petunjuk lain untuk orang Android-Gradle?
nyxee
2
@teknopaul XML digunakan sepenuhnya untuk konfigurasi. Groovy adalah bahasa pemrograman tingkat tinggi. Kadang-kadang Anda mungkin ingin menggunakan XML untuk kesederhanaannya alih-alih asyik untuk keajaibannya.
Dragas
16

Saya kira ada satu pertanyaan yang perlu Anda jawab:

Apakah ada xerces * .jar bahwa semua yang ada di aplikasi Anda dapat hidup?

Jika tidak, Anda pada dasarnya kacau dan harus menggunakan sesuatu seperti OSGI, yang memungkinkan Anda untuk memiliki versi pustaka yang berbeda dimuat secara bersamaan. Berhati-hatilah karena itu pada dasarnya menggantikan masalah versi jar dengan masalah classloader ...

Jika ada versi seperti itu, Anda bisa membuat repositori mengembalikan versi itu untuk semua jenis dependensi. Ini adalah hack yang jelek dan akan berakhir dengan implementasi xerces yang sama di classpath Anda beberapa kali tetapi lebih baik daripada memiliki beberapa versi xerces yang berbeda.

Anda bisa mengecualikan setiap ketergantungan pada xerces dan menambahkan satu ke versi yang ingin Anda gunakan.

Saya ingin tahu apakah Anda dapat menulis semacam strategi resolusi versi sebagai plugin untuk pakar. Ini mungkin solusi terbaik, tetapi jika memungkinkan, perlu dilakukan penelitian dan pengkodean.

Untuk versi yang terdapat dalam lingkungan runtime Anda, Anda harus memastikan itu dihapus dari classpath aplikasi atau guci aplikasi dipertimbangkan terlebih dahulu untuk classloading sebelum folder lib server dipertimbangkan.

Jadi untuk menyelesaikannya: Ini berantakan dan itu tidak akan berubah.

Jens Schauder
sumber
1
Kelas yang sama dari tabung yang sama yang dimuat oleh ClassLoaders yang berbeda masih merupakan ClassCastException (di semua wadah standar)
Ajax
3
Persis. Itu sebabnya saya menulis: Berhati-hatilah karena itu pada dasarnya menggantikan masalah versi jar dengan masalah classloader
Jens Schauder
7

Ada opsi lain yang belum dieksplorasi di sini: menyatakan dependensi Xerces di Maven sebagai opsional :

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

Pada dasarnya apa yang dilakukan adalah untuk memaksa semua tanggungan untuk menyatakan mereka versi Xerces atau proyek mereka tidak akan dikompilasi. Jika mereka ingin mengesampingkan ketergantungan ini, mereka dipersilakan untuk melakukannya, tetapi kemudian mereka akan memiliki masalah potensial.

Ini menciptakan insentif yang kuat untuk proyek-proyek hilir untuk:

  • Buat keputusan aktif. Apakah mereka menggunakan Xerces versi yang sama atau menggunakan yang lain?
  • Sebenarnya menguji parsing mereka (misalnya melalui unit testing) dan classloading serta tidak mengacaukan classpath mereka.

Tidak semua pengembang melacak dependensi yang baru diperkenalkan (misalnya dengan mvn dependency:tree). Pendekatan ini akan segera membawa masalah ini menjadi perhatian mereka.

Ini bekerja dengan sangat baik di organisasi kami. Sebelum diperkenalkan, kami dulu hidup di neraka yang sama seperti yang digambarkan OP.

Daniel
sumber
Haruskah saya benar-benar menggunakan dot-dot-dot dalam elemen versi, atau apakah saya perlu menggunakan versi nyata seperti 2.6.2?
chrisinmtown
3
@chrisinmtown Versi asli.
Daniel
6

Setiap proyek pakar harus berhenti tergantung pada xerces, mereka mungkin tidak benar-benar. XML API dan Impl telah menjadi bagian dari Java sejak 1.4. Tidak perlu bergantung pada xerces atau XML API, seperti mengatakan Anda bergantung pada Java atau Swing. Ini implisit.

Jika saya adalah bos dari repo pakar, saya akan menulis skrip untuk menghapus dependensi xerces secara rekursif dan menulis membaca saya yang mengatakan repo ini membutuhkan Java 1.4.

Apa pun yang benar-benar rusak karena mereferensikan Xerces langsung melalui org.apache import memerlukan perbaikan kode untuk membawanya ke level Java 1.4 (dan telah dilakukan sejak tahun 2002) atau solusi pada level JVM melalui lib yang didukung, bukan pada pakar.

teknopaul
sumber
Saat melakukan refactor yang Anda perinci, Anda juga perlu mencari paket dan nama kelas dalam teks file Java Anda dan konfigurasi. Anda akan menemukan bahwa pengembang telah menempatkan FQN dari kelas Impl dalam string konstan yang digunakan oleh Class.forName dan konstruksi serupa.
Derek Bennett
Ini mengasumsikan semua implementasi SAX melakukan hal yang sama, yang tidak benar. pustaka xercesImpl memungkinkan opsi konfigurasi yang tidak dimiliki pustaka java.xml.parser.
Amalgovinus
6

Anda harus men-debug terlebih dahulu, untuk membantu mengidentifikasi tingkat neraka XML Anda. Menurut pendapat saya, langkah pertama adalah menambahkan

-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

ke baris perintah. Jika itu berhasil, maka mulailah mengecualikan perpustakaan. Jika tidak, tambahkan

-Djaxp.debug=1

ke baris perintah.

Derek Bennett
sumber
2

Apa yang akan membantu, kecuali untuk tidak termasuk, adalah dependensi modular.

Dengan satu flat classloading (aplikasi mandiri), atau semi-hierarkis (JBoss AS / EAP 5.x) ini merupakan masalah.

Tetapi dengan kerangka kerja modular seperti Modul OSGi dan JBoss , ini tidak begitu menyakitkan lagi. Perpustakaan dapat menggunakan perpustakaan mana pun yang mereka inginkan, secara mandiri.

Tentu saja, masih paling direkomendasikan untuk tetap menggunakan hanya satu implementasi dan versi, tetapi jika tidak ada cara lain (menggunakan fitur tambahan dari lebih banyak lib), maka modularisasi mungkin menyelamatkan Anda.

Contoh yang baik dari Modul JBoss yang sedang beraksi adalah, secara alami, JBoss AS 7 / EAP 6 / WildFly 8 , yang terutama dikembangkan.

Definisi modul contoh:

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

Dibandingkan dengan OSGi, Modul JBoss lebih sederhana dan lebih cepat. Meskipun kehilangan fitur-fitur tertentu, itu cukup untuk sebagian besar proyek yang (sebagian besar) di bawah kendali satu vendor, dan memungkinkan boot cepat yang menakjubkan (karena penyelesaian dependensi paralel).

Perhatikan bahwa ada upaya modularisasi yang sedang berjalan untuk Java 8 , tetapi AFAIK yang terutama untuk memodulasi JRE itu sendiri, tidak yakin apakah itu akan berlaku untuk aplikasi.

Ondra Žižka
sumber
Modul jboss adalah tentang modularisasi statis. Ini tidak ada hubungannya dengan modularisasi runtime OSGi yang ditawarkan - Saya akan mengatakan mereka saling memuji. Ini sistem yang bagus.
eis
* melengkapi bukan pujian
Robert Mikes
2

Rupanya xerces:xml-apis:1.4.01tidak lagi di pakar pusat, yang bagaimanapun xerces:xercesImpl:2.11.0referensi.

Ini bekerja untuk saya:

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

Teman saya itu sangat sederhana, berikut sebuah contoh:

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

Dan jika Anda ingin memeriksa di terminal (konsol windows untuk contoh ini) bahwa pohon pakar Anda tidak memiliki masalah:

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