Mengapa begitu sulit untuk melakukan ini di Jawa? Jika Anda ingin memiliki sistem modul apa pun, Anda harus dapat memuat file JAR secara dinamis. Saya diberitahu ada cara melakukannya dengan menulis sendiri ClassLoader
, tapi itu banyak pekerjaan untuk sesuatu yang seharusnya (setidaknya dalam pikiran saya) semudah memanggil metode dengan file JAR sebagai argumennya.
Ada saran untuk kode sederhana yang melakukan ini?
java
jar
classloader
Allain Lalonde
sumber
sumber
Jawaban:
Alasan sulitnya adalah keamanan. Classloader dimaksudkan untuk tidak berubah; Anda seharusnya tidak bisa mau menambahkan kelas untuk itu saat runtime. Saya sebenarnya sangat terkejut bekerja dengan sistem classloader. Inilah cara Anda melakukannya membuat classloader anak Anda sendiri:
Nyeri, tapi itu dia.
sumber
URLClassLoader child = new URLClassLoader (new URL[] {new URL("file://./my.jar")}, Main.class.getClassLoader());
asumsi bahwa file jar dipanggilmy.jar
dan terletak di direktori yang sama.Solusi berikut ini adalah retas, karena menggunakan refleksi untuk mem-bypass enkapsulasi, tetapi berfungsi dengan sempurna:
sumber
URLClassLoader.class.getDeclaredMethod("addURL", URL.class)
penggunaan refleksi secara ilegal, dan akan gagal di masa mendatang.Anda harus melihat OSGi , misalnya diimplementasikan di Platform Eclipse . Itu persis seperti itu. Anda dapat menginstal, mencopot, memulai dan menghentikan bundel yang disebut, yang secara efektif file JAR. Tapi itu sedikit lebih, karena ia menawarkan misalnya layanan yang dapat ditemukan secara dinamis dalam file JAR saat runtime.
Atau lihat spesifikasi untuk Sistem Modul Java .
sumber
Bagaimana dengan kerangka kerja loader kelas JCL ? Harus saya akui, saya belum menggunakannya, tapi kelihatannya menjanjikan.
Contoh penggunaan:
sumber
Ini adalah versi yang tidak ditinggalkan. Saya memodifikasi yang asli untuk menghapus fungsionalitas yang sudah usang.
sumber
Walaupun sebagian besar solusi yang tercantum di sini adalah peretasan (pra JDK 9) yang sulit dikonfigurasi (agen) atau tidak berfungsi lagi (posting JDK 9) Saya merasa sangat mengejutkan bahwa tidak ada yang menyebutkan metode yang terdokumentasi dengan jelas .
Anda dapat membuat pemuat kelas sistem khusus dan kemudian Anda bebas melakukan apa pun yang Anda inginkan. Tidak diperlukan refleksi dan semua kelas berbagi classloader yang sama.
Saat memulai JVM tambahkan flag ini:
Classloader harus memiliki konstruktor yang menerima classloader, yang harus ditetapkan sebagai induknya. Konstruktor akan dipanggil pada startup JVM dan classloader sistem nyata akan dilewati, kelas utama akan dimuat oleh loader kustom.
Untuk menambahkan toples cukup panggil
ClassLoader.getSystemClassLoader()
dan cor ke kelas Anda.Lihatlah implementasi ini untuk classloader yang dibuat dengan cermat. Harap dicatat, Anda dapat mengubah
add()
metode ini ke publik.sumber
Dengan Java 9 , jawaban dengan
URLClassLoader
sekarang memberikan kesalahan seperti:Ini karena pemuat kelas yang digunakan telah berubah. Sebagai gantinya, untuk menambahkan ke pemuat kelas sistem, Anda dapat menggunakan API Instrumentasi melalui agen.
Buat kelas agen:
Tambahkan META-INF / MANIFEST.MF dan letakkan di file JAR dengan kelas agen:
Jalankan agen:
Ini menggunakan perpustakaan byte-buddy-agent untuk menambahkan agen ke JVM yang berjalan:
sumber
Yang terbaik yang saya temukan adalah org.apache.xbean.classloader.JarFileClassLoader yang merupakan bagian dari proyek XBean .
Inilah metode singkat yang saya gunakan di masa lalu, untuk membuat pemuat kelas dari semua file lib di direktori tertentu
Kemudian untuk menggunakan classloader, cukup lakukan:
sumber
Jika Anda bekerja di Android, kode berikut berfungsi:
sumber
Berikut ini adalah solusi cepat untuk metode Allain agar kompatibel dengan versi Java yang lebih baru:
Perhatikan bahwa ini bergantung pada pengetahuan tentang implementasi internal JVM tertentu, jadi itu tidak ideal dan itu bukan solusi universal. Tapi ini solusi cepat dan mudah jika Anda tahu bahwa Anda akan menggunakan OpenJDK standar atau Oracle JVM. Mungkin juga rusak di beberapa titik di masa depan ketika versi JVM baru dirilis, jadi Anda perlu mengingatnya.
sumber
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make void jdk.internal.loader.ClassLoaders$AppClassLoader.appendToClassPathForInstrumentation(java.lang.String) accessible: module java.base does not "opens jdk.internal.loader" to unnamed module @18ef96
Solusi yang diusulkan oleh jodonnell bagus tetapi harus sedikit ditingkatkan. Saya menggunakan pos ini untuk mengembangkan aplikasi saya dengan sukses.
Tetapkan utas saat ini
Pertama-tama kita harus menambahkan
atau Anda tidak akan dapat memuat sumber daya (seperti pegas / context.xml) yang disimpan dalam toples.
Tidak termasuk
guci Anda ke pemuat kelas induk atau Anda tidak akan dapat memahami siapa yang memuat apa.
lihat juga Masalah memuat ulang toples menggunakan URLClassLoader
Namun, kerangka kerja OSGi tetap merupakan cara terbaik.
sumber
Versi lain dari solusi peretasan dari Allain, yang juga berfungsi pada JDK 11:
Pada JDK 11 memberikan peringatan penghentian tetapi berfungsi sebagai solusi sementara mereka yang menggunakan solusi Allain pada JDK 11.
sumber
Solusi kerja lain menggunakan Instrumentasi yang bekerja untuk saya. Ini memiliki keuntungan dari memodifikasi pencarian loader kelas, menghindari masalah pada visibilitas kelas untuk kelas dependen:
Buat Kelas Agen
Untuk contoh ini, itu harus berada di jar yang sama dipanggil oleh baris perintah:
Ubah MANIFEST.MF
Menambahkan referensi ke agen:
Saya benar-benar menggunakan Netbeans, jadi posting ini membantu tentang cara mengubah manifest.mf
Lari
Ini
Launcher-Agent-Class
hanya didukung pada JDK 9+ dan bertanggung jawab untuk memuat agen tanpa secara eksplisit mendefinisikannya pada baris perintah:Cara yang bekerja pada JDK 6+ adalah mendefinisikan
-javaagent
argumen:Menambahkan Jar baru di Runtime
Anda kemudian dapat menambahkan toples seperlunya menggunakan perintah berikut:
Saya tidak menemukan masalah menggunakan ini pada dokumentasi.
sumber
Jika ada yang mencari ini di masa depan, cara ini berfungsi untuk saya dengan OpenJDK 13.0.2.
Saya memiliki banyak kelas yang perlu saya instantiate secara dinamis saat runtime, masing-masing berpotensi dengan classpath yang berbeda.
Dalam kode ini, saya sudah memiliki objek yang disebut paket, yang menampung beberapa metadata tentang kelas yang saya coba muat. Metode getObjectFile () mengembalikan lokasi file kelas untuk kelas. Metode getObjectRootPath () mengembalikan path ke direktori bin / yang berisi file kelas yang menyertakan kelas yang saya coba instantiate. Metode getLibPath () mengembalikan path ke direktori yang berisi file jar yang merupakan classpath untuk modul yang menjadi bagian dari kelas.
Saya menggunakan dependensi Maven: org.xeustechnologies: jcl-core: 2.8 untuk melakukan ini sebelumnya, tetapi setelah melewati JDK 1.8, kadang-kadang membeku dan tidak pernah kembali terjebak "menunggu referensi" di Reference :: waitForReferencePendingList ().
Saya juga menyimpan peta pemuat kelas agar dapat digunakan kembali jika kelas yang saya coba instantiate ada di modul yang sama dengan kelas yang sudah saya instantiated, yang akan saya rekomendasikan.
sumber
silakan lihat proyek yang saya mulai ini: proxy-object lib
Lib ini akan memuat jar dari sistem file atau lokasi lainnya. Ini akan mendedikasikan loader kelas untuk tabung untuk memastikan tidak ada konflik perpustakaan. Pengguna akan dapat membuat objek apa pun dari toples yang dimuat dan memanggil metode apa pun di atasnya. Lib ini dirancang untuk memuat guci yang dikompilasi di Java 8 dari basis kode yang mendukung Java 7.
Untuk membuat objek:
ObjectBuilder mendukung metode pabrik, memanggil fungsi statis, dan memanggil kembali implementasi antarmuka. saya akan memposting lebih banyak contoh di halaman readme.
sumber
Ini bisa menjadi respons yang terlambat, saya bisa melakukannya karena ini (contoh sederhana untuk fastutil-8.2.2.jar) menggunakan kelas jhplot.Web dari DataMelt ( http://jwork.org/dmelt )
Menurut dokumentasi, file ini akan diunduh di dalam "lib / pengguna" dan kemudian dimuat secara dinamis, sehingga Anda dapat segera mulai menggunakan kelas dari file jar ini di program yang sama.
sumber
Saya perlu memuat file jar saat runtime untuk java 8 dan java 9+ (komentar di atas tidak bekerja untuk kedua versi ini). Berikut adalah metode untuk melakukannya (menggunakan Spring Boot 1.5.2 jika itu berhubungan).
sumber
Saya pribadi menemukan java.util.ServiceLoader melakukan pekerjaan dengan cukup baik. Anda bisa mendapatkan contoh di sini .
sumber