Programmēšana

Java klases iekrāvēju pamati

Klases iekrāvēja koncepcija, kas ir viens no Java virtuālās mašīnas stūrakmeņiem, apraksta uzvedību, kā nosaukto klasi pārvērš bitos, kas atbildīgi par šīs klases ieviešanu. Tā kā pastāv klases iekrāvēji, Java darbināšanas laikam, darbinot Java programmas, nav jāzina nekas par failiem un failu sistēmām.

Ko dara klases iekrāvēji

Nodarbības tiek ieviestas Java vidē, kad uz tām jau darbojas klasē atsauces. Ir mazliet burvju, kas notiek, lai palaistu pirmo klasi (tieši tāpēc jums ir jāpaziņo galvenais () metode kā statiska, par argumentu ņemot virknes masīvu), bet, tiklīdz šī klase darbojas, turpmākos klases ielādes mēģinājumus veic klases iekrāvējs.

Vienkāršāk klases iekrāvējs izveido vienotu nosaukuma vietu klases ķermeņiem, uz kuriem atsaucas virknes nosaukums. Metodes definīcija ir:

Klase r = loadClass (String className, būla izšķirtspējaIt); 

Mainīgais className satur virkni, kuru saprot klases iekrāvējs un izmanto, lai unikāli identificētu klases ieviešanu. Mainīgais atrisinātTas ir karodziņš, kas norāda klases iekrāvējam, ka ir jāatrisina klases, uz kurām atsaucas šis klases nosaukums (tas ir, jāielādē arī visas atsauces klases).

Visās Java virtuālajās mašīnās ir viens klases iekrāvējs, kas ir iestrādāts virtuālajā mašīnā. Šo iegulto iekrāvēju sauc par pirmatnējās klases iekrāvēju. Tas ir nedaudz īpašs, jo virtuālā mašīna pieņem, ka tai ir piekļuve uzticamas klases ko VM var palaist bez verifikācijas.

Sākotnējās klases iekrāvējs īsteno noklusējuma versiju loadClass (). Tādējādi šis kods saprot, ka klases nosaukums java.lang.Object tiek saglabāts failā ar prefiksu java / lang / Object.class kaut kur klases ceļā. Šis kods arī īsteno gan klases ceļu meklēšanu, gan zip failu meklēšanu klasēm. Patiesi foršs veids, kā tas tiek veidots, ir tāds, ka Java var mainīt klases glabāšanas modeli, vienkārši mainot funkciju kopumu, kas īsteno klases iekrāvēju.

Rakodamies Java virtuālās mašīnas iekšās, atklāsi, ka pirmatnējās klases iekrāvējs galvenokārt tiek realizēts funkcijās FindClassFromClass un ResolveClass.

Tātad, kad tiek ielādētas klases? Ir tieši divi gadījumi: kad tiek izpildīts jaunais baitkods (piemēram, FooClassf = jauns FooClass ();) un kad baitkodi statiski atsaucas uz klasi (piemēram, Sistēma.ārā).

Nepirms klases iekrāvējs

- Nu un ko? jūs varētu jautāt.

Java virtuālajā mašīnā ir āķi, lai pirmatnējā vietā varētu izmantot lietotāja definētu klases iekrāvēju. Turklāt, tā kā lietotāja klases iekrāvējam ir pirmā plaisa pie klases nosaukuma, lietotājs var ieviest jebkādu skaitu interesantu klases krātuvju, no kurām vismazāk ir HTTP serveri, kas vispirms atnesa Java.

Tomēr ir izmaksas, jo klases iekrāvējs ir tik jaudīgs (piemēram, tas var aizstāt java.lang.Object ar savu versiju), Java klasēm, piemēram, sīklietotnēm, nav atļauts demonstrēt savus iekrāvējus. (Starp citu, to izpilda klases iekrāvējs.) Šī kolonna nebūs noderīga, ja mēģināt to paveikt ar sīklietotni, tikai ar lietojumprogrammu, kas darbojas no uzticamās klases krātuves (piemēram, vietējie faili).

Lietotāju klases iekrāvējs iegūst iespēju ielādēt klasi pirms sākotnējā klases iekrāvēja. Tāpēc tas var ielādēt klases ieviešanas datus no kāda alternatīva avota, tieši tā AppletClassLoader var ielādēt klases, izmantojot HTTP protokolu.

SimpleClassLoader veidošana

Klases iekrāvējs sākas ar to, ka tas ir java.lang.ClassLoader. Vienīgā abstraktā metode, kas jāievieš, ir loadClass (). Plūsma loadClass () ir šāds:

  • Pārbaudiet klases nosaukumu.
  • Pārbaudiet, vai pieprasītā klase jau ir ielādēta.
  • Pārbaudiet, vai klase ir "sistēmas" klase.
  • Mēģiniet ielādēt klasi no šīs klases iekrāvēja krātuves.
  • Definējiet VM klasi.
  • Atrisiniet klasi.
  • Atgrieziet klasi zvanītājam.

SimpleClassLoader tiek parādīts šādi, ar aprakstu par tā darbību mijiedarbojas ar kodu.

 publiski sinhronizēta Class loadClass (String className, boolean resolutionIt) izmet ClassNotFoundException {Class result; baits classData []; System.out.println (">>>>>> Load class:" + className); / * Pārbaudiet mūsu vietējo klašu kešatmiņu * / result = (Klase) class.get (className); if (rezultāts! = null) {System.out.println (">>>>>> atgriež kešatmiņā saglabāto rezultātu."); atgriešanās rezultāts; } 

Iepriekš redzamais kods ir loadClass metodi. Kā redzat, tas aizņem klases nosaukumu un meklē vietējo jaukšanas tabulu, kuru mūsu klases iekrāvējs uztur jau atgriezušās klases. Ir svarīgi šo hash tabulu turēt apkārt kopš jums jābūt katru reizi, kad jums tiek lūgts, atgrieziet to pašu klases objekta atsauci vienam un tam pašam klases nosaukumam. Pretējā gadījumā sistēma uzskatīs, ka ir divas dažādas klases ar tādu pašu nosaukumu un metīs a ClassCastException ikreiz, kad starp viņiem piešķirat objekta atsauci. Ir arī svarīgi saglabāt kešatmiņu, jo loadClass () metodi sauc rekursīvi, kad tiek atrisināta klase, un jums būs jāatgriež kešatmiņā saglabāts rezultāts, nevis jādzen to pēc citas kopijas.

/ * Pārbaudiet ar pirmatnējo klases iekrāvēju * / mēģiniet {result = super.findSystemClass (className); System.out.println (">>>>>> atgriešanās sistēmas klase (CLASSPATH)."); atgriešanās rezultāts; } catch (ClassNotFoundException e) {System.out.println (">>>>>> Nav sistēmas klase."); } 

Kā redzat iepriekš redzamajā kodā, nākamais solis ir pārbaudīt, vai pirmatnējais klases iekrāvējs var atrisināt šo klases nosaukumu. Šī pārbaude ir būtiska gan sistēmas prātam, gan drošībai. Piemēram, ja atgriezīsit savu java.lang.Object zvanītājam, tad šim objektam nav kopīgas superklases ar citiem objektiem! Sistēmas drošība var tikt apdraudēta, ja klases iekrāvējs atgriezīs savu vērtību java.lang.SecurityManager, kurai nebija tādas pašas pārbaudes kā reālajām.

 / * Mēģiniet to ielādēt no mūsu repozitorija * / classData = getClassImplFromDataBase (className); if (classData == null) {mest jaunu ClassNotFoundException (); } 

Pēc sākotnējām pārbaudēm mēs nonākam pie iepriekš minētā koda, kurā vienkāršais klases iekrāvējs iegūst iespēju ielādēt šīs klases ieviešanu. The SimpleClassLoader ir metode getClassImplFromDataBase () kas mūsu vienkāršajā piemērā tikai klases nosaukumam pievieno direktoriju "store \" un pievieno paplašinājumu ".impl". Es izvēlējos šo tehniku ​​piemērā, lai nebūtu runas par to, ka pirmatnējās klases iekrāvējs atrod mūsu klasi. Ņemiet vērā, ka sun.applet.AppletClassLoader koda bāzes URL no HTML lapas, kurā dzīvo sīklietotne, pievieno prefiksu nosaukumam un pēc tam veic HTTP saņemšanas pieprasījumu baitkodu ielādēšanai.

 / * Definējiet to (parsējiet klases failu) * / result = defineClass (classData, 0, classData.length); 

Ja klases ieviešana tika ielādēta, pirmspēdējais solis ir izsaukt defineClass () metode no java.lang.ClassLoader, ko var uzskatīt par klases verifikācijas pirmo soli. Šī metode ir ieviesta Java virtuālajā mašīnā un ir atbildīga par to, lai pārbaudītu, vai klases baiti ir likumīgi Java klases faili. Iekšēji defineClass metode aizpilda datu struktūru, kuru JVM izmanto, lai turētu klases. Ja klases dati ir nepareizi veidoti, šis zvans izraisīs a ClassFormatError jāmet.

 if (atrisinātIt) {atrisinātClass (rezultāts); } 

Pēdējā klases iekrāvējiem raksturīgā prasība ir zvanīt resolClass () ja būla parametrs atrisinātTas bija patiess. Šī metode veic divas lietas: Pirmkārt, tā liek jebkuras klases, uz kurām atsaucas šī klase, tieši ielādēt un izveidot šīs klases prototipa objektu; pēc tam tā aicina verificētāju veikt dinamisku šīs klases baitkodu likumības pārbaudi. Ja verifikācija neizdodas, šī metode izsauks a LinkageError, no kuriem visizplatītākais ir a VerifyError.

Ņemiet vērā, ka jebkurai klasei, kuru ielādēsit, atrisinātTas mainīgais vienmēr būs patiess. Tas notiek tikai tad, kad sistēma rekursīvi zvana loadClass () ka tas var iestatīt šo mainīgo kā nepatiesu, jo zina, ka prasītā klase jau ir atrisināta.

 class.put (className, rezultāts); System.out.println (">>>>>> Atgriež tikko ielādētu klasi."); atgriešanās rezultāts; } 

Pēdējais procesa solis ir ieliktās un atrisinātās klases saglabāšana mūsu hash tabulā, lai vajadzības gadījumā varētu to atgriezt vēlreiz, un pēc tam atgriezt Klase atsauce uz zvanītāju.

Protams, ja tas būtu tik vienkārši, nebūtu daudz vairāk par ko runāt. Faktiski ir divi jautājumi, kas būs jārisina klases iekrāvēju veidotājiem, drošība un sarunas ar klasēm, kuras ielādējis pielāgots klases iekrāvējs.

Drošības apsvērumi

Ikreiz, kad lietojumprogramma ielādē sistēmā patvaļīgas klases, izmantojot klases iekrāvēju, tiek apdraudēta jūsu lietojumprogrammas integritāte. Tas ir saistīts ar klases iekrāvēja jaudu. Pieņemsim brīdi, lai apskatītu vienu no veidiem, kā potenciālais ļaundaris varētu ielauzties jūsu pieteikumā, ja neesat piesardzīgs.

Mūsu vienkāršajā klases iekrāvējā, ja pirmatnējais klases iekrāvējs nevarēja atrast klasi, mēs to ielādējām no sava privātā repozitorija. Kas notiek, ja šajā repozitorijā ir klase java.lang.FooBar ? Nav nevienas klases nosaukta java.lang.FooBar, bet mēs to varētu instalēt, ielādējot to no klases repozitorija. Šī klase, ņemot vērā faktu, ka tai būtu piekļuve jebkuram ar pakotni aizsargātam mainīgajam java.lang pakete, var manipulēt ar dažiem sensitīviem mainīgajiem lielumiem, lai jaunākās klases varētu pasliktināt drošības pasākumus. Tāpēc jebkurš klases iekrāvējs ir viens no uzdevumiem aizsargāt sistēmas nosaukuma vietu.

Mūsu vienkāršajā klases iekrāvējā mēs varam pievienot kodu:

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

tieši pēc zvana uz findSystemClass virs. Šo paņēmienu var izmantot, lai aizsargātu jebkuru paketi, ja esat pārliecināts, ka ielādētajam kodam nekad nebūs iemesla ielādēt jaunu paketi kādā klasē.

Vēl viena riska joma ir tā, ka nodotajam vārdam jābūt pārbaudītam derīgam vārdam. Apsveriet naidīgu lietojumprogrammu, kuras klases nosaukums ir ".. \ .. \ .. \ .. \ netscape \ temp \ xxx.class", ko tā vēlas ielādēt. Skaidrs, ka, ja klases iekrāvējs vienkārši uzrādīja šo vārdu mūsu vienkāršotajam failu sistēmas ielādētājam, tas varētu ielādēt klasi, kuru mūsu lietojumprogramma faktiski negaidīja. Tādējādi, pirms meklēt mūsu pašu klases krātuvē, ieteicams uzrakstīt metodi, kas pārbauda jūsu klases nosaukumu integritāti. Pēc tam izsauciet šo metodi tieši pirms došanās meklēt krātuvē.

Saskarnes izmantošana, lai mazinātu plaisu

Otra neintuitīva problēma, kas saistīta ar darbu ar klases iekrāvējiem, ir nespēja nomest objektu, kas tika izveidots no ielādētas klases, sākotnējā klasē. Atgrieztais objekts ir jāraida, jo parastais pielāgotās klases iekrāvēja lietojums ir kaut kas līdzīgs:

 CustomClassLoader ccl = jauns CustomClassLoader (); Objekts o; C klase; c = ccl.loadClass ("dažiNewClass"); o = c.newInstance (); ((SomeNewClass) o) .someClassMethod (); 

Tomēr jūs nevarat spēlēt o uz SomeNewClass jo tikai pielāgotās klases iekrāvējs "zina" par tikko ielādēto jauno klasi.

Tam ir divi iemesli. Pirmkārt, Java virtuālās mašīnas klases tiek uzskatītas par lietām, ja tām ir vismaz viens kopīgs klases rādītājs. Tomēr klasēm, kuras ielādējuši divi dažādi klases iekrāvēji, būs divas dažādas klases norādes un kopīgas klases nebūs (izņemot java.lang.Object parasti). Otrkārt, ideja par pielāgotu klases iekrāvēju ir klašu ielāde pēc lietojumprogramma ir izvietota, tāpēc lietojumprogramma nezina prioritāti par klasēm, kuras tā ielādēs. Šī dilemma tiek atrisināta, piešķirot gan lietojumprogrammai, gan ielādētajai klasei kopīgu klasi.

Šīs kopējās klases izveidei ir divi veidi, vai nu ielādētajai klasei jābūt klases apakšklasei, kuru lietojumprogramma ir ielādējusi no uzticamās krātuves, vai arī ielādētajai klasei jāievieš saskarne, kas tika ielādēta no uzticamās krātuves. Tādējādi ielādētajai klasei un klasei, kurai nav kopīgas pilnas pielāgotās klases iekrāvēja nosaukuma vietas, ir kopīga klase. Piemērā es izmantoju interfeisu ar nosaukumu LocalModule, lai gan tikpat viegli jūs varētu padarīt šo klasi un apakšklasi.

Labākais pirmās tehnikas piemērs ir tīmekļa pārlūkprogramma. Java definētā klase, kuru īsteno visi sīklietotnes, ir java.applet.Applet. Kad klasi ielādē AppletClassLoader, objekta eksemplārs, kas tiek izveidots, tiek pārsūtīts uz Aplets. Ja šis sastāvs izdosies tajā() metodi sauc. Savā piemērā es izmantoju otro tehniku, interfeisu.

Spēlējot ar piemēru

Noslēdzot piemēru, esmu izveidojis vēl pāris

.java

failus. Šie ir:

 publiskā saskarne LocalModule {/ * Sāciet moduli * / void start (String opcija); } 
$config[zx-auto] not found$config[zx-overlay] not found