Osnove učitavača Java klase

Koncept učitavača klasa, jedan od temelja Java virtualnog stroja, opisuje ponašanje pretvaranja imenovane klase u bitove odgovorne za implementaciju te klase. Budući da učitavači klasa postoje, vrijeme izvođenja Java ne mora znati ništa o datotekama i datotečnim sustavima kada se izvode Java programi.

Što rade utovarivači klase

Klase se uvode u Java okruženje kada se na njih referencira ime u klasi koja je već pokrenuta. Postoji malo čarolije koja pokreće prvu klasu (zbog čega morate metodu main () proglasiti statičnom, uzimajući niz argumenata kao argument), ali kad se ta klasa pokrene, budući pokušaji klase učitavanja vrši učitavač klasa.

U najjednostavnijem obliku, učitavač klasa stvara ravni prostor imena tijela klase na koja se poziva naziv niza. Definicija metode je:

Klasa r = loadClass (Niz klaseName, logička razriješitiIt); 

Varijabla className sadrži niz koji razumije učitavač klase i koristi se za jedinstvenu identifikaciju izvedbe klase. Varijabla resolIt je zastava koja poručuje učitavaču klase da treba razriješiti klase na koje se poziva ovo ime klase (to jest, treba učitati i svaku referenciranu klasu).

Svi Java virtualni strojevi uključuju učitavač jedne klase koji je ugrađen u virtualni stroj. Ovaj ugrađeni loader naziva se primarni loader klase. Nešto je poseban jer virtualni stroj pretpostavlja da ima pristup spremištu pouzdanih klasa koje VM može pokrenuti bez provjere.

Učitavač osnovne klase provodi zadanu implementaciju loadClass () . Dakle, ovaj kôd razumije da se naziv klase java.lang.Object sprema u datoteku s prefiksom java / lang / Object.class negdje na putu do klase. Ovaj kod također implementira i traženje puta klase i traženje zip datoteka za klase. Stvarno zgodna stvar u načinu na koji je ovo dizajnirano je da Java može promijeniti svoj model pohrane klase jednostavnim promjenom skupa funkcija koji implementiraju učitavač klasa.

Kopajući po crijevima Java virtualnog stroja, otkrit ćete da je osnovni učitavač klase implementiran prvenstveno u funkcije FindClassFromClass i ResolveClass .

Pa kada se učitavaju klase? Postoje točno dva slučaja: kada se izvršava novi bytecode (na primjer, FooClass f = new FooClass () ;) i kada bytecode čine statičku referencu na klasu (na primjer, System. Out ).

Utovarivač neprimordijalne klase

"Pa što?" mogli biste pitati.

Java virtualni stroj u sebi ima kuke koje omogućuju upotrebu korisnički definiranog učitavača klasa umjesto prvobitnog. Nadalje, budući da loader korisničke klase dobiva prvu pukotinu na imenu klase, korisnik može implementirati bilo koji broj zanimljivih spremišta klasa, od kojih su najmanje HTTP poslužitelji - koji su Java uopće digli s temelja.

No, postoji trošak, jer je učitavač klasa toliko moćan (na primjer, može zamijeniti java.lang.Object vlastitom verzijom), Java klasama poput apleta nije dopušteno instanciranje vlastitih učitavača. (To, inače, nameće učitavač klase.) Ovaj stupac neće biti koristan ako to pokušavate raditi pomoću apleta, samo s aplikacijom koja se izvodi iz pouzdanog spremišta klasa (kao što su lokalne datoteke).

Učitavač klase korisnika dobiva priliku učitati klasu prije nego što to učini osnovni učitavač klase. Zbog toga može učitati podatke o izvedbi klase iz nekog zamjenskog izvora, što je način na koji AppletClassLoader može učitati klase pomoću HTTP protokola.

Izgradnja SimpleClassLoader

Učitavač klase započinje podrazredom java.lang.ClassLoader . Jedina apstraktna metoda koja se mora implementirati je loadClass () . Protok loadClass () je kako slijedi:

  • Provjerite naziv predmeta.
  • Provjerite je li tražena klasa već učitana.
  • Provjerite je li klasa "sistemska" klasa.
  • Pokušaj dohvaćanja klase iz spremišta učitavača klase.
  • Definirajte klasu za VM.
  • Riješite razred.
  • Vratite predavaču pozivatelja.

SimpleClassLoader pojavljuje se na sljedeći način, s opisima onoga što čini prošaranim kodom.

javna sinkronizirana klasa loadClass (StringName klase, logičko rješenjeIt) baca ClassNotFoundException {Rezultat klase; bajt classData []; System.out.println (">>>>>> Učitaj klasu:" + className); / * Provjerite našu lokalnu predmemoriju predavanja * / result = (Class) classes.get (className); if (rezultat! = null) {System.out.println (">>>>>> vraćanje predmemoriranog rezultata."); povratni rezultat; }

Gornji kod prvi je odjeljak metode loadClass . Kao što vidite, potrebno je ime klase i pretražuje lokalnu hash tablicu koju naš učitavač klasa održava klasa koje je već vratio. Važno je zadržati ovu hash tablicu okolo, jer morate vratiti istu referencu objekta klase za isto ime klase svaki put kada se za to zatraži. Inače će sustav vjerovati da postoje dvije različite klase s istim imenom i bacit će ClassCastException kad god im dodijelite referencu objekta. Također je važno zadržati predmemoriju jer loadClass () metoda poziva se rekurzivno kad se klasa razrješava i morat ćete vratiti predmemorirani rezultat, umjesto da ga lovite za drugom kopijom.

/ * Provjerite s osnovnim učitavačem klase * / try {result = super.findSystemClass (className); System.out.println (">>>>>> vraćanje klase sustava (u CLASSPATH)."); povratni rezultat; } catch (ClassNotFoundException e) {System.out.println (">>>>>> Nije sistemska klasa."); }

Kao što možete vidjeti u gornjem kodu, sljedeći je korak provjeriti može li osnovni učitavač klase riješiti ovo ime klase. Ova je provjera bitna i za zdrav razum i za sigurnost sustava. Na primjer, ako vratite vlastiti primjerak java.lang.Object pozivatelju, tada ovaj objekt neće dijeliti zajedničku superklasu s bilo kojim drugim objektom! Sigurnost sustava može se ugroziti ako je učitač klase vratio vlastitu vrijednost java.lang.SecurityManager , koji nije imao iste provjere kao pravi.

/ * Pokušajte ga učitati iz našeg spremišta * / classData = getClassImplFromDataBase (ime klase); if (classData == null) {throw new ClassNotFoundException (); }

Nakon početnih provjera, dolazimo do gornjeg koda, gdje jednostavni učitavač klase dobiva priliku za učitavanje implementacije ove klase. SimpleClassLoader ima metodu getClassImplFromDataBase () koje u našem jednostavnom primjeru samo prefiksa imenik „dućan \” u ime klase i dodaje ekstenziju „.impl”. Odabrao sam ovu tehniku ​​u primjeru kako ne bi bilo govora o tome da je utovarivač prvobitne klase pronašao našu klasu. Imajte na umu da sun.applet.AppletClassLoader dodaje URL baze baze kodova s ​​HTML stranice na kojoj aplet živi i zatim HTTP dobiva zahtjev za dohvaćanjem bajt kodova.

 / * Definirajte ga (raščlanite datoteku klase) * / rezultat = defineClass (classData, 0, classData.length); 

Ako je učitana implementacija klase, pretposljednji je korak pozvati metodu defineClass () iz java.lang.ClassLoader , što se može smatrati prvim korakom provjere klase. Ova metoda implementirana je u virtualni stroj Java i odgovorna je za provjeru jesu li bajtovi klase legalna datoteka Java klase. Interno, metoda defineClass ispunjava strukturu podataka koju JVM koristi za držanje klasa. Ako su podaci klase neispravni, ovaj će poziv uzrokovati bacanje ClassFormatError .

if (resolIt) {resolClass (rezultat); }

Posljednji zahtjev specifičan za loader klase je pozivanje razrješavanjaKlasa () ako je logički parametar razrješavanje bilo točno. Ova metoda čini dvije stvari: Prvo, uzrokuje učitavanje svih klasa na koje se ova klasa eksplicitno poziva i stvaranje prototipa objekta za ovu klasu; zatim poziva verifikator da izvrši dinamičku provjeru legitimnosti bajt kodova u ovoj klasi. Ako provjera ne uspije, poziv ove metode izbacit će LinkageError , od kojih je najčešća VerifyError .

Imajte na umu da će za bilo koju klasu koju ćete učitati varijabla resolIt uvijek biti istinita. Tek kada sustav rekurzivno poziva loadClass () , ovu varijablu može postaviti kao false jer zna da je klasa koju traži već riješena.

classes.put (ime klase, rezultat); System.out.println (">>>>>> Povratak novo učitane klase."); povratni rezultat; }

Posljednji korak u procesu je pohraniti klasu koju smo učitali i razriješili u našu hash tablicu kako bismo je mogli vratiti ako je potrebno, a zatim vratiti pozivu referencu klase .

Da je bilo tako jednostavno, ne bi se imalo o čemu više razgovarati. U stvari, postoje dva problema s kojima će se morati baviti graditelji učitavača klasa, sigurnost i razgovor s klasama koje učitava prilagođeni učitavač klasa.

Sigurnosna razmatranja

Kad god imate aplikaciju koja učitava proizvoljne klase u sustav putem učitača klasa, ugrožava se integritet vaše aplikacije. To je zbog snage klasičnog utovarivača. Uzmimo trenutak da pogledamo jedan od načina na koji bi potencijalni negativac mogao provaliti u vašu prijavu ako niste oprezni.

U našem jednostavnom učitavaču klasa, ako osnovni učitavač klasa nije mogao pronaći klasu, učitali smo je iz našeg privatnog spremišta. Što se događa kada to spremište sadrži klasu java.lang.FooBar ? Ne postoji klasa java.lang.FooBar , ali mogli bismo je instalirati učitavanjem iz spremišta klasa. Ova klasa, zahvaljujući činjenici da bi imala pristup bilo kojoj varijabli zaštićenoj paketom u paketu java.lang , može manipulirati nekim osjetljivim varijablama kako bi kasnije klase mogle podrezati sigurnosne mjere. Stoga je jedan od poslova bilo kojeg učitavača klase zaštita prostora imena sustava .

U naš jednostavni učitavač klase možemo dodati kod:

 if (className.startsWith ("java.")) baciti newClassNotFoundException (); 

neposredno nakon poziva za pronalazakSystemClass gore. Ovom se tehnikom može zaštititi bilo koji paket u kojem ste sigurni da učitani kôd nikada neće imati razloga za učitavanje nove klase u neki paket.

Sljedeće je područje rizika da preneseno ime mora biti provjereno valjano ime. Razmislite o neprijateljskoj aplikaciji koja je kao naziv klase koristila naziv klase ".. \ .. \ .. \ .. \ netscape \ temp \ xxx.class" koji je željela učitati. Jasno je da ako je učitavač klasa jednostavno predstavio ovo ime našem pojednostavljenom učitavaču datoteka, to bi moglo učitati klasu koju naša aplikacija zapravo nije očekivala. Dakle, prije pretraživanja vlastitog spremišta klasa, dobra je ideja napisati metodu koja provjerava cjelovitost imena vaših klasa. Zatim pozovite tu metodu neposredno prije nego što krenete pretraživati ​​svoje spremište.

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.

Najbolji primjer prve tehnike je web preglednik. Klasa koju definira Java i koju implementiraju svi apleti je java.applet.Applet . Kada AppletClassLoader učita klasu , stvorena instanca objekta prebacuje se na instancu Applet . Ako ovo lijevanje uspije , poziva se metoda init () . U svom primjeru koristim drugu tehniku, sučelje.

Poigravajući se primjerom

Da bih zaokružio primjer, stvorio sam ih još nekoliko

.Java

datoteke. Ovi su:

javno sučelje LocalModule {/ * Pokrenite modul * / void start (opcija String); }