Programiranje

Oglejte si razrede Java

Dobrodošli v mesečnem delu "Java In Depth." Eden najzgodnejših izzivov za Javo je bil, ali lahko deluje kot sposoben sistemski jezik. Koren vprašanja je vključeval varnostne funkcije Java, ki preprečujejo, da bi razred Java poznal druge razrede, ki se izvajajo ob njem v navideznem računalniku. Imenuje se ta sposobnost »pogleda znotraj« introspekcija. V prvi javni izdaji Java, znani kot Alpha3, bi se lahko izognili strogim jezikovnim pravilom glede vidnosti notranjih komponent razreda, čeprav bi uporaba ObjectScope razred. Potem, med beta, kdaj ObjectScope je bil zaradi varnostnih razlogov odstranjen iz časa izvajanja, je veliko ljudi Java razglasilo za neprimerno za "resen" razvoj.

Zakaj je introspekcija potrebna, da lahko jezik velja za "sistemski" jezik? En del odgovora je dokaj vsakdanji: če želite od "nič" (to je neinicializirane VM) do "nečesa" (torej delujočega razreda Java), mora biti del sistema sposoben pregledati razrede, ki jih je treba teči, da bi ugotovil, kaj narediti z njimi. Kanonični primer te težave je preprosto naslednji: "Kako program, napisan v jeziku, ki ne more pogledati" znotraj "druge jezikovne komponente, začne izvajati komponento prvega jezika, ki je izhodišče za izvajanje vseh drugih komponent? "

Obstajata dva načina za reševanje samoogledovanja v Javi: pregled datotek razreda in novi API za odsev, ki je del Java 1.1.x. Obdelala bom obe tehniki, v tem stolpcu pa se bom osredotočila na prvovrstni pregled datotek. V prihodnjem stolpcu si bom ogledal, kako odraz API-ja rešuje to težavo. (Povezave do celotne izvorne kode za ta stolpec so na voljo v razdelku Viri.)

Poglobite se v moje datoteke ...

V izdajah Jave 1.0.x je ena največjih bradavic v času izvajanja Jave način, na katerega izvršni program Java zažene program. V čem je problem? Izvedba je prehod iz domene gostiteljskega operacijskega sistema (Win 95, SunOS itd.) V domeno navideznega stroja Java. Tipkanje črte "java MyClass arg1 arg2"sproži vrsto dogodkov, ki jih tolmač Java popolnoma kodira.

Kot prvi dogodek ukazna lupina operacijskega sistema naloži tolmač Java in ji kot argument posreduje niz "MyClass arg1 arg2". Naslednji dogodek se zgodi, ko tolmač Java poskuša najti razred z imenom Moj razred v enem od imenikov, opredeljenih v razredni poti. Če je razred najden, je tretji dogodek poiskati metodo znotraj imenovanega razreda glavni, katerega podpis ima modifikatorja "public" in "static" in ki zavzema vrsto Vrvica predmeti kot njegov argument. Če najdemo to metodo, se izdela prvotna nit in se prikliče metoda. Tolmač Java nato pretvori "arg1 arg2" v niz nizov. Ko se ta metoda pokliče, je vse ostalo čista Java.

To je vse lepo, le da glavni metoda mora biti statična, ker jo čas izvajanja ne more priklicati z okoljem Java, ki še ne obstaja. Nadalje je treba imenovati prvo metodo glavni ker tolmaču ni mogoče navesti imena metode v ukazni vrstici. Tudi če ste tolmaču povedali ime metode, na splošno ni mogoče ugotoviti, ali je bila v razredu, ki ste ga sploh imenovali. Končno, ker glavni metoda je statična, ne morete jo prijaviti v vmesniku, kar pomeni, da ne morete podati vmesnika, kot je ta:

javni vmesnik Application {public void main (String args []); } 

Če je bil zgornji vmesnik definiran in so ga razredi implementirali, potem lahko vsaj uporabite instanceof v Javi, da ugotovi, ali imate aplikacijo ali ne, in tako ugotovi, ali je bila primerna za klicanje iz ukazne vrstice. Bottom line je, da ne morete (določite vmesnika), ni bil (vgrajen v tolmač Java) in zato ne morete (enostavno določite, ali je datoteka razreda aplikacija). Torej, kaj lahko storite?

Pravzaprav lahko naredite kar nekaj, če veste, kaj iskati in kako to uporabiti.

Razstavljanje datotek razredov

Datoteka razreda Java je arhitekturno nevtralna, kar pomeni, da gre za enak nabor bitov, ne glede na to, ali je naložena iz računalnika z operacijskim sistemom Windows 95 ali iz naprave Sun Solaris. V knjigi je tudi zelo dobro dokumentirano Specifikacija navideznega računalnika Java avtorjev Lindholma in Yellina. Datotečna struktura razreda je bila delno zasnovana tako, da jo je mogoče enostavno naložiti v naslovni prostor SPARC. V bistvu bi lahko datoteko razreda preslikali v navidezni naslovni prostor, nato določili relativne kazalce znotraj razreda in presto! Imeli ste takojšnjo strukturo predavanj. To je bilo manj uporabno za Intelove arhitekturne stroje, toda dediščina je omogočila, da je format datoteke razreda razumljiv in še lažje razgradljiv.

Poleti 1994 sem delal v skupini Java in gradil tako imenovani varnostni model "najmanj privilegiran" za Javo. Ravno sem končal z ugotovitvijo, da sem si zares želel pogledati v razred Java, izrezati tiste kose, ki jih trenutna raven privilegij ne dovoljuje, in nato rezultat naložiti skozi nalagalnik razredov po meri. Takrat sem ugotovil, da v glavnem času izvajanja ni bilo razredov, ki bi vedeli o izdelavi datotek razredov. V drevesu razredov prevajalnikov so bile različice (ki so morale generirati datoteke razredov iz prevedene kode), vendar me je bolj zanimalo, kako zgraditi nekaj za manipulacijo z obstoječimi datotekami razreda.

Začel sem z gradnjo razreda Java, ki bi lahko razgradil datoteko razreda Java, ki mu je bila predstavljena v vhodnem toku. Dala sem mu manj kot prvotno ime ClassFile. Začetek tega razreda je prikazan spodaj.

javni razred ClassFile {int magic; kratka glavna različica; kratka molska različica; ConstantPoolInfo constantPool []; kratek dostopFlags; ConstantPoolInfo thisClass; SuperClass ConstantPoolInfo; Vmesniki ConstantPoolInfo []; FieldInfo polja []; MethodInfo metode []; Atributi AtributaInfo []; logična isValidClass = false; javni statični končni int ACC_PUBLIC = 0x1; javni statični končni int ACC_PRIVATE = 0x2; javni statični končni int ACC_PROTECTED = 0x4; javni statični končni int ACC_STATIC = 0x8; javni statični končni int ACC_FINAL = 0x10; javni statični končni int ACC_SYNCHRONIZED = 0x20; javni statični končni int ACC_THREADSAFE = 0x40; javni statični končni int ACC_TRANSIENT = 0x80; javni statični končni int ACC_NATIVE = 0x100; javni statični končni int ACC_INTERFACE = 0x200; javni statični končni int ACC_ABSTRACT = 0x400; 

Kot lahko vidite, spremenljivke primerka za razred ClassFile določite glavne komponente datoteke razreda Java. Osrednja podatkovna struktura za datoteko razreda Java je znana kot konstantno področje. Drugi zanimivi deli datoteke predavanj dobijo predavanja sami: MethodInfo za metode, FieldInfo za polja (ki so deklaracije spremenljivk v razredu), AttributeInfo da vsebuje atribute datotek razreda in nabor konstant, ki je bil vzet neposredno iz specifikacije datotek razreda za dekodiranje različnih modifikatorjev, ki veljajo za izjave polj, metod in razredov.

Primarna metoda tega razreda je preberite, ki se uporablja za branje datoteke razreda z diska in ustvarjanje novega ClassFile primer iz podatkov. Koda za preberite metoda je prikazana spodaj. Opis sem vmešal v kodo, saj je metoda ponavadi precej dolga.

1 javno logično branje (InputStream in) 2 vrže IOException {3 DataInputStream di = new DataInputStream (in); 4 int štetje; 5 6 magic = di.readInt (); 7 if (magic! = (Int) 0xCAFEBABE) {8 return (false); 9} 10 11 majorVersion = di.readShort (); 12 minorVersion = di.readShort (); 13 štetje = di.readShort (); 14 constantPool = novo ConstantPoolInfo [štetje]; 15 if (odpravljanje napak) 16 System.out.println ("read (): Read header ..."); 17 constantPool [0] = nov ConstantPoolInfo (); 18 for (int i = 1; i <constantPool.length; i ++) {19 constantPool [i] = new ConstantPoolInfo (); 20 if (! ConstantPool [i] .read (di)) {21 return (false); 22} 23 // Ti dve vrsti zavzameta "dve" mesti v tabeli 24, če ((constantPool [i] .type == ConstantPoolInfo.LONG) || 25 (constantPool [i] .type == ConstantPoolInfo.DOUBLE)) 26 i ++; 27} 

Kot lahko vidite, se zgornja koda začne tako, da najprej zavijete a DataInputStream okoli vhodnega toka, na katerega se sklicuje spremenljivka v. Poleg tega so v vrsticah od 6 do 12 prisotne vse informacije, potrebne za ugotovitev, ali koda resnično gleda na veljavno datoteko razreda. Te informacije sestavljajo čarobni "piškotek" 0xCAFEBABE in različici številki 45 oziroma 3 za glavno oziroma manjšo vrednost. Nato se v vrsticah od 13 do 27 neprekinjeno področje bere v niz ConstantPoolInfo predmetov. Izvorna koda za ConstantPoolInfo ni nič posebnega - preprosto bere podatke in jih identificira glede na njihovo vrsto. Kasneje se elementi iz konstantnega področja uporabljajo za prikaz informacij o razredu.

Po zgornji kodi se preberite metoda znova pregleda neprekinjeno področje in "popravi" reference v stalnem področju, ki se nanašajo na druge postavke v konstantnem področju. Koda za popravljanje je prikazana spodaj. Ta popravek je potreben, ker so sklici praviloma indeksi v konstantno področje, zato je koristno, da so ti indeksi že razrešeni. S tem tudi preverite, ali bralec ve, da datoteka razreda ni poškodovana na konstantni ravni področja.

28 for (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} 

V zgornji kodi vsak vnos konstantnega področja uporablja vrednosti indeksa, da ugotovi sklic na drug vnos konstantnega področja. Ko je izpolnjen v vrstici 36, se celotni bazen neobvezno izpusti.

Ko je koda pregledana mimo konstantnega področja, datoteka razreda opredeli informacije o primarnem razredu: ime razreda, ime superklase in vmesnike za izvajanje. The preberite kodo poišče te vrednosti, kot je prikazano spodaj.

32 accessFlags = di.readShort (); 33 34 thisClass = constantPool [di.readShort ()]; 35 superClass = constantPool [di.readShort ()]; 36 if (odpravljanje napak) 37 System.out.println ("read (): Preberite informacije o predavanju ..."); 38 39 / * 30 * Identificirajte vse vmesnike, ki jih izvaja ta razred 31 * / 32 count = di.readShort (); 33 if (count! = 0) {34 if (debug) 35 System.out.println ("Razred izvaja" + count + "vmesniki."); 36 vmesnikov = nov ConstantPoolInfo [štetje]; 37 for (int i = 0; i <count; i ++) {38 int iindex = di.readShort (); 39 if ((iindex constantPool.length - 1)) 40 return (false); 41 vmesnikov [i] = constantPool [iindex]; 42 if (odpravljanje napak) 43 System.out.println ("I" + i + ":" + vmesniki [i]); 44} 45} 46 if (odpravljanje napak) 47 System.out.println ("read (): Preberite informacije o vmesniku ..."); 

Ko je ta koda končana, se preberite metoda je ustvarila precej dobro predstavo o strukturi razreda. Ostalo je le zbrati definicije polj, definicije metod in, kar je najpomembneje, atribute datoteke razreda.

Oblika datoteke razreda razbije vsako od teh treh skupin v odsek, sestavljen iz številke, ki ji sledi število primerkov stvari, ki jo iščete. Torej, za polja ima datoteka razreda število definiranih polj in nato toliko definicij polj. Koda za optično branje v poljih je prikazana spodaj.

48 štetje = di.readShort (); 49 if (odpravljanje napak) 50 System.out.println ("Ta razred ima polja" + count + ".); 51 if (count! = 0) {52 polj = novo FieldInfo [count]; 53 za (int i = 0; i <count; i ++) {54 polj [i] = novo FieldInfo (); 55 if (! Polja [i] .read (di, constantPool)) {56 return (false); 57} 58 if (odpravljanje napak) 59 System.out.println ("F" + i + ":" + 60 polj [i] .toString (constantPool)); 61} 62} 63 if (odpravljanje napak) 64 System.out.println ("read (): Preberite informacije o polju ..."); 

Zgornja koda se začne z branjem štetja v vrstici # 48, nato pa, medtem ko štetje ni nič, v novih poljih bere z uporabo FieldInfo razred. The FieldInfo razred preprosto izpolni podatke, ki definirajo polje za navidezni stroj Java. Koda za branje metod in atributov je enaka, preprosto nadomešča sklice na FieldInfo s sklici na MethodInfo ali AttributeInfo kot je primerno. Ta vir tukaj ni vključen, vendar ga lahko pogledate s povezavami v spodnjem razdelku Viri.

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