Programavimas

„Java“ klasės krautuvų pagrindai

„Class loader“ koncepcija, vienas iš „Java“ virtualiosios mašinos kertinių akmenų, apibūdina elgseną, pavadintą klasę pavertus bitais, atsakingais už tos klasės įgyvendinimą. Kadangi egzistuoja klasės krautuvai, „Java“ vykdymo laikui nereikia nieko žinoti apie failus ir failų sistemas, kai veikia „Java“ programos.

Ką daro klasės krautuvai

Klasės įvedamos į „Java“ aplinką, kai jau veikiančioje klasėje į jas nurodoma pagal pavadinimą. Norint paleisti pirmąją klasę, reikia šiek tiek magijos (todėl jūs turite deklaruoti pagrindinis () metodas kaip statinis, kaip argumentą priimant eilutės masyvą), tačiau kai ta klasė veikia, būsimus bandymus įkelti klases bando atlikti klasės krautuvas.

Paprasčiausiai klasės krautuvas sukuria plokščią klasės kūnų pavadinimų erdvę, į kurią nurodomas eilutės pavadinimas. Metodo apibrėžimas yra:

R klasė = loadClass (String className, loginis sprendimasIt); 

Kintamasis className yra eilutė, kurią supranta klasės krautuvas ir naudojama unikaliai identifikuoti klasės įgyvendinimą. Kintamasis išspręstiTai yra vėliava, nurodanti klasės krautuvui, kad klasės, nurodytos šiuo klasės pavadinimu, turėtų būti išspręstos (ty taip pat turėtų būti įkelta bet kuri nurodyta klasė).

Visose „Java“ virtualiose mašinose yra vienas klasės krautuvas, įterptas į virtualiąją mašiną. Šis įterptasis krautuvas vadinamas pirminės klasės krautuvu. Tai šiek tiek ypatinga, nes virtuali mašina daro prielaidą, kad ji turi prieigą prie patikimos klasės kurį VM gali valdyti be patikrinimo.

Pirminės klasės krautuvas įgyvendina numatytąjį „loadClass“ (). Taigi šis kodas supranta, kad klasės pavadinimas java.lang.Object yra saugomas faile su priešdėliu java / lang / Object.class kažkur klasės kelyje. Šis kodas taip pat įgyvendina klasių kelio paiešką ir klasių zip failų paiešką. Tikrai šaunu apie tai, kaip tai suprojektuota, yra tai, kad „Java“ gali pakeisti savo klasės saugyklos modelį paprasčiausiai pakeisdama funkcijų rinkinį, įgyvendinantį klasės krautuvą.

Kasinėdami „Java“ virtualiosios mašinos vidurius, sužinosite, kad pirmykštės klasės krautuvas pirmiausia įgyvendinamas funkcijose „FindClassFromClass“ ir „ResolveClass“.

Taigi, kada pakraunamos klasės? Yra tiksliai du atvejai: kai vykdomas naujas baitų kodas (pvz., „FooClass“f = naujas „FooClass“ ();) ir kai baitų kodai daro statinę nuorodą į klasę (pavyzdžiui, Sistema.išėjo).

Ne pirmykštės klasės krautuvas

"Tai kas?" galite paklausti.

„Java“ virtuali mašina turi kabliukus, kad vietoj pirmapradžio būtų galima naudoti vartotojo apibrėžtą klasės krautuvą. Be to, kadangi vartotojo klasės krautuvas pirmą kartą susiduria su klasės pavadinimo įtrūkimais, vartotojas gali įdiegti bet kokį skaičių įdomių klasės saugyklų, iš kurių svarbiausia yra HTTP serveriai, kurie pirmiausia išvedė „Java“.

Tačiau tai kainuoja, nes klasės krautuvas yra toks galingas (pavyzdžiui, jis gali pakeisti java.lang.Object su savo versija), „Java“ klasėms, pavyzdžiui, programėlėms, neleidžiama kurti savo krautuvų. (Beje, tai vykdo klasės krautuvas.) Šis stulpelis nebus naudingas, jei bandote tai padaryti naudodami programėlę, tik naudodamiesi programa, veikiančia iš patikimos klasės saugyklos (pvz., Vietiniai failai).

Vartotojo klasės krautuvas gauna galimybę įkelti klasę anksčiau nei pirmykštė klasės krautuvas. Dėl to jis gali įkelti klasės diegimo duomenis iš kažkokio alternatyvaus šaltinio, būtent taip „AppletClassLoader“ gali įkelti klases naudodamas HTTP protokolą.

„SimpleClassLoader“ kūrimas

Klasės krautuvas prasideda nuo to, kad yra poklasis java.lang.ClassLoader. Vienintelis abstraktus metodas, kurį reikia įgyvendinti, yra „loadClass“ (). Srautas „loadClass“ () yra toks:

  • Patikrinkite kurso pavadinimą.
  • Patikrinkite, ar prašoma klasė jau įkelta.
  • Patikrinkite, ar klasė yra „sistemos“ klasė.
  • Pabandykite paimti klasę iš šios klasės krautuvo saugyklos.
  • Apibrėžkite VM klasę.
  • Išspręskite klasę.
  • Grąžinkite klasę skambinančiajam.

„SimpleClassLoader“ rodomas taip, o aprašymai apie tai, ką ji daro, įsiterpia į kodą.

 viešai sinchronizuota klasės loadClass (String className, boolean resolutionIt) meta ClassNotFoundException {Class rezultatas; baitas classData []; System.out.println (">>>>>> Įkėlimo klasė:" + klasėsPavadinimas); / * Patikrinkite mūsų vietinę klasių talpyklą * / result = (Klasė) class.get (className); if (rezultatas! = null) {System.out.println (">>>>>> grąžinamas talpyklos rezultatas."); grąžinimo rezultatas; } 

Aukščiau pateiktas kodas yra pirmasis loadClass metodas. Kaip matote, jis paima klasės pavadinimą ir ieško vietinės maišos lentelės, kurią mūsų klasės krautuvas prižiūri jau grąžintas klases. Svarbu laikyti šią maišos lentelę nuo jūsų turi kiekvieną kartą, kai to paprašoma, grąžinkite tą pačią klasės objekto nuorodą tam pačiam klasės pavadinimui. Priešingu atveju sistema manys, kad yra dvi skirtingos klasės tuo pačiu pavadinimu, ir meta a „ClassCastException“ kai tarp jų priskiriate objekto nuorodą. Taip pat svarbu saugoti talpyklą, nes „loadClass“ () metodas vadinamas rekursyviai, kai klasė yra išspręsta, ir jums reikės grąžinti talpykloje išsaugotą rezultatą, o ne vytis jį dėl kitos kopijos.

/ * Patikrinkite pradiniu klasės krautuvu * / pabandykite {result = super.findSystemClass (className); System.out.println (">>>>>> grįžtanti sistemos klasė (CLASSPATH)."); grąžinimo rezultatas; } catch (ClassNotFoundException e) {System.out.println (">>>>>> Ne sistemos klasė."); } 

Kaip matote aukščiau pateiktame kode, kitas žingsnis yra patikrinti, ar pirminis klasės krautuvas gali išspręsti šį klasės pavadinimą. Šis patikrinimas yra būtinas sistemos protingumui ir saugumui užtikrinti. Pvz., Jei grąžinate savo java.lang.Object skambinančiajam, tada šis objektas nesidalys jokia bendra superklase su jokiu kitu objektu! Sistemos saugumui gali kilti pavojus, jei jūsų klasės krautuvas grąžins savo vertę java.lang.SecurityManager, kuri neturėjo tokių pačių patikrinimų kaip tikroji.

 / * Pabandykite jį įkelti iš mūsų saugyklos * / classData = getClassImplFromDataBase (className); if (classData == null) {mesti naują ClassNotFoundException (); } 

Po pirminių patikrinimų mes prieiname aukščiau esantį kodą, kuriame paprastas klasės krautuvas gauna galimybę įkelti šios klasės diegimą. „SimpleClassLoader“ turi metodą getClassImplFromDataBase () kuris mūsų paprastame pavyzdyje tik priskiria katalogą „store \“ prie klasės pavadinimo ir prideda plėtinį „.impl“. Pasirinkau šią techniką pavyzdyje, kad nekiltų klausimas apie tai, kaip pirmykščių klasių krautuvas ras mūsų klasę. Atkreipkite dėmesį, kad sun.applet.AppletClassLoader prideda kodo bazės URL iš HTML puslapio, kuriame gyvena programėlė, prie pavadinimo ir tada pateikia HTTP gavimo užklausą, kad gautų baitų kodus.

 / * Apibrėžkite ją (analizuokite klasės failą) * / result = defineClass (classData, 0, classData.length); 

Jei klasės diegimas buvo įkeltas, priešpaskutinis žingsnis yra paskambinti defineClass () metodas nuo java.lang.ClassLoader, kurį galima laikyti pirmuoju klasės patikrinimo žingsniu. Šis metodas įdiegtas „Java“ virtualioje mašinoje ir yra atsakingas už patikrinimą, ar klasės baitai yra teisėtas „Java“ klasės failas. Viduje defineClass metodas užpildo duomenų struktūrą, kurią JVM naudoja klasėms laikyti. Jei klasės duomenys yra netinkamai suformuoti, šis skambutis sukels a „ClassFormatError“ būti išmestam.

 if (resolutIt) {spręstiClass (rezultatas); } 

Paskutinis klasės krautuvų reikalavimas yra paskambinti resolClass () jei loginis parametras išspręstiTai buvo tiesa. Šis metodas daro du dalykus: Pirma, jis priverčia bet kokias klases, į kurias yra nurodyta ši klasė, aiškiai įkelti ir sukurti šios klasės objekto prototipą; tada ji kviečia tikrintoją atlikti dinaminį šios klasės baitekodų teisėtumo patikrinimą. Jei patvirtinti nepavyksta, šio metodo iškvietimas suteiks a „LinkageError“, iš kurių dažniausiai yra a „VerifyError“.

Atminkite, kad bet kuriai klasei, kurią įkelsite, išspręstiTai kintamasis visada bus teisingas. Tik tada, kai sistema rekursyviai skambina „loadClass“ () kad jis gali nustatyti šį kintamąjį klaidingą, nes žino, kad jo prašoma klasė jau yra išspręsta.

 class.put (className, rezultatas); System.out.println (">>>>>> Grąžinama naujai įkelta klasė."); grąžinimo rezultatas; } 

Paskutinis proceso žingsnis yra klasėje, kurią įkėlėme ir išsprendėme, saugoti savo maišos lentelę, kad prireikus galėtume ją grąžinti dar kartą, o tada grąžinti Klasė nuoroda į skambinantįjį.

Žinoma, jei tai būtų taip paprasta, nebūtų apie ką daugiau kalbėti. Tiesą sakant, yra du klausimai, kuriuos turės spręsti klasių krautuvų kūrėjai, - apsauga ir pokalbiai su klasėmis, kurias įkrauna pasirinktinis klasės krautuvas.

Saugumo sumetimai

Kiekvieną kartą, kai turite programą, į klasių krautuvą į sistemą įkeliančias savavališkas klases, kyla pavojus jūsų programos vientisumui. Taip yra dėl klasės krautuvo galios. Pažvelkime šiek tiek laiko į tai, kaip potencialus piktadarys galėtų įsiskverbti į jūsų paraišką, jei nesate atsargūs.

Mūsų paprastame klasės krautuve, jei pradinis klasės krautuvas nerado klasės, įkėlėme jį iš savo asmeninės saugyklos. Kas atsitiks, kai toje saugykloje yra klasė java.lang.FooBar ? Nėra pavadintos klasės java.lang.FooBar, bet mes galėtume jį įdiegti įkeldami iš klasės saugyklos. Ši klasė, turėdama prieigą prie bet kurio pakete saugomo kintamojo java.lang paketą, gali manipuliuoti kai kuriais jautriais kintamaisiais, kad vėlesnės klasės galėtų sugadinti saugumo priemones. Todėl vienas iš bet kurios klasės krautuvo darbų yra apsaugoti sistemos pavadinimo erdvę.

Savo paprastame klasės krautuve galime pridėti kodą:

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

iškart po skambučio „findSystemClass“ aukščiau. Ši technika gali būti naudojama apsaugoti bet kokį paketą, kai esate tikri, kad įkeliamas kodas niekada neturės priežasties į kurį nors paketą įkelti naujos klasės.

Kita rizikos sritis yra ta, kad perduotas vardas turi būti patvirtintas galiojantis vardas. Apsvarstykite priešišką programą, kurios klasės pavadinimas, kurį ji norėjo įkelti, naudojo „.. \ .. \ .. \ .. \ netscape \ temp \ xxx.class“. Aišku, jei klasės krautuvas paprasčiausiai pateiks šį pavadinimą mūsų supaprastintam failų sistemos pakrovėjui, tai gali įkelti klasę, kurios iš tikrųjų nesitikėjo mūsų programa. Taigi prieš ieškant mūsų pačių klasių saugykloje, verta parašyti metodą, kuris patikrintų jūsų klasių pavadinimų vientisumą. Tada iškvieskite šį metodą prieš pat eidami ieškoti į saugyklą.

Sąsajos naudojimas spragai įveikti

Antra ne intuityvi problema dirbant su klasės krautuvais yra nesugebėjimas perkelti objekto, kuris buvo sukurtas iš pakrautos klasės, į pradinę klasę. Turite perduoti grąžintą objektą, nes įprastas pasirinktinės klasės krautuvo naudojimas yra kažkas panašaus:

 „CustomClassLoader“ ccl = naujas „CustomClassLoader“ (); Objektas o; C klasė; c = ccl.loadClass ("someNewClass"); o = c.newInstance (); ((SomeNewClass) o) .someClassMethod (); 

Tačiau jūs negalite perduoti o į SomeNewClass nes tik pasirinktos klasės krautuvas „žino“ apie ką tik įkeltą naują klasę.

Tam yra dvi priežastys. Pirma, „Java“ virtualiosios mašinos klasės laikomos perduotomis, jei turi bent vieną bendrą klasės rodyklę. Tačiau dviejų skirtingų klasių krautuvų pakrautose klasėse bus du skirtingi klasių rodyklės ir nebus bendrų klasių (išskyrus java.lang.Object paprastai). Antra, užsakovo klasės krautuvo idėja yra klasių įkėlimas po to programa yra dislokuota, todėl programa nežino pirmumo apie klases, kurias ji įkels. Ši dilema išsprendžiama suteikiant tiek programai, tiek pakrautai klasei bendrą klasę.

Yra du šios bendros klasės sukūrimo būdai: arba įkelta klasė turi būti klasės, kurią programa įkėlė iš patikimos saugyklos, poklasis, arba įkelta klasė turi įdiegti sąsają, įkeltą iš patikimos saugyklos. Tokiu būdu įkelta klasė ir klasė, kuri nesidalija visa pasirinktinės klasės krautuvo pavadinimo erdve, turi bendrą klasę. Pavyzdyje naudoju sąsają, pavadintą „LocalModule“, nors jūs taip pat lengvai galėtumėte padaryti šią klasę ir subklasę.

Geriausias pirmosios technikos pavyzdys yra interneto naršyklė. „Java“ apibrėžta klasė, kurią įgyvendina visos programėlės, yra java.applet.Applet. Kai klasę pakrauna „AppletClassLoader“, sukurtas objekto egzempliorius perduodamas į Applet. Jei šiam būriui pavyks inicijuoti () metodas vadinamas. Savo pavyzdyje naudoju antrąją techniką - sąsają.

Žaidžia su pavyzdžiu

Apibendrindamas pavyzdį sukūriau dar porą

.java

failus. Šitie yra:

 viešoji sąsaja „LocalModule“ {/ * Paleiskite modulį * / void start („String“ parinktis); } 
$config[zx-auto] not found$config[zx-overlay] not found