Programovanie

Prezrite si triedy Java

Vitajte pri splátke programu „Java In Depth“, ktorá sa koná tento mesiac. Jednou z prvých výziev pre Javu bolo, či dokáže alebo nemôže obstáť ako schopný „systémový“ jazyk. Základ otázky obsahoval bezpečnostné prvky Java, ktoré bránia triede Java poznať ďalšie triedy, ktoré na virtuálnom stroji bežia popri nej. Táto schopnosť „nahliadnuť do“ tried sa nazýva introspekcia. V prvom verejnom vydaní Java, známom ako Alpha3, bolo možné obísť prísne jazykové pravidlá týkajúce sa viditeľnosti vnútorných komponentov triedy, hoci sa používa ObjectScope trieda. Potom, počas verzie beta, keď ObjectScope bol odstránený z doby chodu z dôvodu obáv o bezpečnosť, mnoho ľudí označilo Javu za nevhodnú na „vážny“ vývoj.

Prečo je nevyhnutná introspekcia, aby sa jazyk mohol považovať za „systémový“ jazyk? Jedna časť odpovede je dosť všedná: Dostať sa z „ničoho“ (tj. Neinicializovaného VM) k „niečomu“ (tj. Bežiacej triede Java) vyžaduje, aby niektorá časť systému mohla kontrolovať triedy, ktoré majú byť bežať tak, aby ste prišli na to, čo s nimi. Kanonický príklad tohto problému je jednoduchý: „Ako program, napísaný v jazyku, ktorý sa nemôže pozerať„ dovnútra “do inej jazykovej súčasti, začne vykonávať prvú jazykovú zložku, ktorá je východiskovým bodom vykonania pre všetky ostatné súčasti? „

Existujú dva spôsoby riešenia introspekcie v Jave: kontrola súborov triedy a nové odrazové API, ktoré je súčasťou Java 1.1.x. Obidvom technikám sa budem venovať, ale v tomto stĺpci sa zameriam na prvotriednu kontrolu spisov. V budúcom stĺpci sa pozriem na to, ako reflexné API rieši tento problém. (Odkazy na kompletný zdrojový kód pre tento stĺpec sú k dispozícii v časti Zdroje.)

Nahliadnite hlboko do mojich súborov ...

Vo vydaniach Java 1.0.x je jednou z najväčších bradavíc za behu Java spôsob, ktorým spustiteľný program Java spustí program. Aký je problém? Vykonanie prechádza z domény hostiteľského operačného systému (Win 95, SunOS atď.) Do domény virtuálneho počítača Java. Písanie riadku “java MyClass arg1 arg2„uvádza do pohybu sériu udalostí, ktoré sú úplne pevne zakódované interpretom Java.

Ako prvá udalosť načíta príkazový shell operačného systému tlmočník Java a ako argument mu odovzdá reťazec „MyClass arg1 arg2“. Ďalšia udalosť nastane, keď sa tlmočník Java pokúsi nájsť triedu s názvom Moja trieda v jednom z adresárov identifikovaných v ceste triedy. Ak sa trieda nájde, treťou udalosťou je vyhľadanie metódy vo vnútri pomenovanej triedy hlavný, ktorého podpis má modifikátory "public" a "static" a ktorý má pole String objekty ako jeho argument. Ak sa táto metóda nájde, vytvorí sa prvotné vlákno a táto metóda sa vyvolá. Interpret Java potom prevádza výraz „arg1 arg2“ na pole reťazcov. Po vyvolaní tejto metódy je všetko ostatné čistá Java.

To je všetko dobré a dobré okrem toho, že hlavný metóda musí byť statická, pretože čas spustenia ju nemožno vyvolať v prostredí Java, ktoré ešte neexistuje. Ďalej je potrebné pomenovať prvý spôsob hlavný pretože neexistuje žiadny spôsob, ako tlmočníkovi povedať názov metódy v príkazovom riadku. Aj keby ste tlmočníkovi povedali názov metódy, neexistuje nijaký všeobecný spôsob, ako zistiť, či sa jedná o triedu, ktorú ste v prvom rade pomenovali. Nakoniec, pretože hlavný metóda je statická, nemôžete ju deklarovať v rozhraní, čo znamená, že nemôžete určiť rozhranie takto:

verejné rozhranie Aplikácia {public void main (String args []); } 

Pokiaľ bolo definované vyššie uvedené rozhranie a triedy ho implementovali, mohli by ste použiť aspoň inštancia operátor v Jave, aby určil, či ste aplikáciu mali alebo nie, a tak určil, či je alebo nie je vhodná na vyvolanie z príkazového riadku. Záverom je, že nemôžete (definovať rozhranie), nebolo (zabudované do interpreta Java), a teda nemôžete (ľahko určiť, či je súbor triedy aplikáciou). Čo teda môžete robiť?

V skutočnosti môžete urobiť dosť, ak viete, čo treba hľadať a ako to používať.

Dekompilácia súborov triedy

Súbor triedy Java je neutrálny v architektúre, čo znamená, že ide o rovnakú sadu bitov, či už je načítaný zo stroja so systémom Windows 95 alebo zo stroja Sun Solaris. Je to tiež veľmi dobre zdokumentované v knihe Špecifikácia virtuálneho stroja Java Lindholm a Yellin. Štruktúra súborov triedy bola čiastočne navrhnutá tak, aby sa dala ľahko načítať do adresného priestoru SPARC. V zásade by sa dal súbor triedy namapovať na virtuálny adresný priestor, potom sa opravili relatívne ukazovatele vo vnútri triedy a presto! Mali ste okamžitú štruktúru triedy. To nebolo na architektúrach Intel veľmi užitočné, ale vďaka dedičstvu zostal formát súboru triedy ľahko pochopiteľný a ešte ľahšie sa dal členiť.

V lete 1994 som pracoval v skupine Java a budoval som bezpečnostný model „najmenej privilegovaných“ pre Java. Práve som dokončil zisťovanie, že to, čo som naozaj chcel, bolo pozrieť sa do triedy Java, vyrezať kúsky, ktoré súčasná úroveň oprávnení neumožňovala, a potom načítať výsledok pomocou vlastného zavádzača triedy. Vtedy som zistil, že v hlavnom behu neexistujú žiadne triedy, ktoré by vedeli o konštrukcii súborov triedy. V strome tried kompilátorov boli verzie (ktoré museli generovať súbory triedy z kompilovaného kódu), ale viac ma zaujímalo vybudovanie niečoho pre manipuláciu s už existujúcimi súbormi triedy.

Začal som budovaním triedy Java, ktorá dokázala rozložiť súbor triedy Java, ktorý mu bol predložený na vstupnom toku. Dal som tomu menej originálny názov ClassFile. Začiatok tejto triedy je uvedený nižšie.

verejná trieda ClassFile {int mágia; krátka veľkáVerzia; krátka menšia verzia; ConstantPoolInfo constantPool []; krátky prístupVlajky; ConstantPoolInfo thisClass; ConstantPoolInfo superClass; Rozhrania ConstantPoolInfo []; Polia FieldInfo []; Metódy MethodInfo []; AttributeInfo attributes []; boolean isValidClass = false; public static final int ACC_PUBLIC = 0x1; public static final int ACC_PRIVATE = 0x2; public static final int ACC_PROTECTED = 0x4; public static final int ACC_STATIC = 0x8; public static final int ACC_FINAL = 0x10; verejný statický konečný int ACC_SYNCHRONIZED = 0x20; verejný statický konečný int ACC_THREADSAFE = 0x40; verejný statický konečný int ACC_TRANSIENT = 0x80; public static final int ACC_NATIVE = 0x100; verejná statická konečná int ACC_INTERFACE = 0x200; verejný statický konečný int ACC_ABSTRACT = 0x400; 

Ako vidíte, inštančné premenné pre triedu ClassFile definujte hlavné komponenty súboru triedy Java. Najmä centrálna dátová štruktúra pre súbor triedy Java je známa ako konštantná oblasť. Ďalšie zaujímavé časti súboru triedy dostávajú vlastné triedy: MethodInfo pre metódy, FieldInfo pre polia (ktoré sú vyhláseniami premenných v triede), AttributeInfo uchovávať atribúty súborov triedy a množinu konštánt, ktorá bola prevzatá priamo zo špecifikácie súborov triedy, aby sa dekódovali rôzne modifikátory, ktoré sa vzťahujú na deklarácie polí, metód a tried.

Primárnou metódou tejto triedy je čítať, ktorý slúži na načítanie súboru triedy z disku a na vytvorenie nového ClassFile napríklad z údajov. Kód pre čítať metóda je uvedená nižšie. Popis som vložil do kódu, pretože metóda býva dosť dlhá.

1 verejné boolovské čítanie (InputStream v) 2 vrhá IOException {3 DataInputStream di = nový DataInputStream (v); 4 int počet; 5 6 mágia = di.readInt (); 7 if (mágia! = (Int) 0xCAFEBABE) {8 návrat (nepravda); 9} 10 11 majorVersion = di.readShort (); 12 minorVersion = di.readShort (); 13 count = di.readShort (); 14 constantPool = nový ConstantPoolInfo [počet]; 15 if (debug) 16 System.out.println ("read (): Čítať hlavičku ..."); 17 constantPool [0] = nový ConstantPoolInfo (); 18 pre (int i = 1; i <constantPool.length; i ++) {19 constantPool [i] = nový ConstantPoolInfo (); 20 if (! ConstantPool [i] .read (di)) {21 return (false); 22} 23 // Tieto dva typy zaberajú „dve“ miesta v tabuľke 24 if ((constantPool [i] .type == ConstantPoolInfo.LONG) || 25 (constantPool [i] .type == ConstantPoolInfo.DOUBLE)) 26 i ++; 27} 

Ako vidíte, vyššie uvedený kód začína zabalením a DataInputStream okolo vstupného toku, na ktorý odkazuje premenná v. Ďalej v riadkoch 6 až 12 sú všetky informácie potrebné na zistenie, či sa kód skutočne pozerá na platný súbor triedy. Tieto informácie pozostávajú z čarovného súboru „cookie“ 0xCAFEBABE a čísel verzií 45 a 3 pre hlavnú a vedľajšiu hodnotu. Ďalej sa na riadkoch 13 až 27 načíta konštantná skupina do poľa ConstantPoolInfo predmety. Zdrojový kód do ConstantPoolInfo je pozoruhodný - jednoducho načíta údaje a identifikuje ich na základe ich typu. Neskoršie prvky z konštantného fondu sa používajú na zobrazenie informácií o triede.

Podľa vyššie uvedeného kódu čítať metóda opätovne prehľadá konštantnú oblasť a „opraví“ odkazy v konštantnej oblasti, ktoré odkazujú na ďalšie položky v konštantnej oblasti. Kód opravy je uvedený nižšie. Táto oprava je nevyhnutná, pretože referenciami sú zvyčajne indexy do konštantnej oblasti a je užitočné mať tieto indexy už vyriešené. Toto tiež poskytuje čitateľovi kontrolu, či súbor triedy nie je poškodený na konštantnej úrovni fondu.

28 pre (int i = 1; i 0) 32 constantPool [i] .arg1 = constantPool [constantPool [i] .index1]; 33 if (constantPool [i] .index2> 0) 34 constantPool [i] .arg2 = constantPool [constantPool [i] .index2]; 35} 36 37 if (dumpConstants) {38 for (int i = 1; i <constantPool.length; i ++) {39 System.out.println ("C" + i + "-" + constantPool [i]); 30} 31} 

Vo vyššie uvedenom kóde každá položka konštantnej skupiny používa hodnoty indexu na zistenie odkazu na inú položku konštantnej skupiny. Po dokončení v riadku 36 sa prípadne vypustí celý bazén.

Po skenovaní kódu za oblasťou konštánt definuje súbor triedy informácie o primárnej triede: jej názov, názov nadtriedy a implementačné rozhrania. The čítať kód vyhľadá tieto hodnoty, ako je uvedené nižšie.

32 accessFlags = di.readShort (); 33 34 thisClass = constantPool [di.readShort ()]; 35 superClass = constantPool [di.readShort ()]; 36 if (debug) 37 System.out.println ("read (): Prečítajte si informácie o triede ..."); 38 39 / * 30 * Identifikujte všetky rozhrania implementované touto triedou 31 * / 32 count = di.readShort (); 33 if (count! = 0) {34 if (debug) 35 System.out.println ("Class implements" + count + "interfaces."); 36 rozhraní = nový ConstantPoolInfo [počet]; 37 pre (int i = 0; i <počet; i ++) {38 int iindex = di.readShort (); 39 if ((iindex constantPool.length - 1)) 40 return (false); 41 rozhraní [i] = constantPool [iindex]; 42 if (ladenie) 43 System.out.println ("I" + i + ":" + rozhrania [i]); 44} 45} 46 if (debug) 47 System.out.println ("read (): Read interface info ..."); 

Po dokončení tohto kódu sa zobrazí čítať metóda vytvorila celkom dobrú predstavu o štruktúre triedy. Zostáva iba zhromaždiť definície polí, definície metód a, čo je možno najdôležitejšie, atribúty súborov triedy.

Formát súboru triedy rozdelí každú z týchto troch skupín na sekciu pozostávajúcu z čísla, za ktorým nasleduje počet inštancií hľadanej veci. Takže pre polia má súbor triedy počet definovaných polí a potom toľko definícií polí. Kód, ktorý sa má skenovať do polí, je uvedený nižšie.

48 count = di.readShort (); 49 if (debug) 50 System.out.println ("Táto trieda má polia" + count + "."); 51 if (count! = 0) {52 fields = new FieldInfo [count]; 53 for (int i = 0; i <count; i ++) {54 fields [i] = new FieldInfo (); 55 if (! Fields [i] .read (di, constantPool)) {56 return (false); 57} 58 if (debug) 59 System.out.println ("F" + i + ":" + 60 polí [i] .toString (constantPool)); 61} 62} 63 if (debug) 64 System.out.println ("read (): Prečítajte si informácie o poli ..."); 

Vyššie uvedený kód začína načítaním počtu v riadku # 48, potom, keď je počet nenulový, číta sa do nových polí pomocou FieldInfo trieda. The FieldInfo trieda jednoducho vyplní údaje, ktoré definujú pole, na virtuálny stroj Java. Kód na čítanie metód a atribútov je rovnaký, jednoducho nahradí odkazy na FieldInfo s odkazmi na MethodInfo alebo AttributeInfo podľa potreby. Tento zdroj tu nie je zahrnutý, môžete si ho však pozrieť pomocou odkazov v sekcii Zdroje nižšie.

$config[zx-auto] not found$config[zx-overlay] not found