Programmēšana

Apskatiet Java klases

Laipni lūdzam šomēnes "Java dziļumā". Viens no agrākajiem Java izaicinājumiem bija tas, vai tā var darboties kā spējīga "sistēmu" valoda. Jautājuma pamatā bija Java drošības funkcijas, kas neļauj Java klasei uzzināt citas klases, kas darbojas blakus tai virtuālajā mašīnā. Tiek saukta šī spēja "ielūkoties iekšā" pašpārbaude. Pirmajā publiskajā Java izlaidumā, kas pazīstams kā Alpha3, stingros valodas noteikumus attiecībā uz klases iekšējo komponentu redzamību varēja apiet, izmantojot ObjectScope klasē. Tad beta laikā, kad ObjectScope drošības apsvērumu dēļ tika noņemts no izpildes laika, daudzi cilvēki pasludināja Java par nederīgu "nopietnai" attīstībai.

Kāpēc pašpārbaude ir nepieciešama, lai valodu varētu uzskatīt par "sistēmu" valodu? Viena atbildes daļa ir diezgan ikdienišķa: lai nokļūtu no "nekā" (tas ir, neinicializēta VM) uz "kaut ko" (tas ir, darbojas Java klase), ir nepieciešams, lai kāda sistēmas daļa varētu pārbaudīt klases, palaist tā, lai saprastu, ko tieši ar viņiem darīt. Šīs problēmas kanoniskais piemērs ir vienkārši šāds: "Kā programma, kas uzrakstīta valodā, kas nevar izskatīties citas valodas sastāvdaļas iekšpusē, sāk izpildīt pirmo valodas komponentu, kas ir visu pārējo komponentu izpildes sākumpunkts? "

Ir divi veidi, kā tikt galā ar introspekciju Java: klases failu pārbaude un jaunā refleksijas API, kas ir Java 1.1.x sastāvdaļa. Es apskatīšu abus paņēmienus, bet šajā slejā es koncentrēšos uz pirmās klases lietu pārbaudi. Nākamajā slejā es apskatīšu, kā refleksijas API atrisina šo problēmu. (Saites uz pilnu šīs kolonnas avota kodu ir pieejamas sadaļā Resursi.)

Ieskaties dziļi manos failos ...

Java 1.0.x laidienos viena no lielākajām kārpām Java izpildes laikā ir veids, kā Java izpildāmā programma startē programmu. Kāda ir problēma? Izpilde tiek pārsūtīta no resursdatora operētājsistēmas domēna (Win 95, SunOS un tā tālāk) uz Java virtuālās mašīnas domēnu. Rakstīt līniju "java MyClass arg1 arg2"aktivizē virkni notikumu, kurus Java tulks pilnībā kodē.

Kā pirmo notikumu operētājsistēmas komandu apvalks ielādē Java tulku un kā argumentu tam nodod virkni "MyClass arg1 arg2". Nākamais notikums notiek, kad Java tulks mēģina atrast klasi ar nosaukumu Mana klase vienā no klases ceļā norādītajiem direktorijiem. Ja klase ir atrasta, trešais notikums ir metodes atrašana nosauktajā klasē galvenais, kuras paraksta modifikatori ir "publisks" un "statisks", un kas aizņem masīvu Stīga objekti kā tās arguments. Ja šī metode ir atrasta, tiek konstruēta pirmatnējā vītne un tiek izsaukta metode. Tad Java tulks pārveido "arg1 arg2" virkņu masīvā. Kad šī metode ir izmantota, viss pārējais ir tīrs Java.

Tas viss ir labi, izņemot to, ka galvenais metodei jābūt statiskai, jo izpildes laiks to nevar izsaukt ar Java vidi, kuras vēl nav. Tālāk ir jānosauc pirmā metode galvenais jo nekādi nevar tulkotājam pateikt metodes nosaukumu komandrindā. Pat ja jūs tulkotājam teicāt metodes nosaukumu, nav vispārēja veida, kā uzzināt, vai tas vispār ir jūsu nosauktajā klasē. Visbeidzot, jo galvenais metode ir statiska, jūs to nevarat deklarēt saskarnē, un tas nozīmē, ka jūs nevarat norādīt šādu saskarni:

publiskā saskarne Lietojumprogramma {public void main (String args []); } 

Ja iepriekšminētā saskarne tika definēta un klases to ieviesa, tad vismaz jūs varētu izmantot instanceof operatoru Java, lai noteiktu, vai jums ir bijusi programma, vai nē, un tādējādi noskaidrotu, vai tā ir piemērota izsaukšanai no komandrindas. Apakšējā līnija ir tāda, ka jūs nevarat (definēt saskarni), tas nebija (iebūvēts Java tulkā), un tāpēc jūs nevarat (viegli noteikt, vai klases fails ir lietojumprogramma). Tātad, ko jūs varat darīt?

Patiesībā jūs varat darīt diezgan daudz, ja zināt, ko meklēt un kā to izmantot.

Klases failu dekompilēšana

Java klases fails ir neitrāls arhitektūrai, kas nozīmē, ka tas ir tas pats bitu kopums neatkarīgi no tā, vai tas ir ielādēts no Windows 95 vai Sun Solaris mašīnas. Tas ir arī ļoti labi dokumentēts grāmatā Java virtuālās mašīnas specifikācija autori Lindholm un Yellin. Klases faila struktūra daļēji tika veidota tā, lai to varētu viegli ielādēt SPARC adreses telpā. Būtībā klases failu varēja kartēt virtuālās adreses telpā, pēc tam relatīvie rādītāji klases iekšpusē tika salaboti un presto! Jums bija tūlītēja klases struktūra. Tas bija mazāk noderīgi Intel arhitektūras mašīnās, taču mantojums ļāva klases faila formātu viegli saprast un pat vieglāk sadalīt.

1994. gada vasarā es strādāju Java grupā un veidoju tā dēvēto Java "mazāko privilēģiju" drošības modeli. Es tikko biju pabeidzis saprast, ka tas, ko es patiešām vēlējos darīt, bija ieskatīties Java klases iekšienē, izgriezt tos gabalus, kurus neatļāva pašreizējais privilēģiju līmenis, un pēc tam ielādēt rezultātu caur pielāgotu klases iekrāvēju. Toreiz es atklāju, ka galvenajā izpildes laikā nav nevienas klases, kas zinātu par klases failu izveidi. Kompilatora klases kokā bija versijas (kurām bija jāveido klases faili no sastādītā koda), bet mani vairāk interesēja kaut ko veidot, lai manipulētu ar jau esošiem klases failiem.

Es sāku ar Java klases izveidi, kas varētu sadalīt Java klases failu, kas tam tika parādīts ievades straumē. Es tam devu mazāk oriģinālo nosaukumu ClassFile. Šīs klases sākums ir parādīts zemāk.

publiskā klase ClassFile {int maģija; īss majorVersion; īss nepilngadīgaisVersija; ConstantPoolInfo constantPool []; īsa piekļuveFlags; ConstantPoolInfo thisClass; ConstantPoolInfo superClass; ConstantPoolInfo saskarnes []; FieldInfo lauki []; MethodInfo metodes []; AttributeInfo atribūti []; būla vērtība irValidClass = false; publiskais statiskais fināls int ACC_PUBLIC = 0x1; public static final int ACC_PRIVATE = 0x2; publiskais statiskais fināls int ACC_PROTECTED = 0x4; publiskais statiskais fināls int ACC_STATIC = 0x8; public static final int ACC_FINAL = 0x10; publiskais statiskais fināls int ACC_SYNCHRONIZED = 0x20; publiskais statiskais fināls int ACC_THREADSAFE = 0x40; public static final int ACC_TRANSIENT = 0x80; public static final int ACC_NATIVE = 0x100; public static final int ACC_INTERFACE = 0x200; public static final int ACC_ABSTRACT = 0x400; 

Kā redzat, klases mainīgie mainīgie ClassFile definēt Java klases faila galvenos komponentus. Jo īpaši Java klases faila centrālā datu struktūra ir pazīstama kā pastāvīgā kopa. Citi interesanti klases failu fragmenti iegūst savas klases: MethodInfo par metodēm, FieldInfo laukiem (kas ir mainīgās deklarācijas klasē), AttributeInfo turēt klases faila atribūtus un konstantu kopu, kas ņemts tieši no klases failu specifikācijas, lai atšifrētu dažādos modifikatorus, kas attiecas uz lauka, metodes un klases deklarācijām.

Šīs klases galvenā metode ir lasīt, ko izmanto klases faila nolasīšanai no diska un jauna izveidošanai ClassFile piemēram, no datiem. Programmas kods lasīt metode ir parādīta zemāk. Es esmu iejaucis aprakstu ar kodu, jo metode mēdz būt diezgan gara.

1 publisks būla lasījums (InputStream in) 2 metieni IOException {3 DataInputStream di = new DataInputStream (in); 4 int skaits; 5 6 maģija = di.readInt (); 7 if (maģija! = (Int) 0xCAFEBABE) {8 atgriešanās (nepatiesa); 9} 10 11 majorVersion = di.readShort (); 12 minorVersion = di.readShort (); 13 skaits = di.readShort (); 14 constantPool = jauns ConstantPoolInfo [skaits]; 15 if (atkļūdošana) 16 System.out.println ("read (): Read header ..."); 17 constantPool [0] = jauns ConstantPoolInfo (); 18 par (int i = 1; i <konstantsPool.length; i ++) {19 konstantsPool [i] = jauns ConstantPoolInfo (); 20 if (! ConstantPool [i] .read (di)) {21 return (false); 22} 23 // Šie divi tipi 24. tabulā aizņem "divus" punktus, ja ((konstantsPool [i] .type == ConstantPoolInfo.LONG) || 25 (konstantsPool [i] .type == ConstantPoolInfo.DOUBLE)) 26 i ++; 27} 

Kā redzat, iepriekš minētais kods vispirms sākas ar a DataInputStream ap ievades straumi, uz kuru atsaucas mainīgais iekšā. Turklāt 6. līdz 12. rindā ir visa informācija, kas nepieciešama, lai noteiktu, vai kods patiešām meklē derīgu klases failu. Šī informācija sastāv no burvju sīkfaila 0xCAFEBABE un versijas numuriem 45 un 3 attiecīgi galvenajām un mazākajām vērtībām. Pēc tam no 13. līdz 27. rindai nemainīgais kopa tiek nolasīta masīvā ConstantPoolInfo objektiem. Avota kods ConstantPoolInfo ir neievērojams - tas vienkārši nolasa datus un identificē tos, pamatojoties uz to tipu. Vēlāk elementi no nemainīgā baseina tiek izmantoti, lai parādītu informāciju par klasi.

Ievērojot iepriekš minēto kodu, lasīt metode atkārtoti skenē nemainīgo kopu un "salabo" atsauces nemainīgajā kopā, kas attiecas uz citiem konstanta kopas elementiem. Labojuma kods ir parādīts zemāk. Šī labošana ir nepieciešama, jo atsauces parasti ir nemainīgā kopas indeksi, un ir lietderīgi, ja šie rādītāji jau ir atrisināti. Tas arī pārbauda, ​​vai lasītājs zina, ka klases fails nav bojāts pastāvīgā kopas līmenī.

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

Iepriekš minētajā kodā katrs pastāvīgā pūla ieraksts izmanto indeksa vērtības, lai noskaidrotu atsauci uz citu nemainīgu kopas ierakstu. Pabeidzot 36. rindu, pēc izvēles tiek izmests viss baseins.

Kad kods ir skenēts aiz nemainīgā kopas, klases failā tiek definēta primārā klases informācija: tās klases nosaukums, augstākās klases nosaukums un interfeisu ieviešana. The lasīt kodu skenē šīs vērtības, kā parādīts zemāk.

32 accessFlags = di.readShort (); 33 34 thisClass = konstantsPool [di.readShort ()]; 35 superClass = konstants baseins [di.readShort ()]; 36 if (atkļūdošana) 37 System.out.println ("read (): Read class info ..."); 38 39 / * 30 * Norādiet visas saskarnes, ko ievieš šī klase 31 * / 32 count = di.readShort (); 33 if (skaits! = 0) {34 if (atkļūdot) 35 System.out.println ("Klase ievieš" + skaits + "saskarnes."); 36 saskarnes = new ConstantPoolInfo [skaits]; 37 par (int i = 0; i <skaits; i ++) {38 int iindex = di.readShort (); 39 if ((iindex konstantePool.length - 1)) 40 atgriešanās (false); 41 saskarne [i] = constantPool [iindex]; 42 if (atkļūdošana) 43 System.out.println ("I" + i + ":" + saskarnes [i]); 44} 45} 46 if (atkļūdot) 47 System.out.println ("lasīt (): lasīt saskarnes informāciju ..."); 

Kad šis kods ir pabeigts, lasīt metode ir izveidojusi diezgan labu ideju par klases struktūru. Atliek tikai apkopot lauku definīcijas, metožu definīcijas un, iespējams, pats galvenais, klases faila atribūtus.

Klases faila formāts sadala katru no šīm trim grupām sadaļā, kas sastāv no skaitļa, kam seko šis meklētās lietas gadījumu skaits. Tātad laukiem klases failā ir definēto lauku skaits un pēc tam tikpat daudz lauku definīciju. Laukos skenējamais kods ir parādīts zemāk.

48 skaits = di.readShort (); 49 if (atkļūdošana) 50 System.out.println ("Šajā klasē ir lauki" + skaits + "."); 51 if (skaits! = 0) {52 lauki = jauns FieldInfo [skaits]; 53 par (int i = 0; i <skaits; i ++) {54 lauki [i] = jauns FieldInfo (); 55 if (! Lauki [i] .lasīt (di, konstantsPool)) {56 atgriezties (nepatiesa); 57} 58 if (atkļūdošana) 59 System.out.println ("F" + i + ":" + 60 lauki [i] .toString (constantPool)); 61} 62} 63 if (atkļūdot) 64 System.out.println ("read (): Lasīt lauka informāciju ..."); 

Iepriekšminētais kods sākas ar skaitīšanas skaitīšanu 48. rindā, bet, kamēr skaitlis nav nulle, tas tiek lasīts jaunos laukos, izmantojot FieldInfo klasē. The FieldInfo klase vienkārši aizpilda datus, kas nosaka lauku Java virtuālajai mašīnai. Metode un atribūti, kas jāizlasa, ir vienāds, vienkārši aizstājot atsauces uz FieldInfo ar atsaucēm uz MethodInfo vai AttributeInfo pēc vajadzības. Šis avots šeit nav iekļauts, tomēr jūs varat apskatīt avotu, izmantojot saites zemāk esošajā sadaļā Resursi.

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