Programování

Základy zavaděčů tříd Java

Koncept zavaděče tříd, jeden ze základních kamenů virtuálního počítače Java, popisuje chování převodu pojmenované třídy na bity odpovědné za implementaci této třídy. Protože existují zavaděče tříd, doba běhu prostředí Java nemusí při spuštění programů Java vědět nic o souborech a souborových systémech.

Co dělají třídní nakladače

Třídy jsou zavedeny do prostředí Java, když na ně ve třídě, která je již spuštěna, odkazuje název. Existuje trochu magie, která vede k první třídě (což je důvod, proč musíte deklarovat hlavní() metoda jako statická, přičemž argumentem je pole řetězců), ale jakmile je tato třída spuštěna, budoucí pokusy o načtení tříd provádí zavaděč tříd.

V nejjednodušším případě vytvoří zavaděč tříd plochý prostor jmen těl tříd, na které odkazuje název řetězce. Definice metody je:

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

Proměnná jméno třídy obsahuje řetězec, kterému rozumí zavaděč tříd a slouží k jedinečné identifikaci implementace třídy. Proměnná vyřešit je příznak, který oznamuje zavaděči tříd, že třídy odkazované tímto názvem třídy by měly být vyřešeny (to znamená, že by měla být načtena také jakákoli odkazovaná třída).

Všechny virtuální stroje Java obsahují jeden zavaděč tříd, který je zabudovaný do virtuálního stroje. Tento vložený zavaděč se nazývá zavaděč prvotní třídy. Je to poněkud zvláštní, protože virtuální stroj předpokládá, že má přístup k úložišti důvěryhodné třídy které lze spustit VM bez ověření.

Zavaděč prvotních tříd implementuje výchozí implementaci loadClass (). Tento kód tedy chápe, že název třídy java.lang.Object je uložen v souboru s předponou java / lang / Object.class někde v cestě ke třídě. Tento kód také implementuje jak hledání cesty ke třídě, tak hledání souborů zip pro třídy. Skutečně skvělé na způsobu, jakým je to navrženo, je to, že Java může změnit svůj model úložiště tříd jednoduše změnou sady funkcí, které implementují zavaděč tříd.

Při procházení vnitřností virtuálního stroje Java zjistíte, že zavaděč prvotní třídy je implementován primárně ve funkcích FindClassFromClass a ResolveClass.

Kdy jsou tedy načteny třídy? Existují přesně dva případy: když je spuštěn nový bytecode (například FooClassF = nový FooClass ();) a když bytecodes vytvoří statický odkaz na třídu (například Systém.ven).

Nakladač nepůvodních tříd

"No a co?" můžete se zeptat.

Virtuální stroj Java má v sobě háčky, které umožňují použití uživatelem definovaného zavaděče tříd místo toho prvotního. Kromě toho, protože zavaděč tříd uživatelů dostane první crack na název třídy, je uživatel schopen implementovat libovolný počet zajímavých úložišť tříd, v neposlední řadě jsou to servery HTTP - které na prvním místě rozběhly Javu.

Existuje však cena, protože zavaděč tříd je tak výkonný (například může nahradit java.lang.Object s vlastní verzí), třídy Java, jako jsou applety, nemají povoleno vytvářet instanci svých vlastních zavaděčů. (Mimochodem je to vynucováno zavaděčem tříd.) Tento sloupec nebude užitečný, pokud se pokoušíte dělat tyto věci pomocí appletu, pouze s aplikací spuštěnou z důvěryhodného úložiště tříd (například místní soubory).

Zavaděč tříd uživatelů dostane šanci načíst třídu dříve než zavaděč prvotních tříd. Z tohoto důvodu může načíst data implementace třídy z nějakého alternativního zdroje, kterým je AppletClassLoader může načíst třídy pomocí protokolu HTTP.

Vytváření SimpleClassLoader

Zavaděč tříd začíná tím, že je podtřídou java.lang.ClassLoader. Jedinou abstraktní metodou, kterou je třeba implementovat, je loadClass (). Tok loadClass () je následující:

  • Ověřte název třídy.
  • Zkontrolujte, zda požadovaná třída již byla načtena.
  • Zkontrolujte, zda je třída „systémovou“ třídou.
  • Pokus o načtení třídy z úložiště tohoto zavaděče tříd.
  • Definujte třídu pro virtuální počítač.
  • Vyřešte třídu.
  • Vraťte třídu volajícímu.

SimpleClassLoader vypadá následovně s popisy toho, co dělá, proložené kódem.

 public synchronized Class loadClass (String className, boolean resolveIt) throws ClassNotFoundException {Class result; byte classData []; System.out.println (">>>>>> Načíst třídu:" + className); / * Zkontrolujte naši místní mezipaměť tříd * / result = (Class) classes.get (className); if (result! = null) {System.out.println (">>>>>> vrácení výsledku v mezipaměti."); vrátit výsledek; } 

Výše uvedený kód je první částí loadClass metoda. Jak vidíte, trvá název třídy a prohledá místní hashovací tabulku, kterou náš zavaděč tříd udržuje, u tříd, které již vrátila. Je důležité udržovat tento hashový stůl u sebe musí vždy, když budete o to požádáni, vraťte stejný odkaz na objekt třídy se stejným názvem třídy. Jinak bude systém věřit, že existují dvě různé třídy se stejným názvem, a hodí ClassCastException kdykoli mezi nimi přiřadíte odkaz na objekt. Je také důležité udržovat mezipaměť, protože loadClass () metoda je volána rekurzivně, když je třída vyřešena, a budete muset vrátit výsledek uložený v mezipaměti, místo abyste ho honili za další kopii.

/ * Zkontrolujte pomocí zavaděče prvotních tříd * / try {result = super.findSystemClass (className); System.out.println (">>>>>> vracející se systémová třída (v CLASSPATH)."); vrátit výsledek; } catch (ClassNotFoundException e) {System.out.println (">>>>>> Není to systémová třída."); } 

Jak vidíte ve výše uvedeném kódu, dalším krokem je zkontrolovat, zda zavaděč prvotní třídy dokáže vyřešit tento název třídy. Tato kontrola je nezbytná pro zdravý rozum i bezpečnost systému. Například pokud vrátíte svou vlastní instanci java.lang.Object volajícímu, pak tento objekt nebude sdílet žádnou společnou nadtřídu s žádným jiným objektem! Zabezpečení systému může být ohroženo, pokud váš zavaděč tříd vrátil svou vlastní hodnotu java.lang.SecurityManager, který neměl stejné kontroly jako skutečný.

 / * Zkuste jej načíst z našeho úložiště * / classData = getClassImplFromDataBase (className); if (classData == null) {hodit novou ClassNotFoundException (); } 

Po počátečních kontrolách přijdeme k výše uvedenému kódu, kde jednoduchý zavaděč tříd dostane příležitost načíst implementaci této třídy. The SimpleClassLoader má metodu getClassImplFromDataBase () který v našem jednoduchém příkladu pouze prefixuje adresář „store \“ před název třídy a připojí příponu „.impl“. Tuto techniku ​​jsem zvolil v příkladu, aby nemohlo být pochyb o tom, že prvotřídní zavaděč třídy našel naši třídu. Všimněte si, že sun.applet.AppletClassLoader předpona URL codebase ze stránky HTML, kde applet žije podle názvu, a poté provede požadavek HTTP get na načtení bajtových kódů.

 / * Definujte jej (analyzujte soubor třídy) * / result = defineClass (classData, 0, classData.length); 

Pokud byla implementace třídy načtena, předposledním krokem je volání defineClass () metoda od java.lang.ClassLoader, což lze považovat za první krok ověření třídy. Tato metoda je implementována ve virtuálním stroji Java a je zodpovědná za ověření, že bajty třídy jsou legálním souborem třídy Java. Interně defineClass metoda vyplní datovou strukturu, kterou JVM používá k držení tříd. Pokud jsou data třídy chybně, toto volání způsobí a ClassFormatError být hozen.

 if (resolveIt) {resolveClass (result); } 

Posledním specifickým požadavkem pro zavaděč třídy je zavolat vyřešitClass () pokud je booleovský parametr vyřešit byla pravda. Tato metoda dělá dvě věci: Nejprve způsobí, že se načtou všechny třídy, na které tato třída výslovně odkazuje, a vytvoří se prototypový objekt pro tuto třídu; poté vyvolá ověřovatele, aby provedl dynamické ověření legitimity bytových kódů v této třídě. Pokud se ověření nezdaří, vyvolá toto volání metody a Chyba spojení, z nichž nejběžnější je a VerifyError.

Všimněte si, že pro každou třídu, kterou načtete, vyřešit proměnná bude vždy pravdivá. Je to jen tehdy, když systém rekurzivně volá loadClass () že může nastavit tuto proměnnou na false, protože ví, že třída, o kterou žádá, je již vyřešena.

 classes.put (className, result); System.out.println (">>>>>> Vracení nově načtené třídy."); vrátit výsledek; } 

Posledním krokem v tomto procesu je uložení třídy, kterou jsme načetli a vyřešili, do naší hash tabulky, abychom ji mohli v případě potřeby znovu vrátit, a poté vrátit Třída odkaz na volajícího.

Samozřejmě, kdyby to bylo tak jednoduché, nebylo by o čem mluvit. Ve skutečnosti existují dva problémy, s nimiž se budou muset stavitelé zavaděčů tříd vypořádat, zabezpečení a mluvení s třídami načtenými vlastním zavaděčem tříd.

Bezpečnostní aspekty

Kdykoli máte aplikaci, která do vašeho systému načítá libovolné třídy prostřednictvím vašeho zavaděče tříd, je ohrožena integrita vaší aplikace. To je způsobeno výkonem třídního nakladače. Pojďme se na chvíli podívat na jeden ze způsobů, jak by potenciální darebák mohl proniknout do vaší aplikace, pokud nejste opatrní.

V našem jednoduchém zavaděči tříd, pokud zavaděč prvotních tříd nemohl najít třídu, načetli jsme ji z našeho soukromého úložiště. Co se stane, když úložiště obsahuje třídu java.lang.FooBar ? Není pojmenována žádná třída java.lang.FooBar, ale jeden bychom mohli nainstalovat načtením z úložiště tříd. Tato třída na základě skutečnosti, že by měla přístup k jakékoli proměnné chráněné balíčkem v java.lang balíček, může manipulovat s některými citlivými proměnnými, aby pozdější třídy mohly narušit bezpečnostní opatření. Jednou z úloh libovolného zavaděče tříd je proto chránit prostor názvů systému.

V našem jednoduchém zavaděči tříd můžeme přidat kód:

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

hned po zavolání findSystemClass výše. Tuto techniku ​​lze použít k ochraně libovolného balíčku, kde jste si jisti, že načtený kód nebude mít nikdy důvod načíst do nějakého balíčku novou třídu.

Další oblastí rizika je, že předávané jméno musí být ověřené platné jméno. Vezměte v úvahu nepřátelskou aplikaci, která jako název třídy používala název třídy „.. \ .. \ .. \ .. \ netscape \ temp \ xxx.class“, který chtěla načíst. Je zřejmé, že pokud zavaděč tříd jednoduše představil tento název našemu zjednodušujícímu zavaděči souborového systému, mohlo by to načíst třídu, kterou naše aplikace ve skutečnosti neočekávala. Před prohledáním našeho vlastního úložiště tříd je tedy dobré napsat metodu, která ověří integritu názvů vašich tříd. Tuto metodu pak zavolejte těsně předtím, než půjdete prohledat úložiště.

Pomocí rozhraní k překlenutí mezery

Druhým neintuitivním problémem při práci s zavaděči tříd je neschopnost vrhat objekt, který byl vytvořen z načtené třídy, do své původní třídy. Musíte vrátit objekt vrácený, protože typické použití zavaděče vlastní třídy je něco jako:

 CustomClassLoader ccl = new CustomClassLoader (); Objekt o; Třída c; c = ccl.loadClass ("someNewClass"); o = c.newInstance (); ((SomeNewClass) o) .someClassMethod (); 

Nelze však házet Ó na SomeNewClass protože pouze vlastní zavaděč třídy „ví“ o nové třídě, kterou právě načetla.

Existují pro to dva důvody. Za prvé, třídy ve virtuálním stroji Java jsou považovány za castovatelné, pokud mají alespoň jeden společný ukazatel třídy. Třídy načtené dvěma různými zavaděči tříd však budou mít dva různé ukazatele tříd a žádné společné třídy (kromě java.lang.Object obvykle). Zadruhé, myšlenkou vlastního zavaděče tříd je načítání tříd po aplikace je nasazena, takže aplikace nezná prioritu o třídách, které se načte. Toto dilema je vyřešeno tak, že aplikace i načtená třída budou mít společnou třídu.

Existují dva způsoby vytvoření této běžné třídy, buď načtená třída musí být podtřídou třídy, kterou aplikace načetla ze svého důvěryhodného úložiště, nebo načtená třída musí implementovat rozhraní, které bylo načteno z důvěryhodného úložiště. Tímto způsobem mají načtená třída a třída, která nesdílí celý prostor názvů zavaděče vlastní třídy, společnou třídu. V příkladu používám rozhraní s názvem LocalModule, i když byste to mohli stejně snadno udělat třídou a podtřídou.

Nejlepším příkladem první techniky je webový prohlížeč. Třída definovaná prostředím Java, která je implementována všemi applety, je java.applet.Applet. Když je třída načtena AppletClassLoader, instance objektu, který je vytvořen, je přetypován na instanci Applet. Pokud toto obsazení uspěje init () metoda se nazývá. V mém příkladu používám druhou techniku, rozhraní.

Hraní s příkladem

Pro doplnění příkladu jsem vytvořil ještě pár

.Jáva

soubory. Tyto jsou:

 veřejné rozhraní LocalModule {/ * Spustit modul * / void start (možnost Řetězec); } 
$config[zx-auto] not found$config[zx-overlay] not found