Programovanie

Základy nakladačov triedy Java

Koncept triedy loader, jeden zo základných kameňov virtuálneho stroja Java, popisuje správanie pri prevode pomenovanej triedy na bity zodpovedné za implementáciu tejto triedy. Pretože existujú zavádzače tried, doba behu Java nemusí pri spustení programov Java vedieť nič o súboroch a súborových systémoch.

Čo robia nakladače triedy

Triedy sa do prostredia Java zavádzajú, keď na ne odkazuje trieda, ktorá je už v prevádzke. Existuje trochu mágie, ktorá vedie k spusteniu prvej triedy (z tohto dôvodu musíte uviesť) hlavný() metóda ako statická, pričom argumentom je pole reťazcov), ale akonáhle je táto trieda spustená, budúce pokusy o načítanie tried vykoná nakladač tried.

Najjednoduchšie je, že zavádzač tried vytvorí plochý priestor názvov telies tried, na ktoré odkazuje názov reťazca. Definícia metódy je:

Class r = loadClass (String className, boolean resolveIt); 

Premenná className obsahuje reťazec, ktorému rozumie zavádzač triedy a slúži na jednoznačnú identifikáciu implementácie triedy. Premenná vyriešiťIt je príznak, ktorý oznámi nakladaču tried, že triedy, na ktoré sa odkazuje týmto názvom triedy, by sa mali vyriešiť (to znamená, že by sa mala načítať aj akákoľvek odkazovaná trieda).

Všetky virtuálne stroje Java obsahujú jeden zavádzač triedy, ktorý je zabudovaný do virtuálneho stroja. Tento vložený zavádzač sa nazýva prvotriedny zavádzač. Je to trochu zvláštne, pretože virtuálny stroj predpokladá, že má prístup do úložiska dôveryhodné triedy ktoré môže spustiť VM bez overenia.

Zavádzač prvotnej triedy implementuje predvolenú implementáciu loadClass (). Tento kód teda chápe, že názov triedy java.lang.Objekt je uložený v súbore s predponou java / lang / Object.class niekde v ceste k triede. Tento kód tiež implementuje vyhľadávanie v triedach aj hľadanie súborov zip pre triedy. Skutočne super na spôsobe, akým je to navrhnuté, je to, že Java môže zmeniť svoj model ukladania tried jednoducho zmenou množiny funkcií, ktoré implementujú zavádzač tried.

Keď sa prehrabete v útrobách virtuálneho stroja Java, zistíte, že prvotný zavádzač tried je implementovaný predovšetkým vo funkciách FindClassFromClass a ResolveClass.

Kedy sú teda načítané triedy? Existujú presne dva prípady: keď sa vykoná nový bajtkód (napríklad FooClassf = nový FooClass ();) a keď bajtové kódy staticky odkazujú na triedu (napríklad Systém.von).

Nakladač triedy nie primordial

"No a čo?" môžete sa opýtať.

Virtuálny stroj Java má v sebe háčiky, ktoré umožňujú použitie používateľom definovaného zavádzača tried namiesto toho prvotného. Ďalej, keďže načítač triedy používateľov dostane prvý crack v názve triedy, je užívateľ schopný implementovať ľubovoľný počet zaujímavých úložísk tried, v neposlednom rade sú to servery HTTP - ktoré na prvom mieste dostali Javu od zeme.

Existuje však cena, pretože triedny nakladač je taký výkonný (môže napríklad nahradiť) java.lang.Objekt s vlastnou verziou), triedam Java, ako sú applety, nie je dovolené vytvárať inštancie svojich vlastných zavádzačov. (Toto je mimochodom vynútené zavádzačom tried.) Tento stĺpec nebude užitočný, ak sa pokúšate robiť tieto veci pomocou appletu, iba pomocou aplikácie spustenej z dôveryhodného úložiska triedy (napríklad lokálne súbory).

Zavádzač triedy používateľov dostane príležitosť načítať triedu skôr, ako to urobí prvotný zavádzač tried. Z tohto dôvodu môže načítať údaje o implementácii triedy z nejakého alternatívneho zdroja, ktorým je AppletClassLoader môže načítať triedy pomocou protokolu HTTP.

Budovanie SimpleClassLoader

Zavádzač tried začína tým, že je podtriedou java.lang.ClassLoader. Jedinou abstraktnou metódou, ktorá sa musí implementovať, je loadClass (). Tok loadClass () je nasledujúci:

  • Overte názov triedy.
  • Skontrolujte, či požadovaná trieda už nebola načítaná.
  • Skontrolujte, či je trieda triedou „systému“.
  • Pokus o načítanie triedy z úložiska tohto zavádzača triedy.
  • Definujte triedu pre VM.
  • Vyriešiť triedu.
  • Vráťte triedu volajúcemu.

Program SimpleClassLoader sa javí nasledovne s popisom toho, čo robí, popretkávaný kódom.

 verejná synchronizovaná trieda loadClass (String className, boolean resolveIt) hodí ClassNotFoundException {výsledok triedy; byte classData []; System.out.println (">>>>>> Trieda načítania:" + className); / * Skontrolujte našu miestnu medzipamäť tried * / result = (Class) classes.get (className); if (result! = null) {System.out.println (">>>>>> vrátenie výsledku z medzipamäte."); návratový výsledok; } 

Vyššie uvedený kód je prvou časťou protokolu loadClass metóda. Ako vidíte, trvá názov triedy a prehľadáva miestnu hašovaciu tabuľku, ktorú náš nakladač tried udržiava, a to z tried, ktoré už vrátila. Je dôležité, aby ste tento hashový stôl udržiavali stále poruke musieť vždy, keď sa zobrazí výzva, vráťte rovnakú referenciu na objekt triedy pre rovnaký názov triedy. V opačnom prípade systém uverí, že existujú dve rôzne triedy s rovnakým názvom, a hodí písmeno ClassCastException kedykoľvek medzi nimi priradíte odkaz na objekt. Je tiež dôležité udržiavať medzipamäť, pretože loadClass () metóda sa volá rekurzívne, keď sa trieda rieši, a namiesto uloženia ďalšej kópie budete musieť výsledok v pamäti uložiť skôr.

/ * Skontrolovať pomocou prvotného nakladača tried * / try {result = super.findSystemClass (className); System.out.println (">>>>>> návratová systémová trieda (v CLASSPATH)."); návratový výsledok; } catch (ClassNotFoundException e) {System.out.println (">>>>>> Nie je to systémová trieda."); } 

Ako vidíte v kóde vyššie, ďalším krokom je kontrola, či prvotný zavádzač triedy dokáže vyriešiť tento názov triedy. Táto kontrola je nevyhnutná pre zdravý rozum a bezpečnosť systému. Napríklad, ak vrátite svoju vlastnú inštanciu z java.lang.Objekt volajúcemu, potom tento objekt nebude zdieľať žiadnu spoločnú nadtriedu s iným objektom! Zabezpečenie systému môže byť ohrozené, ak načítač vašich tried vrátil svoju vlastnú hodnotu java.lang.SecurityManager, ktorá nemala rovnaké kontroly ako skutočná.

 / * Skúste to načítať z nášho úložiska * / classData = getClassImplFromDataBase (className); if (classData == null) {hodiť novú ClassNotFoundException (); } 

Po úvodných kontrolách prichádzame k vyššie uvedenému kódu, v ktorom jednoduchý zavaděč tried dostane príležitosť načítať implementáciu tejto triedy. The SimpleClassLoader má metódu getClassImplFromDataBase () ktorý v našom jednoduchom príklade iba predponuje adresár „store \“ pred názov triedy a pripojí príponu „.impl“. V príklade som zvolil túto techniku, aby nemohlo dôjsť k tomu, že prvotný triedny nakladač nájde našu triedu. Všimnite si, že sun.applet.AppletClassLoader prefixuje adresu URL kódovej základne zo stránky HTML, kde applet žije podľa názvu, a potom vykoná požiadavku HTTP get na načítanie bajtových kódov.

 / * Definujte to (analyzujte súbor triedy) * / result = defineClass (classData, 0, classData.length); 

Ak bola implementácia triedy načítaná, predposledným krokom je zavolať na defineClass () metóda z java.lang.ClassLoader, čo možno považovať za prvý krok overenia triedy. Táto metóda je implementovaná vo virtuálnom stroji Java a je zodpovedná za overenie, či sú bajty triedy legálnym súborom triedy Java. Vnútorne defineClass metóda vypĺňa dátovú štruktúru, ktorú JVM používa na usporiadanie tried. Ak sú údaje triedy chybné, toto volanie spôsobí a ClassFormatError byť vyhodený.

 if (resolveIt) {resolveClass (result); } 

Poslednou špecifickou požiadavkou na nakladač je volať resolveClass () ako boolovsky parameter vyriešiťIt bola pravda. Táto metóda robí dve veci: Najprv spôsobí, že sa načítajú všetky triedy, na ktoré táto trieda odkazuje, a vytvorí sa prototyp objektu pre túto triedu; potom vyvolá overovateľa, aby vykonal dynamické overenie legitimity bytových kódov v tejto triede. Ak overenie zlyhá, toto volanie metódy vyvolá a Chyba prepojenia, z ktorých najbežnejšia je a VerifyError.

Upozorňujeme, že pre každú triedu, ktorú načítate, vyriešiťIt premenná bude vždy pravdivá. Je to len vtedy, keď systém rekurzívne volá loadClass () že môže nastaviť túto premennú na nepravú, pretože vie, že trieda, ktorú žiada, je už vyriešená.

 classes.put (className, result); System.out.println (">>>>>> Vrátenie novo načítanej triedy."); návratový výsledok; } 

Posledným krokom v tomto procese je uloženie triedy, ktorú sme načítali a vyriešili, do našej hash tabuľky, aby sme ju v prípade potreby mohli znova vrátiť, a potom vrátiť Trieda odkaz na volajúceho.

Keby to bolo také jednoduché, nebolo by samozrejme o čom hovoriť. V skutočnosti existujú dva problémy, s ktorými sa budú musieť stavitelia zavádzačov tried vysporiadať, a to zabezpečenie a rozhovor s triedami načítanými vlastným zavádzačom tried.

Bezpečnostné hľadiská

Kedykoľvek máte aplikáciu, ktorá do vášho systému načítava ľubovoľné triedy prostredníctvom vášho zavádzača tried, je ohrozená integrita vašej aplikácie. Je to spôsobené silou nakladača triedy. Poďme sa na chvíľu pozrieť na jeden zo spôsobov, ako by potenciálny záporák mohol preniknúť do vašej aplikácie, pokiaľ nie ste opatrní.

Pokiaľ v našom jednoduchom načítavači tried trieda nemohla triedu nájsť, načítali sme ju z nášho súkromného úložiska. Čo sa stane, keď dané úložisko obsahuje triedu java.lang.FooBar ? Nie je pomenovaná žiadna trieda java.lang.FooBar, ale mohli by sme si nejaký nainštalovať načítaním z úložiska triedy. Táto trieda na základe skutočnosti, že by mala prístup k akejkoľvek premennej chránenej balíkom v java.lang balíček, dokáže manipulovať s niektorými citlivými premennými, aby neskoršie triedy mohli narušiť bezpečnostné opatrenia. Jednou z úloh ktoréhokoľvek nakladača triedy je preto chrániť priestor názvov systému.

V našom jednoduchom zavádzači tried môžeme pridať kód:

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

tesne po volaní na findSystemClass vyššie. Túto techniku ​​je možné použiť na ochranu ľubovoľného balíka, pri ktorom ste si istí, že načítaný kód nebude mať nikdy dôvod načítať do nejakého balíka novú triedu.

Ďalšou oblasťou rizika je, že odovzdané meno musí byť overeným platným menom. Zvážte nepriateľskú aplikáciu, ktorá ako názov svojej triedy použila názov triedy „.. \ .. \ .. \ .. \ netscape \ temp \ xxx.class“, ktorú chcela načítať. Je zrejmé, že ak zavádzač tried jednoducho predstavil tento názov nášmu zjednodušenému zavádzaču súborového systému, mohlo by to načítať triedu, ktorú naša aplikácia v skutočnosti neočakávala. Pred prehľadaním nášho vlastného úložiska tried je teda dobré napísať metódu, ktorá overí integritu názvov vašich tried. Potom zavolajte túto metódu tesne predtým, ako pôjdete prehľadať svoje úložisko.

Na prekonanie medzery pomocou rozhrania

Druhým neintuitívnym problémom pri práci s nakladačmi tried je neschopnosť vrhať objekt, ktorý bol vytvorený z načítanej triedy, do pôvodnej triedy. Musíte vrátiť objekt, ktorý ste vrátili, pretože typické použitie zavádzača vlastných tried je niečo ako:

 CustomClassLoader ccl = nový CustomClassLoader (); Objekt o; Trieda c; c = ccl.loadClass ("someNewClass"); o = c.newInstance (); ((SomeNewClass) o) .someClassMethod (); 

Nemôžete však hádzať o do SomeNewClass pretože iba zavádzač vlastných tried „vie“ o novej triede, ktorú práve načítal.

Existujú pre to dva dôvody. Po prvé, triedy vo virtuálnom stroji Java sa považujú za castovateľné, ak majú aspoň jedného spoločného ukazovateľa triedy. Triedy načítané dvoma rôznymi zavádzačmi tried však budú mať dva rôzne ukazovatele triedy a žiadne spoločné triedy (okrem java.lang.Objekt zvyčajne). Po druhé, hlavným cieľom zavádzača vlastných tried je načítanie tried po aplikácia je nasadená, takže nevie o prioritách o triedach, ktoré sa načíta. Táto dilema je vyriešená tak, že aplikácie aj načítaná trieda budú mať spoločnú triedu.

Existujú dva spôsoby vytvorenia tejto bežnej triedy, buď načítaná trieda musí byť podtriedou triedy, ktorú aplikácia načítala zo svojho dôveryhodného úložiska, alebo načítaná trieda musí implementovať rozhranie, ktoré bolo načítané z dôveryhodného úložiska. Takto majú načítaná trieda a trieda, ktorá nezdieľa celý priestor názvov zavádzača vlastných tried, spoločnú triedu. V príklade používam rozhranie s názvom LocalModule, aj keď by ste z tohto mohli ľahko urobiť triedu a podtriedu.

Najlepším príkladom prvej techniky je webový prehľadávač. Trieda definovaná programom Java, ktorá je implementovaná do všetkých appletov, je java.applet.Applet. Keď je trieda načítaná AppletClassLoader, vytvorená inštancia objektu sa vrhne na inštanciu Applet. Ak je toto obsadenie úspešné, init () metóda sa volá. V mojom príklade používam druhú techniku, rozhranie.

Pohrávanie sa s príkladom

Na záver príkladu som vytvoril ešte niekoľko ďalších

.java

súbory. Sú to:

 verejné rozhranie LocalModule {/ * Spustiť modul * / void start (možnosť String); } 
$config[zx-auto] not found$config[zx-overlay] not found