Programavimas

Pažvelkite į „Java“ klases

Sveiki atvykę į šio mėnesio „Java In Depth“ dalį. Vienas iš ankstyviausių „Java“ iššūkių buvo tai, ar ji gali būti tinkama „sistemų“ kalba. Klausimo esmė buvo susijusi su „Java“ saugos funkcijomis, kurios neleidžia „Java“ klasei žinoti kitų klasių, kurios veikia kartu su ja virtualioje mašinoje. Šis gebėjimas „pažvelgti į vidų“ yra vadinamas savistaba. Pirmajame viešajame „Java“ leidime, vadinamame „Alpha3“, griežtos kalbos taisyklės, susijusios su klasės vidinių komponentų matomumu, galėjo būti apeinamos naudojant ObjectScope klasė. Tada beta versijos metu ObjectScope buvo pašalintas iš vykdymo laiko dėl saugumo problemų, daugelis žmonių paskelbė „Java“ netinkama „rimtai“ plėtrai.

Kodėl reikalinga savistaba, kad kalba būtų laikoma „sistemų“ kalba? Viena atsakymo dalis yra gana kasdieniška: norint pereiti nuo „nieko“ (t. Y. Neinicijuoto VM) prie „kažko“ (tai yra veikiančios „Java“ klasės), reikia, kad tam tikra sistemos dalis galėtų patikrinti klases, kurios bus bėk taip, kad suprastum, ką su jais daryti. Kanoninis šios problemos pavyzdys yra tiesiog toks: „Kaip programa, parašyta kalba, kuri negali atrodyti kito kalbos komponento viduje, pradeda vykdyti pirmąjį kalbos komponentą, kuris yra visų kitų komponentų vykdymo pradžios taškas? "

Yra du būdai, kaip susidoroti su „Java“ savistaba: klasės failų tikrinimas ir nauja atspindėjimo API, kuri yra „Java 1.1.x“ dalis. Apžvelgsiu abi technikas, tačiau šiame stulpelyje daugiausia dėmesio skirsiu pirmos klasės bylos patikrinimui. Būsimame stulpelyje apžvelgsiu, kaip atspindėjimo API išsprendžia šią problemą. (Nuorodas į visą šio stulpelio šaltinio kodą rasite skyriuje Ištekliai.)

Pažvelkite į mano bylas ...

1.0.x „Java“ leidimuose vienas didžiausių „Java“ vykdymo laiko karpų yra būdas, kuriuo „Java“ vykdomoji programa paleidžia programą. Kokia problema? Vykdymas pereinamas iš pagrindinės operacinės sistemos domeno („Win 95“, „SunOS“ ir pan.) Į „Java“ virtualiosios mašinos domeną. Rašyti eilutę "java „MyClass“ arg1 arg2"išjudina įvykių seriją, kurią visiškai interpretuoja" Java "vertėjas.

Kaip pirmasis įvykis, operacinės sistemos komandos apvalkalas įkelia „Java“ vertėją ir perduoda jam eilutę „MyClass arg1 arg2“ kaip savo argumentą. Kitas įvykis įvyksta, kai „Java“ vertėjas bando surasti pavadintą klasę Mano klasė viename iš kataloge nurodytų katalogų. Jei klasė randama, trečiasis įvykis yra metodo suradimas pavadintoje klasėje pagrindinis, kurio parašo modifikatoriai yra "public" ir "static", o kuris užima masyvą Stygos objektai kaip jos argumentas. Jei randamas šis metodas, sukonstruojamas pradinis siūlas ir naudojamas metodas. Tada „Java“ vertėjas konvertuoja „arg1 arg2“ į eilučių masyvą. Pasinaudojus šiuo metodu, visa kita yra gryna „Java“.

Tai viskas gerai ir gerai, išskyrus tai, kad pagrindinis metodas turi būti statiškas, nes vykdymo laikas negali jo iškviesti naudojant „Java“ aplinką, kurios dar nėra. Be to, reikia įvardyti pirmąjį metodą pagrindinis nes jokiu būdu negalima pasakyti vertėjui metodo pavadinimo komandinėje eilutėje. Net jei vertėjui pasakėte metodo pavadinimą, nėra jokio bendro būdo, kaip sužinoti, ar tai buvo jūsų nurodytoje klasėje. Galiausiai, nes pagrindinis metodas yra statinis, jūs negalite jo deklaruoti sąsajoje, o tai reiškia, kad negalite nurodyti tokios sąsajos:

viešoji sąsaja Programa {public void main (String args []); } 

Jei pirmiau nurodyta sąsaja buvo apibrėžta ir klasės ją įgyvendino, bent jau galėtumėte naudoti egzempliorius „Java“ operatorius, norėdamas nustatyti, ar turite programą, ar ne, ir taip nustatyti, ar ji buvo tinkama iškviesti iš komandinės eilutės. Esmė ta, kad jūs negalite (apibrėžti sąsajos), jo nebuvo (integruotas į „Java“ vertėją) ir todėl negalite (lengvai nustatykite, ar klasės failas yra programa). Taigi, ką tu gali padaryti?

Tiesą sakant, jūs galite padaryti nemažai, jei žinote, ko ieškoti ir kaip juo naudotis.

Klasės failų dekompiliavimas

„Java“ klasės failas yra neutralus architektūrai, o tai reiškia, kad tai yra tas pats bitų rinkinys, nesvarbu, ar jis įkeltas iš „Windows 95“, ar iš „Sun Solaris“. Tai taip pat labai gerai dokumentuota knygoje „Java“ virtualiosios mašinos specifikacija pateikė Lindholm ir Yellin. Klasės failo struktūra buvo sukurta iš dalies taip, kad ją būtų galima lengvai įkelti į SPARC adresų erdvę. Iš esmės klasės failą galima susieti su virtualaus adreso erdve, tada santykiniai rodikliai klasės viduje sutvarkomi ir presto! Jūs turėjote momentinę klasės struktūrą. Tai buvo mažiau naudinga „Intel“ architektūros mašinoms, tačiau paveldas leido klasės failo formatą lengvai suprasti ir dar lengviau suskaidyti.

1994 m. Vasarą dirbau „Java“ grupėje ir kūriau vadinamąjį „mažiausios privilegijos“ „Java“ saugos modelį. Aš ką tik baigiau suprasti, kad tai, ką aš iš tikrųjų norėjau padaryti, buvo pažvelgti į „Java“ klasės vidų, iškirpti tuos gabalus, kurių neleido dabartinis privilegijų lygis, ir tada įkelti rezultatą per pasirinktinį klasės krautuvą. Tada atradau, kad per pagrindinį laiką nebuvo nė vienos klasės, žinančios apie klasės bylų kūrimą. Kompiliatoriaus klasės medyje buvo versijų (kurios turėjo sugeneruoti klasės failus iš sukompiliuoto kodo), bet man buvo įdomiau sukurti ką nors manipuliuoti jau esamais klasės failais.

Aš pradėjau kurti „Java“ klasę, kuri galėtų suskaidyti „Java“ klasės failą, kuris buvo pateiktas įvesties sraute. Aš jam daviau mažiau nei originalų pavadinimą „ClassFile“. Šios klasės pradžia parodyta žemiau.

viešoji klasė „ClassFile“ {int magija; trumpas pagrindinis variantas; trumpas nepilnametis variantas; ConstantPoolInfo constantPool []; trumpa prieigaFlags; „ConstantPoolInfo thisClass“; „ConstantPoolInfo superClass“; „ConstantPoolInfo“ sąsajos []; „FieldInfo“ laukai []; „MethodInfo“ metodai []; „AttributeInfo“ atributai []; loginė reikšmė isValidClass = false; viešasis statinis finalas ACC_PUBLIC = 0x1; public static final int ACC_PRIVATE = 0x2; viešasis statinis finalas ACC_PROTECTED = 0x4; viešasis statinis galutinis int ACC_STATIC = 0x8; viešasis statinis galutinis int ACC_FINAL = 0x10; public static final int ACC_SYNCHRONIZED = 0x20; viešasis statinis galutinis int ACC_THREADSAFE = 0x40; public static final int ACC_TRANSIENT = 0x80; public static final int ACC_NATIVE = 0x100; viešasis statinis galutinis int ACC_INTERFACE = 0x200; viešas statinis galutinis int ACC_ABSTRACT = 0x400; 

Kaip matote, klasės egzempliorių kintamieji „ClassFile“ apibrėžti pagrindinius „Java“ klasės failo komponentus. Visų pirma, „Java“ klasės failo centrinė duomenų struktūra yra žinoma kaip pastovus telkinys. Kiti įdomūs klasės failų gabalai gauna savo klases: „MethodInfo“ metodams, „FieldInfo“ laukams (kurie yra kintamieji deklaracijos klasėje), „AttributeInfo“ laikyti klasės failų atributus ir konstantų rinkinį, kuris buvo paimtas tiesiogiai iš klasės failų specifikacijos, norint iššifruoti įvairius modifikatorius, taikomus lauko, metodo ir klasės deklaracijoms.

Pagrindinis šios klasės metodas yra skaityti, kuris naudojamas klasės failui nuskaityti iš disko ir sukurti naują „ClassFile“ iš duomenų. Kodas skaityti metodas parodytas žemiau. Įtraukiau aprašą į kodą, nes metodas paprastai būna gana ilgas.

1 viešasis loginis skaitymas („InputStream“) 2 metimai „IOException“ {3 DataInputStream di = new DataInputStream (in); 4 int skaičius; 5 6 magija = di.readInt (); 7 if (magija! = (Int) 0xCAFEBABE) {8 grįžti (klaidinga); 9} 10 11 majorVersion = di.readShort (); 12 minorVersion = di.readShort (); 13 skaičius = di.readShort (); 14 constantPool = naujas ConstantPoolInfo [skaičius]; 15 if (derinti) 16 System.out.println ("read (): Skaityti antraštę ..."); 17 constantPool [0] = nauja ConstantPoolInfo (); 18 for (int i = 1; i <constantPool.length; i ++) {19 konstantaPool [i] = nauja ConstantPoolInfo (); 20 if (! ConstantPool [i] .read (di)) {21 return (klaidingas); 22} 23 // Šie du tipai užima „dvi“ 24 lentelės vietas, jei ((konstantaPool [i] .type == ConstantPoolInfo.LONG) || 25 (konstantaPool [i] .type == ConstantPoolInfo.DOUBLE)) 26 i ++; 27} 

Kaip matote, aukščiau pateiktas kodas prasideda pirmiausia suvyniojus a „DataInputStream“ aplink kintamojo nurodytą įvesties srautą į. Be to, 6–12 eilutėse yra visa informacija, reikalinga norint nustatyti, ar kodas iš tikrųjų ieško galiojančios klasės bylos. Šią informaciją sudaro stebuklingas „slapukas“ 0xCAFEBABE, o versijų numeriai 45 ir 3 - atitinkamai pagrindinėms ir mažosioms reikšmėms. Toliau 13–27 eilutėse pastovus telkinys nuskaitomas į masyvą „ConstantPoolInfo“ objektai. Šaltinio kodas į „ConstantPoolInfo“ yra niekuo neišsiskiriantis - jis tiesiog nuskaito duomenis ir identifikuoja juos pagal savo tipą. Vėlesni elementai iš pastovaus baseino naudojami informacijai apie klasę rodyti.

Laikydamiesi pirmiau nurodyto kodo, skaityti metodas pakartotinai nuskaito pastovų telkinį ir „sutvarko“ pastovaus telkinio nuorodas, kurios nurodo kitus pastovaus telkinio elementus. Pataisymo kodas parodytas žemiau. Šis pataisymas yra būtinas, nes nuorodos paprastai yra indeksai į pastovų telkinį, todėl naudinga, kad tie indeksai jau būtų išspręsti. Tai taip pat suteikia skaitytojui galimybę sužinoti, ar klasės failas nėra sugadintas pastoviu baseino lygiu.

28 for (int i = 1; i 0) 32 constantPool [i] .arg1 = konstantaPool [konstantaPool [i] .index1]; 33 if (constantPool [i] .index2> 0) 34 constantPool [i] .arg2 = konstantaPool [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} 

Pirmiau pateiktame kode kiekvienas pastovaus telkinio įrašas naudoja indekso reikšmes, kad išsiaiškintų nuorodą į kitą pastovaus telkinio įrašą. Užpildžius 36 eilutę, visas baseinas pasirinktinai išmetamas.

Kai kodas nuskaito pastovųjį telkinį, klasės failas apibrėžia pirminę klasės informaciją: jos klasės pavadinimą, superklasės pavadinimą ir diegimo sąsajas. skaityti kodas nuskaito šias reikšmes, kaip parodyta žemiau.

32 accessFlags = di.readShort (); 33 34 thisClass = pastovus baseinas [di.readShort ()]; 35 „superClass“ = pastovus baseinas [di.readShort ()]; 36 if (derinti) 37 System.out.println ("read (): Skaityti klasės informaciją ..."); 38 39 / * 30 * Nurodykite visas šios klasės sąsajas 31 * / 32 count = di.readShort (); 33 if (skaičius! = 0) {34 if (derinti) 35 System.out.println („Klasės įrankiai“ + skaičius + „sąsajos“.); 36 sąsajos = nauja ConstantPoolInfo [skaičius]; 37 už (int i = 0; i <skaičius; i ++) {38 int iindex = di.readShort (); 39 if ((iindex constantPool.length - 1)) 40 return (klaidinga); 41 sąsaja [i] = constantPool [iindex]; 42 if (derinti) 43 System.out.println ("I" + i + ":" + sąsajos [i]); 44} 45} 46 if (derinti) 47 System.out.println ("read (): Skaityti sąsajos informaciją ..."); 

Kai šis kodas bus baigtas, skaityti metodas sukūrė gana gerą klasės struktūros idėją. Belieka surinkti lauko apibrėžimus, metodo apibrėžimus ir, svarbiausia, klasės failo atributus.

Klasės failo formatas suskirsto visas šias tris grupes į skyrių, susidedantį iš skaičiaus, po kurio seka ieškomo daikto egzempliorių skaičius. Taigi laukams klasės faile yra apibrėžtų laukų skaičius, o paskui - tiek daug laukų apibrėžimų. Žemėse rodomas kodas, kurį reikia nuskaityti laukuose.

48 skaičius = di.readShort (); 49 if (derinti) 50 System.out.println ("Šioje klasėje yra laukai" + skaičius + "."); 51 if (skaičius! = 0) {52 laukai = naujas „FieldInfo“ [skaičius]; 53 for (int i = 0; i <count; i ++) {54 laukai [i] = naujas „FieldInfo“); 55 if (! Laukai [i] .read (di, constantPool)) {56 return (klaidingas); 57} 58 if (derinti) 59 System.out.println ("F" + i + ":" + 60 laukų [i] .toString (constantPool)); 61} 62} 63 if (derinti) 64 System.out.println ("read (): Skaityti lauko informaciją ..."); 

Pirmiau pateiktas kodas pradedamas skaičiuojant skaičių 48 eilutėje, tada, kai skaičius nėra lygus nuliui, jis skaito naujuose laukuose naudodamas „FieldInfo“ klasė. „FieldInfo“ klasė tiesiog užpildo duomenis, kurie apibrėžia lauką „Java“ virtualioje mašinoje. Metodas ir atributus skaitantis kodas yra tas pats, paprasčiausiai pakeičiant nuorodas į „FieldInfo“ su nuorodomis į „MethodInfo“ arba „AttributeInfo“ kaip pridera. Šis šaltinis čia nėra įtrauktas, tačiau galite ieškoti šaltinio naudodamiesi nuorodomis, esančiomis toliau esančiame skyriuje „Ištekliai“.