Asas pemuat kelas Java

Konsep pemuat kelas, salah satu landasan mesin maya Java, menerangkan tingkah laku menukar kelas yang dinamakan menjadi bit yang bertanggungjawab untuk melaksanakan kelas itu. Kerana pemuat kelas ada, waktu menjalankan Java tidak perlu mengetahui apa-apa mengenai fail dan sistem fail ketika menjalankan program Java.

Apa yang dilakukan oleh pemuat kelas

Kelas diperkenalkan ke lingkungan Java ketika mereka dirujuk dengan nama di kelas yang sudah berjalan. Terdapat sedikit keajaiban yang berterusan untuk menjalankan kelas pertama (sebab itulah anda harus menyatakan kaedah utama () sebagai statik, mengambil rentetan rentetan sebagai argumen), tetapi setelah kelas itu berjalan, percubaan di masa depan akan kelas memuat dilakukan oleh kelas pemuat.

Paling sederhana, pemuat kelas mencipta ruang nama rata badan kelas yang dirujuk dengan nama rentetan. Definisi kaedah adalah:

Kelas r = loadClass (String className, boolean resolutionIt); 

ClassName pemboleh ubah mengandungi rentetan yang difahami oleh pemuat kelas dan digunakan untuk mengenal pasti pelaksanaan kelas secara unik. Pemboleh ubah resolusIa adalah bendera untuk memberitahu pemuat kelas bahawa kelas yang dirujuk oleh nama kelas ini harus diselesaikan (iaitu, kelas yang dirujuk harus dimuat juga).

Semua mesin maya Java termasuk pemuat satu kelas yang tertanam di mesin maya. Loader terbenam ini dipanggil loader kelas primordial. Ia agak istimewa kerana mesin maya menganggap bahawa ia mempunyai akses ke repositori kelas dipercayai yang boleh dijalankan oleh VM tanpa pengesahan.

Loader kelas primordial melaksanakan pelaksanaan defaultClass () . Oleh itu, kod ini memahami bahawa nama kelas java.lang.Object disimpan dalam fail dengan awalan java / lang / Object.class di suatu tempat di laluan kelas. Kod ini juga melaksanakan pencarian jalan kelas dan mencari fail zip untuk kelas. Perkara yang sangat keren tentang cara ini dirancang ialah Java dapat mengubah model penyimpanan kelasnya hanya dengan mengubah set fungsi yang menerapkan pemuat kelas.

Menggali keberanian mesin maya Java, anda akan mengetahui bahawa pemuat kelas primer dilaksanakan terutamanya dalam fungsi FindClassFromClass dan ResolveClass .

Jadi bilakah kelas dimuat? Terdapat betul-betul dua kes: apabila kod bytek baru dijalankan (contohnya, FooClass f = FooClass baru () ;) dan ketika kod bytek membuat rujukan statik ke kelas (misalnya, Sistem keluar ).

Pemuat kelas bukan primordial

"Jadi apa?" anda mungkin bertanya.

Mesin maya Java mempunyai cangkuk di dalamnya untuk membolehkan pemuat kelas yang ditentukan pengguna digunakan sebagai ganti primordial. Tambahan pula, sejak pemuat kelas pengguna mendapat retak pertama pada nama kelas, pengguna dapat menerapkan sejumlah repositori kelas yang menarik, tidak sedikit daripadanya adalah pelayan HTTP - yang membuat Java dimuat di tempat pertama.

Terdapat kos, bagaimanapun, kerana kelas loader sangat kuat (misalnya, ia dapat menggantikan java.lang.Object dengan versi tersendiri), kelas Java seperti applet tidak dibenarkan untuk memberi contoh pemuat mereka sendiri. (Ini ditegakkan oleh pemuat kelas, dengan cara.) Lajur ini tidak akan berguna jika anda berusaha melakukan perkara ini dengan applet, hanya dengan aplikasi yang berjalan dari repositori kelas yang dipercayai (seperti fail tempatan).

Pemuat kelas pengguna berpeluang memuat kelas sebelum pemuat kelas primer melakukannya. Oleh kerana itu, ia dapat memuat data pelaksanaan kelas dari beberapa sumber alternatif, yaitu bagaimana AppletClassLoader dapat memuat kelas menggunakan protokol HTTP.

Membina SimpleClassLoader

Pemuat kelas dimulakan dengan menjadi subkelas java.lang.ClassLoader . Satu-satunya kaedah abstrak yang mesti dilaksanakan ialah loadClass () . Aliran loadClass () adalah seperti berikut:

  • Sahkan nama kelas.
  • Periksa untuk mengetahui sama ada kelas yang diminta telah dimuatkan.
  • Periksa untuk mengetahui sama ada kelas itu "sistem" kelas.
  • Cuba ambil kelas dari repositori pemuat kelas ini.
  • Tentukan kelas untuk VM.
  • Selesaikan kelas.
  • Kembalikan kelas ke pemanggil.

SimpleClassLoader muncul seperti berikut, dengan keterangan tentang apa yang diselingi dengan kod.

loadClass Kelas yang diselaraskan awam (String className, boolean resolIt) membuang ClassNotFoundException {Hasil kelas; byte classData []; System.out.println (">>>>>> Muatkan kelas:" + className); / * Periksa cache tempatan kelas kami * / hasil = (Kelas) class.get (className); if (hasil! = null) {System.out.println (">>>>>> mengembalikan hasil cache."); hasil pulangan; }

Kod di atas adalah bahagian pertama kaedah loadClass . Seperti yang anda lihat, butuh nama kelas dan mencari jadual hash tempatan yang dikendalikan oleh pemuat kelas kami dari kelas yang telah dikembalikan. Penting untuk menyimpan jadual hash ini kerana anda mesti mengembalikan rujukan objek kelas yang sama untuk nama kelas yang sama setiap kali anda memintanya. Jika tidak, sistem akan mempercayai bahawa terdapat dua kelas yang berbeza dengan nama yang sama dan akan membuang ClassCastException setiap kali anda memberikan rujukan objek di antara mereka. Juga penting untuk menyimpan cache kerana loadClass () kaedah dipanggil secara rekursif ketika kelas sedang diselesaikan, dan anda perlu mengembalikan hasil cache daripada mengejarnya untuk salinan lain.

/ * Periksa dengan pemuat kelas primordial * / cuba {result = super.findSystemClass (className); System.out.println (">>>>>> mengembalikan kelas sistem (dalam CLASSPATH)."); hasil pulangan; } tangkapan (ClassNotFoundException e) {System.out.println (">>>>>> Bukan kelas sistem."); }

Seperti yang anda lihat dalam kod di atas, langkah seterusnya adalah memeriksa apakah pemuat kelas primordial dapat menyelesaikan nama kelas ini. Pemeriksaan ini penting untuk kewarasan dan keselamatan sistem. Sebagai contoh, jika anda mengembalikan contoh java.lang.Object anda sendiri kepada pemanggil, maka objek ini tidak akan berkongsi superclass biasa dengan objek lain! Keselamatan sistem boleh dikompromikan jika pemuat kelas anda mengembalikan nilai java.lang.SecurityManager sendiri , yang tidak mempunyai pemeriksaan yang sama dengan yang sebenarnya.

/ * Cuba muatkan dari repositori kami * / classData = getClassImplFromDataBase (className); if (classData == null) {lemparkan ClassNotFoundException baru (); }

Selepas pemeriksaan awal, kami sampai ke kod di atas yang mana pemuat kelas sederhana mendapat peluang untuk memuat pelaksanaan kelas ini. The SimpleClassLoader mempunyai kaedah getClassImplFromDataBase () yang dalam contoh mudah kami hanya awalan direktori "kedai \" pada nama kelas dan Melampirkan sambungan ".impl". Saya memilih teknik ini dalam contohnya supaya tidak ada persoalan mengenai kelas primer yang memuat kelas kami. Perhatikan bahawa sun.applet.AppletClassLoader awalan URL pangkalan kode dari halaman HTML di mana applet tinggal dengan nama dan kemudian melakukan permintaan HTTP untuk mengambil kod bytek.

 / * Definisikannya (parse file class) * / result = defineClass (classData, 0, classData.length); 

Sekiranya pelaksanaan kelas dimuat, langkah kedua dari belakang adalah memanggil kaedah defineClass () dari java.lang.ClassLoader , yang dapat dianggap sebagai langkah pertama pengesahan kelas. Kaedah ini dilaksanakan di mesin maya Java dan bertanggungjawab untuk mengesahkan bahawa bait kelas adalah fail kelas Java yang sah. Secara dalaman, kaedah defineClass mengisi struktur data yang digunakan JVM untuk mengadakan kelas. Sekiranya data kelas salah, panggilan ini akan menyebabkan ClassFormatError dilemparkan.

jika (resolIt) {resolClass (hasil); }

Keperluan khusus pemuat kelas terakhir adalah memanggil resolClass () jika parameter boolean resolIt itu benar. Kaedah ini melakukan dua perkara: Pertama, ia menyebabkan setiap kelas yang dirujuk oleh kelas ini dimuat secara eksplisit dan objek prototaip untuk kelas ini dibuat; kemudian, ia meminta pengesah untuk melakukan pengesahan dinamik kesahihan kod byt dalam kelas ini. Sekiranya pengesahan gagal, panggilan kaedah ini akan membuang LinkageError , yang paling biasa adalah VerifyError .

Perhatikan bahawa untuk kelas yang anda muatkan, pembolehubah resolIt akan selalu benar. Hanya apabila sistem secara berulang memanggil loadClass () , ia dapat menetapkan pemboleh ubah ini salah kerana ia mengetahui kelas yang dimintanya sudah diselesaikan.

class.put (className, hasil); System.out.println (">>>>>> Mengembalikan kelas yang baru dimuat."); hasil pulangan; }

Langkah terakhir dalam proses ini adalah untuk menyimpan kelas yang telah kami muatkan dan diselesaikan ke dalam jadual hash kami sehingga kami dapat mengembalikannya semula jika perlu, dan kemudian mengembalikan rujukan Kelas kepada pemanggil.

Sudah tentu jika ini sederhana tidak akan ada banyak lagi yang boleh dibincangkan. Sebenarnya, terdapat dua masalah yang perlu ditangani oleh pembangun pemuat kelas, keselamatan dan bercakap dengan kelas yang dimuatkan oleh pemuat kelas khusus.

Pertimbangan keselamatan

Setiap kali anda mempunyai aplikasi memuat kelas sewenang-wenang ke dalam sistem melalui pemuat kelas anda, integriti aplikasi anda berisiko. Ini disebabkan oleh kekuatan loader kelas. Mari luangkan masa untuk melihat salah satu cara calon penjahat dapat masuk ke dalam aplikasi anda jika anda tidak berhati-hati.

Di loader kelas sederhana kami, jika loader kelas primordial tidak dapat menemui kelas, kami memuatkannya dari repositori peribadi kami. Apa yang berlaku apabila repositori itu mengandungi kelas java.lang.FooBar ? Tidak ada kelas bernama java.lang.FooBar , tetapi kami dapat memasangnya dengan memuatkannya dari repositori kelas. Kelas ini, berdasarkan hakikat bahawa ia akan mempunyai akses ke mana-mana pemboleh ubah yang dilindungi pakej dalam pakej java.lang , dapat memanipulasi beberapa pemboleh ubah sensitif sehingga kelas kemudian dapat menumbangkan langkah keselamatan. Oleh itu, salah satu tugas dari mana-mana pemuat kelas adalah melindungi ruang nama sistem .

Dalam pemuat kelas sederhana kami dapat menambahkan kod:

 jika (className.startsWith ("java.")) buang newClassNotFoundException (); 

just after the call to findSystemClass above. This technique can be used to protect any package where you are sure that the loaded code will never have a reason to load a new class into some package.

Another area of risk is that the name passed must be a verified valid name. Consider a hostile application that used a class name of "..\..\..\..\netscape\temp\xxx.class" as its class name that it wanted loaded. Clearly, if the class loader simply presented this name to our simplistic file system loader this might load a class that actually wasn't expected by our application. Thus, before searching our own repository of classes, it is a good idea to write a method that verifies the integrity of your class names. Then call that method just before you go to search your repository.

Using an interface to bridge the gap

The second non-intuitive issue with working with class loaders is the inability to cast an object that was created from a loaded class into its original class. You need to cast the object returned because the typical use of a custom class loader is something like:

 CustomClassLoader ccl = new CustomClassLoader(); Object o; Class c; c = ccl.loadClass("someNewClass"); o = c.newInstance(); ((SomeNewClass)o).someClassMethod(); 

However, you cannot cast o to SomeNewClass because only the custom class loader "knows" about the new class it has just loaded.

There are two reasons for this. First, the classes in the Java virtual machine are considered castable if they have at least one common class pointer. However, classes loaded by two different class loaders will have two different class pointers and no classes in common (except java.lang.Object usually). Second, the idea behind having a custom class loader is to load classes after the application is deployed so the application does not know a priory about the classes it will load. This dilemma is solved by giving both the application and the loaded class a class in common.

There are two ways of creating this common class, either the loaded class must be a subclass of a class that the application has loaded from its trusted repository, or the loaded class must implement an interface that was loaded from the trusted repository. This way the loaded class and the class that does not share the complete name space of the custom class loader have a class in common. In the example I use an interface named LocalModule, although you could just as easily make this a class and subclass it.

Contoh terbaik teknik pertama ialah penyemak imbas Web. Kelas yang ditentukan oleh Java yang dilaksanakan oleh semua applet adalah java.applet.Applet . Apabila kelas dimuat oleh AppletClassLoader , instance objek yang dibuat dilemparkan ke instance Applet . Sekiranya pemain ini berjaya, kaedah init () dipanggil. Dalam contoh saya, saya menggunakan teknik kedua, antara muka.

Bermain dengan contoh

Untuk melengkapkan contoh saya telah membuat beberapa lagi

.java

fail. Ini adalah:

antara muka awam LocalModule {/ * Mulakan modul * / batal mula (Pilihan rentetan); }