Ohjelmointi

Java-luokan kuormaajien perusteet

Luokkakuormaajakonsepti, joka on yksi Java-virtuaalikoneen kulmakivistä, kuvaa käyttäytymistä, kun nimetty luokka muunnetaan kyseisen luokan toteuttamisesta vastaaviksi biteiksi. Koska luokkakuormaajia on olemassa, Java-ajoajan ei tarvitse tietää mitään tiedostoista ja tiedostojärjestelmistä Java-ohjelmia suoritettaessa.

Mitä luokan kuormaajat tekevät

Luokat tuodaan Java-ympäristöön, kun niihin viitataan nimellä jo käynnissä olevassa luokassa. Ensimmäisen luokan käyntiin saattamiseksi jatkuu vähän taikuutta (minkä vuoksi sinun on ilmoitettava main () menetelmä staattisena, kun argumenttina käytetään merkkijonotaulukkoa), mutta kun kyseinen luokka on käynnissä, luokan kuormaaja yrittää tulevaisuudessa yrittää ladata luokkia.

Yksinkertaisimmillaan luokkakuormaaja luo tasaisen nimiavaruuden luokkaryhmistä, joihin merkkijonon nimi viittaa. Menetelmän määritelmä on:

Luokka r = loadClass (String className, looginen resoluutioIt); 

Muuttuja luokan nimi sisältää merkkijonon, jonka luokan kuormaaja ymmärtää ja jota käytetään yksilöimään luokan toteutus. Muuttuja resolIt on lippu, joka kertoo luokan lataajalle, että tällä luokkanimellä viitatut luokat on ratkaistava (ts. myös kaikki viitatut luokat on ladattava).

Kaikissa Java-virtuaalikoneissa on yksi luokan kuormaaja, joka on upotettu virtuaalikoneeseen. Tätä upotettua kuormaajaa kutsutaan alkuluokan kuormaajaksi. Se on jonkin verran erityinen, koska virtuaalikone olettaa, että sillä on pääsy luotetut luokat jota virtuaalikone voi käyttää ilman vahvistusta.

Alkuperäinen luokan kuormaaja toteuttaa oletusarvoisen toteutuksen loadClass (). Siten tämä koodi ymmärtää luokan nimen java.lang.objekti on tallennettu tiedostoon, jonka etuliite on java / lang / Object.class jossain luokan polussa. Tämä koodi toteuttaa myös sekä luokan polun etsinnän että luokkien zip-tiedostojen etsimisen. Todella hieno asia tämän suunnittelussa on se, että Java voi muuttaa luokkatallennusmalliaan yksinkertaisesti muuttamalla toimintoja, jotka toteuttavat luokan kuormaajan.

Kaivamalla Java-virtuaalikoneen suolistossa huomaat, että alkuluokan kuormaaja on toteutettu ensisijaisesti toiminnoissa FindClassFromClass ja ResolveClass.

Joten milloin luokat ladataan? On täsmälleen kaksi tapausta: kun uusi tavukoodi suoritetaan (esimerkiksi FooClassf = uusi FooClass ();) ja kun tavukoodit viittaavat staattisesti luokkaan (esimerkiksi Järjestelmä.ulos).

Ei-alkukantinen luokan kuormaaja

"Mitä sitten?" saatat kysyä.

Java-virtuaalikoneessa on koukut, joiden avulla käyttäjän määrittämää luokan kuormaajaa voidaan käyttää alkuperäisen koneen sijasta. Lisäksi, koska käyttäjäluokan latauslaite saa ensimmäisen halkeaman luokan nimessä, käyttäjä pystyy toteuttamaan minkä tahansa määrän mielenkiintoisia luokkavarastoja, joista vähäisimmät ovat HTTP-palvelimet - jotka saivat Java alusta alkaen.

Kustannukset ovat kuitenkin, koska luokan kuormaaja on niin tehokas (esimerkiksi se voi korvata java.lang.objekti omalla versiollaan), Java-luokat, kuten sovelmat, eivät saa instantisoida omia kuormaajiaan. (Muuten, tämän suorittaa pakollinen luokan latauslaite.) Tästä sarakkeesta ei ole hyötyä, jos yrität tehdä näitä asioita sovelmalla, vain luotetusta luokan arkistosta käynnissä olevan sovelluksen (kuten paikalliset tiedostot) kanssa.

Käyttäjäluokkakuormaaja saa mahdollisuuden ladata luokan ennen kuin alkuluokan kuormaaja tekee. Tämän vuoksi se voi ladata luokan toteutustiedot jostakin vaihtoehtoisesta lähteestä, mikä on AppletClassLoader voi ladata luokkia HTTP-protokollan avulla.

SimpleClassLoaderin rakentaminen

Luokkakuormaaja aloittaa olemalla alaluokka java.lang.ClassLoader. Ainoa abstrakti menetelmä, joka on toteutettava, on loadClass (). Virtaus loadClass () on seuraava:

  • Vahvista luokan nimi.
  • Tarkista, onko pyydetty luokka jo ladattu.
  • Tarkista, onko luokka "järjestelmä" -luokka.
  • Yritä hakea luokka tämän luokan lataajatietovarastosta.
  • Määritä virtuaalikoneen luokka.
  • Ratkaise luokka.
  • Palauta luokka soittajalle.

SimpleClassLoader näkyy seuraavasti, ja sen kuvaukset koodin kanssa.

 julkinen synkronoitu luokan loadClass (merkkijono luokan nimi, boolen resoluutioIt) heittää ClassNotFoundException {luokan tulos; tavu classData []; System.out.println (">>>>>> Kuormaluokka:" + luokanNimi); / * Tarkista luokkien paikallinen välimuisti * / result = (Luokka) class.get (className); if (tulos! = null) {System.out.println (">>>>>> palauttaa välimuistissa olevan tuloksen."); paluutulos; } 

Yllä oleva koodi on loadClass menetelmä. Kuten näette, se vie luokan nimen ja etsii paikallista hajautustaulukkoa, jonka luokkakuormaajamme ylläpitää jo palauttamiaan luokkia. On tärkeää pitää tämä hash-taulukko ympärilläsi sinusta lähtien on pakko palauta sama luokan objektiviite samalle luokan nimelle aina, kun sitä pyydetään. Muuten järjestelmä uskoo, että on olemassa kaksi erilaista luokkaa samalla nimellä, ja heittää a ClassCastException aina kun määrität objektiviitteen niiden välille. On myös tärkeää pitää välimuisti, koska loadClass () Metodia kutsutaan rekursiivisesti, kun luokkaa ratkaistaan, ja sinun on palautettava välimuistissa oleva tulos sen sijaan, että ajoisit sitä toisen kopion saamiseksi.

/ * Tarkista alkuluokan latauslaitteesta * / kokeile {result = super.findSystemClass (className); System.out.println (">>>>>> palaava järjestelmäluokka (CLASSPATH: ssa)."); paluutulos; } catch (ClassNotFoundException e) {System.out.println (">>>>>> Ei järjestelmäluokka."); } 

Kuten yllä olevasta koodista näet, seuraava askel on tarkistaa, pystyykö alkuluokan lataaja ratkaisemaan tämän luokan nimen. Tämä tarkistus on välttämätön järjestelmän järjen ja turvallisuuden kannalta. Esimerkiksi, jos palautat oman java.lang.objekti soittajalle, niin tämä esine ei jaa mitään yhteistä superluokkaa minkään muun kohteen kanssa! Järjestelmän turvallisuus voi vaarantua, jos luokan kuormaaja palauttaa oman arvonsa java.lang.SecurityManager, jolla ei ollut samoja tarkastuksia kuin todellisella.

 / * Yritä ladata se arkistostamme * / classData = getClassImplFromDataBase (className); if (classData == null) {heitä uusi ClassNotFoundException (); } 

Ensimmäisten tarkastusten jälkeen pääsemme yllä olevaan koodiin, jossa yksinkertainen luokan lataaja saa mahdollisuuden ladata tämän luokan toteutus. SimpleClassLoader on menetelmä getClassImplFromDataBase () joka yksinkertaisessa esimerkissämme vain etuliittaa hakemiston "store \" luokan nimeen ja liittää laajennuksen ".impl". Valitsin tämän tekniikan esimerkissä, jotta ei olisi kysymystä siitä, että alkukurssikuormaaja löytää luokkamme. Huomaa, että sun.applet.AppletClassLoader lisää koodin perustan URL-osoitteen HTML-sivulta, jolla sovelma asuu, ja lisää sitten HTTP-hakupyynnön tavukoodien noutamiseksi.

 / * Määritä se (jäsennä luokkatiedosto) * / result = defineClass (classData, 0, classData.length); 

Jos luokan toteutus ladattiin, viimeinen vaihe on soittaa defineClass () menetelmä java.lang.ClassLoader, jota voidaan pitää luokan todentamisen ensimmäisenä vaiheena. Tämä menetelmä on toteutettu Java-virtuaalikoneessa ja on vastuussa siitä, että luokan tavut ovat laillisia Java-luokan tiedostoja. Sisäisesti defineClass menetelmä täyttää tietorakenteen, jota JVM käyttää luokkien pitämiseen. Jos luokan tiedot ovat väärin, tämä kutsu aiheuttaa a ClassFormatError heittää.

 if (resoluuti) {ratkaisuluokka (tulos); } 

Viimeinen luokan kuormaajakohtainen vaatimus on soittaa ResolClass () jos looginen parametri resolIt oli totta. Tämä menetelmä tekee kahta asiaa: Ensinnäkin se saa kaikki luokat, joihin tämä luokka viittaa, suoraan ladattavaksi ja luodaan tämän luokan prototyyppi-objekti; sitten se kehottaa todentajaa tekemään dynaamisen tarkistuksen tämän luokan tavukoodien oikeutuksesta. Jos vahvistus epäonnistuu, tämä menetelmäpuhelu heittää a LinkageError, joista yleisin on a VerifyError.

Huomaa, että minkä tahansa luokan lataat, resolIt muuttuja on aina totta. Vasta kun järjestelmä soittaa rekursiivisesti loadClass () että se voi asettaa tämän muuttujan vääräksi, koska se tietää, että luokka, jota se pyytää, on jo ratkaistu.

 class.put (luokan nimi, tulos); System.out.println (">>>>>> Palautetaan vasta ladattu luokka."); paluutulos; } 

Viimeinen vaihe prosessissa on tallentaa ladattu ja ratkaistu luokka hash-taulukkoon, jotta voimme tarvittaessa palauttaa sen uudelleen ja palauttaa sitten Luokka viittaus soittajaan.

Tietysti, jos se olisi niin yksinkertaista, ei olisi paljon muuta puhuttavaa. Itse asiassa luokan kuormaajarakentajien on käsiteltävä kahta asiaa, turvallisuus ja puhuminen mukautetun luokan kuormaajan lataamien luokkien kanssa.

Turvallisuusnäkökohdat

Aina kun sovelluksesi lataa mielivaltaisia ​​luokkia järjestelmään luokkakuormaajan kautta, sovelluksesi eheys on vaarassa. Tämä johtuu luokan kuormaajan voimasta. Katsotaan hetki yhteen tapoista, joilla potentiaalinen konna voi murtautua sovellukseesi, jos et ole varovainen.

Jos yksinkertainen luokan kuormaajamme ei löytänyt luokkaa, ladasimme sen yksityisestä arkistostamme. Mitä tapahtuu, kun tietovarasto sisältää luokan java.lang.FooBar ? Nimettyä luokkaa ei ole java.lang.FooBar, mutta voimme asentaa yhden lataamalla sen luokan arkistosta. Tämä luokka, koska sillä olisi pääsy mihin tahansa paketilla suojattuun muuttujaan java.lang paketti, voi manipuloida joitain arkaluontoisia muuttujia, jotta myöhemmät luokat kumoavat turvatoimet. Siksi minkä tahansa luokan kuormaajan yksi tehtävistä on suojata järjestelmän nimiavaruus.

Yksinkertaiseen luokan kuormaajamme voimme lisätä koodin:

 if (className.startsWith ("java."))) heittää newClassNotFoundException (); 

heti puhelun jälkeen findSystemClass edellä. Tätä tekniikkaa voidaan käyttää suojaamaan kaikkia paketteja, joissa olet varma, että ladatulla koodilla ei ole koskaan syytä ladata uutta luokkaa johonkin pakettiin.

Toinen riskialue on, että välitetyn nimen on oltava vahvistettu kelvollinen nimi. Tarkastellaan vihamielistä sovellusta, joka käytti luokan nimeä ".. \ .. \ .. \ .. \ netscape \ temp \ xxx.class" luokkanimenä, jonka halusi ladata. On selvää, että jos luokan kuormaaja yksinkertaisesti esitti tämän nimen yksinkertaistetulle tiedostojärjestelmälataimellemme, se saattaa ladata luokan, jota sovelluksemme ei todellakaan odottanut. Siksi, ennen kuin etsit omasta luokkavarastostamme, kannattaa kirjoittaa menetelmä, joka varmistaa luokkien nimien eheyden. Soita sitten tähän menetelmään juuri ennen kuin menet etsimään arkistostasi.

Käyttöliittymän avulla kuilun kurominen

Toinen ei-intuitiivinen ongelma luokkakuormaajien kanssa on kyvyttömyys heittää ladatusta luokasta luotua objektia alkuperäiseen luokkaansa. Palautettu objekti on valettava, koska mukautetun luokan kuormaajan tyypillinen käyttö on jotain:

 CustomClassLoader ccl = uusi CustomClassLoader (); Kohde o; Luokka c; c = ccl.loadClass ("someNewClass"); o = c.uusi asia (); ((SomeNewClass) o) .someClassMethod (); 

Et voi kuitenkaan lähettää o että JotkutUusi luokka koska vain mukautettu luokan kuormaaja "tietää" juuri lataamastaan ​​uudesta luokasta.

Tähän on kaksi syytä. Ensinnäkin Java-virtuaalikoneen luokkia pidetään heitettävinä, jos niillä on vähintään yksi yhteinen luokan osoittin. Kahden eri luokkakuormaajan lataamilla luokilla on kuitenkin kaksi erilaista luokan osoitinta eikä yhtään yhteistä luokkaa (paitsi java.lang.objekti yleensä). Toiseksi ajatus mukautetun luokan kuormaajasta on luokkien lataaminen jälkeen sovellus on otettu käyttöön, joten sovellus ei tiedä prioriteettia luokista, jotka se lataa. Tämä ongelma ratkaistaan ​​antamalla sekä sovellukselle että ladatulle luokalle yhteinen luokka.

Tämän yhteisen luokan luomisessa on kaksi tapaa, joko ladatun luokan on oltava luokan alaluokka, jonka sovellus on ladannut luotettavasta arkistostaan, tai ladatun luokan on toteutettava luotettava tietovarastosta ladattu käyttöliittymä. Näin ladatulla luokalla ja luokassa, joka ei jaa mukautetun luokan kuormaajan täydellistä nimiavaruutta, on yhteinen luokka. Käytän esimerkissä käyttöliittymää nimeltä LocalModule, vaikka voisit yhtä helposti tehdä tästä luokan ja aliluokan.

Paras esimerkki ensimmäisestä tekniikasta on verkkoselain. Java: n määrittelemä luokka, jonka kaikki sovelmat toteuttavat, on java.applet.Applet. Kun luokka ladataan AppletClassLoader, luotu objektin ilmentymä lähetetään Appletti. Jos tämä näyttelijä onnistuu sen sisällä() menetelmää kutsutaan. Käytän esimerkissä toista tekniikkaa, käyttöliittymää.

Pelaaminen esimerkillä

Esimerkin täydentämiseksi olen luonut pari muuta

.java

tiedostot. Nämä ovat:

 julkinen käyttöliittymä LocalModule {/ * Käynnistä moduuli * / void start (String-vaihtoehto); }