Perbedaan antara loader kelas konteks thread dan classloader normal


242

Apa perbedaan antara pemuat kelas konteks thread dan pemuat kelas normal?

Yaitu, jika Thread.currentThread().getContextClassLoader()dan getClass().getClassLoader()mengembalikan objek loader kelas yang berbeda, mana yang akan digunakan?

Jawaban:


151

Setiap kelas akan menggunakan classloadernya sendiri untuk memuat kelas lain. Jadi, jika ClassA.classreferensi ClassB.classmaka ClassBperlu berada di jalan setapak classloader ClassA, atau orang tuanya.

Classloader konteks thread adalah classloader saat ini untuk utas saat ini. Objek dapat dibuat dari kelas di ClassLoaderCdan kemudian diteruskan ke utas yang dimiliki oleh ClassLoaderD. Dalam hal ini objek perlu digunakan Thread.currentThread().getContextClassLoader()secara langsung jika ingin memuat sumber daya yang tidak tersedia pada classloadernya sendiri.


1
Mengapa Anda mengatakan bahwa itu ClassBharus di jalan setapak ClassAloader (atau ClassAorang tua loader)? Mungkinkah ClassAloader override loadClass(), sehingga dapat memuat dengan sukses ClassBbahkan ketika ClassBtidak ada di classpath?
Pacerier

8
Memang, tidak semua classloader memiliki classpath. Ketika menulis "ClassB perlu berada di classpath dari classloader ClassA", maksud saya "ClassB perlu dimuat oleh classloader ClassA". 90% dari waktu artinya sama. Tetapi jika Anda tidak menggunakan classloader berbasis URL, maka hanya kasus kedua yang benar.
David Roussel

Apa artinya mengatakan " ClassA.classrujukan ClassB.class" itu?
jameshfisher

1
Ketika ClassA memiliki pernyataan impor untuk ClassB, atau jika ada metode di ClassA yang memiliki variabel lokal tipe ClassB. Itu akan memicu ClassB untuk dimuat, jika belum dimuat.
David Roussel

Saya pikir masalah saya terhubung dengan topik ini. Apa yang Anda pikirkan tentang solusi saya? Saya tahu ini bukan pola yang baik, tapi saya tidak punya ide lain bagaimana memperbaikinya: stackoverflow.com/questions/29238493/…
Marcin Erbel

109

Ini tidak menjawab pertanyaan awal, tetapi karena pertanyaan itu berperingkat tinggi dan ditautkan untuk ContextClassLoaderpertanyaan apa pun , saya pikir penting untuk menjawab pertanyaan terkait kapan pemuat kelas konteks harus digunakan. Jawaban singkat: jangan pernah gunakan pemuat kelas konteks ! Tetapi atur getClass().getClassLoader()ketika Anda harus memanggil metode yang tidak memiliki ClassLoaderparameter.

Ketika kode dari satu kelas meminta untuk memuat kelas lain, pemuat kelas yang benar untuk digunakan adalah pemuat kelas yang sama dengan kelas pemanggil (yaitu, getClass().getClassLoader()). Ini adalah cara kerja 99,9% dari waktu karena ini adalah apa yang JVM lakukan sendiri saat pertama kali Anda membangun sebuah instance dari kelas baru, memanggil metode statis, atau mengakses bidang statis.

Ketika Anda ingin membuat kelas menggunakan refleksi (seperti ketika deserializing atau memuat kelas bernama yang dapat dikonfigurasi), perpustakaan yang melakukan refleksi harus selalu menanyakan aplikasi pemuat kelas mana yang akan digunakan, dengan menerima ClassLoadersebagai parameter dari aplikasi. Aplikasi (yang tahu semua kelas yang perlu dibangun) harus lulus getClass().getClassLoader().

Cara lain untuk mendapatkan pemuat kelas tidak benar. Jika penggunaan perpustakaan hacks seperti Thread.getContextClassLoader(), sun.misc.VM.latestUserDefinedLoader(), atau sun.reflect.Reflection.getCallerClass()itu adalah bug yang disebabkan oleh kekurangan dalam API. Pada dasarnya, Thread.getContextClassLoader()ada hanya karena siapa pun yang merancang ObjectInputStreamAPI lupa untuk menerima ClassLoadersebagai parameter, dan kesalahan ini telah menghantui komunitas Java hingga hari ini.

Yang mengatakan, banyak banyak kelas JDK menggunakan satu dari beberapa peretasan untuk menebak beberapa loader kelas untuk digunakan. Beberapa menggunakan ContextClassLoader(yang gagal ketika Anda menjalankan aplikasi yang berbeda pada kumpulan utas bersama, atau ketika Anda meninggalkan ContextClassLoader null), beberapa berjalan tumpukan (yang gagal ketika penelepon langsung dari kelas itu sendiri perpustakaan), beberapa menggunakan loader sistem kelas (yang baik-baik saja, asalkan didokumentasikan hanya menggunakan kelas di CLASSPATH) atau bootstrap kelas loader, dan beberapa menggunakan kombinasi teknik-teknik di atas yang tidak dapat diprediksi (yang hanya membuat hal-hal lebih membingungkan). Ini telah menyebabkan banyak tangisan dan kertakan gigi.

Saat menggunakan API seperti itu, pertama-tama, cobalah untuk menemukan kelebihan metode yang menerima pemuat kelas sebagai parameter . Jika tidak ada metode yang masuk akal, cobalah mengatur ContextClassLoadersebelum panggilan API (dan mengatur ulang setelah itu):

ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try {
    Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
    // call some API that uses reflection without taking ClassLoader param
} finally {
    Thread.currentThread().setContextClassLoader(originalClassLoader);
}

5
Ya, inilah jawaban yang saya tunjukkan kepada siapa pun yang mengajukan pertanyaan.
Marko Topolnik

6
Jawaban ini berfokus pada penggunaan pemuat kelas untuk memuat kelas (untuk membuat instance melalui refleksi atau serupa), sementara tujuan lain digunakan untuk (dan, pada kenyataannya, satu-satunya tujuan yang pernah saya gunakan untuk pribadi) adalah memuat sumber daya. Apakah prinsip yang sama berlaku, atau adakah situasi di mana Anda ingin mengambil sumber daya melalui pemuat kelas konteks daripada pemanggil kelas pemuat?
Egor Hans

90

Ada artikel di javaworld.com yang menjelaskan perbedaan => ClassLoader mana yang harus Anda gunakan

(1)

Classloaders konteks thread memberikan pintu belakang di sekitar skema delegasi classloading.

Ambil JNDI sebagai contoh: nyali diimplementasikan oleh kelas bootstrap di rt.jar (dimulai dengan J2SE 1.3), tetapi kelas inti JNDI ini dapat memuat penyedia JNDI yang diimplementasikan oleh vendor independen dan berpotensi digunakan dalam -kelas jalur aplikasi. Skenario ini memerlukan classloader induk (yang primordial dalam kasus ini) untuk memuat kelas yang terlihat oleh salah satu classloader anak-nya (sistem satu, misalnya). Delegasi J2SE yang normal tidak berfungsi, dan solusinya adalah membuat kelas inti JNDI menggunakan thread konteks loader, sehingga secara efektif "tunneling" melalui hierarki classloader dalam arah yang berlawanan dengan delegasi yang tepat.

(2) dari sumber yang sama:

Kebingungan ini mungkin akan tetap dengan Java untuk beberapa waktu. Ambil API J2SE apa pun dengan pemuatan sumber daya dinamis apa pun dan coba tebak strategi pemuatan yang digunakannya. Berikut adalah contohnya:

  • JNDI menggunakan classloaders konteks
  • Class.getResource () dan Class.forName () menggunakan classloader saat ini
  • JAXP menggunakan classloaders konteks (pada J2SE 1.4)
  • java.util.ResourceBundle menggunakan classloader pemanggil saat ini
  • Penangan protokol URL yang ditentukan melalui properti sistem java.protocol.handler.pkgs dilihat hanya di bootstrap dan classloaders sistem
  • Java Serialization API menggunakan classloader saat ini dari pemanggil secara default

Seperti yang disarankan bahwa solusinya adalah untuk membuat kelas inti JNDI menggunakan thread context loader, saya tidak mengerti bagaimana ini membantu dalam kasus ini. Kami ingin memuat kelas vendor implementasi menggunakan induk classloader tetapi mereka tidak terlihat oleh classloader induk . Jadi bagaimana kita bisa memuatnya menggunakan induk, bahkan jika kita mengatur classloader induk ini dalam konteks classloader utas.
Sunny Gupta

6
@ Sam, solusi yang disarankan sebenarnya sangat bertolak belakang dengan apa yang Anda katakan di akhir. Ini bukan bootstrappemuat kelas induk yang ditetapkan sebagai pemuat kelas konteks tetapi systempemuat kelas classpath anak yang Threadsedang disiapkan. The JNDIkelas kemudian pastikan untuk menggunakan Thread.currentThread().getContextClassLoader()untuk memuat kelas implementasi JNDI tersedia di classpath.
Ravi Thapliyal

"Delegasi J2SE yang normal tidak bekerja", boleh saya tahu mengapa itu tidak berhasil? Karena Bootstrap ClassLoader hanya dapat memuat kelas dari rt.jar dan tidak dapat memuat kelas dari -classpath aplikasi? Baik?
YuFeng Shen

37

Menambahkan ke @David Roussel jawaban, kelas dapat dimuat oleh beberapa kelas loader.

Mari kita mengerti cara kerja pemuat kelas .

Dari blog javin paul di javarevisited:

masukkan deskripsi gambar di sini

masukkan deskripsi gambar di sini

ClassLoader mengikuti tiga prinsip.

Prinsip pendelegasian

Kelas dimuat di Jawa, saat dibutuhkan. Misalkan Anda memiliki kelas khusus aplikasi yang disebut Abc.class, permintaan pertama untuk memuat kelas ini akan datang ke Application ClassLoader yang akan mendelegasikan kepada induknya Extension ClassLoader yang selanjutnya mendelegasikan ke loader kelas Primordial atau Bootstrap

  • Bootstrap ClassLoader bertanggung jawab untuk memuat file kelas JDK standar dari rt.jar dan merupakan induk dari semua pemuat kelas di Jawa. Pemuat kelas bootstrap tidak memiliki orang tua.

  • Extension ClassLoader mendelegasikan permintaan pemuatan kelas ke induknya, Bootstrap dan jika tidak berhasil, memuat kelas dari direktori jre / lib / ext direktori atau direktori lain yang diarahkan oleh properti sistem java.ext.dirs

  • Pemuat kelas sistem atau Aplikasi dan bertanggung jawab untuk memuat kelas aplikasi khusus dari variabel lingkungan CLASSPATH, opsi baris -classpath atau -cp, baris atribut Path-file dari file Manifest di dalam JAR.

  • Pemuat kelas aplikasi adalah anak dari Extension ClassLoader dan diimplementasikan oleh sun.misc.Launcher$AppClassLoaderkelas.

CATATAN: Kecuali pemuat kelas Bootstrap , yang diimplementasikan dalam bahasa asli sebagian besar dalam bahasa C, semua pemuat kelas Java diimplementasikan menggunakan java.lang.ClassLoader.

Prinsip Visibilitas

Menurut prinsip visibilitas, Child ClassLoader dapat melihat kelas dimuat oleh Parent ClassLoader tetapi sebaliknya tidak benar.

Prinsip Keunikan

Menurut prinsip ini, kelas yang dimuat oleh Orang Tua tidak boleh dimuat oleh Child ClassLoader lagi

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.