Programmering

Grunnleggende om Java-klasselastere

Klasselaster-konseptet, en av hjørnesteinene i den virtuelle Java-maskinen, beskriver oppførselen til å konvertere en navngitt klasse til biter som er ansvarlige for å implementere den klassen. Fordi klasselastere eksisterer, trenger ikke Java-kjøretiden å vite noe om filer og filsystemer når du kjører Java-programmer.

Hva klasselastere gjør

Klasser blir introdusert i Java-miljøet når de blir referert til etter navn i en klasse som allerede kjører. Det er litt magi som fortsetter for å få første klasse i gang (det er grunnen til at du må erklære hoved() metoden som statisk, tar en strengmatrise som et argument), men når den klassen kjører, gjøres fremtidige forsøk på å laste klasser av klasselasteren.

På sin enkleste måte skaper en klasselaster en flat navneplass for klassekropper som det refereres til med et strengnavn. Metodedefinisjonen er:

Klasse r = loadClass (String className, boolean resolutionIt); 

Variabelen klassenavn inneholder en streng som er forstått av klasselaster og som brukes til å identifisere en klasseimplementering på en unik måte. Variabelen løse det er et flagg som forteller klasselaster at klasser det refereres til med dette klassenavnet skal løses (det vil si at enhver referert klasse også skal lastes).

Alle virtuelle Java-maskiner inkluderer en klasselaster som er innebygd i den virtuelle maskinen. Denne innebygde lasteren kalles primordial class loader. Det er noe spesielt fordi den virtuelle maskinen antar at den har tilgang til et lager av klarerte klasser som kan kjøres av VM uten bekreftelse.

Den primordial class loader implementerer standardimplementeringen av loadClass (). Dermed forstår denne koden at klassenavnet java.lang.Objekt lagres i en fil med prefikset java / lang / Object.class et sted i klassebanen. Denne koden implementerer også både klassesøk og ser på zip-filer for klasser. Det veldig kule med måten dette er designet på er at Java kan endre sin klasselagringsmodell ved å endre funksjonssettet som implementerer klasselasteren.

Når du graver rundt i den virtuelle Java-maskinen, vil du oppdage at primordial class loader først og fremst er implementert i funksjonene FinnClassFromClass og ResolveClass.

Så når er klassene lastet? Det er nøyaktig to tilfeller: når den nye bytekoden kjøres (for eksempel FooClassf = ny FooClass ();) og når bytekodene viser en statisk referanse til en klasse (for eksempel System.ute).

En ikke-primordial klasselaster

"Hva så?" spør du kanskje.

Den virtuelle Java-maskinen har kroker i den, slik at en brukerdefinert klasselaster kan brukes i stedet for den primære. Videre, siden brukerklasselaster får første sprekk på klassenavnet, er brukeren i stand til å implementere et hvilket som helst antall interessante klasselager, ikke minst HTTP-servere - som fikk Java i første omgang.

Det er imidlertid en pris fordi klasselaster er så kraftig (for eksempel kan den erstatte java.lang.Objekt med sin egen versjon), har Java-klasser som applets ikke lov til å instansiere sine egne lastere. (Dette håndheves for øvrig av klasselasteren.) Denne kolonnen vil ikke være nyttig hvis du prøver å gjøre dette med en applet, bare med et program som kjører fra det klarerte klassedepotet (for eksempel lokale filer).

En brukerklasselaster får sjansen til å laste en klasse før den første klasselasteren gjør det. På grunn av dette kan den laste data for klasseimplementering fra en hvilken som helst alternativ kilde, slik er det AppletClassLoader kan laste klasser ved hjelp av HTTP-protokollen.

Bygg en SimpleClassLoader

En klasselaster starter med å være en underklasse av java.lang.ClassLoader. Den eneste abstrakte metoden som må implementeres er loadClass (). Flyten av loadClass () er som følgende:

  • Bekreft kursnavnet.
  • Sjekk om den valgte klassen allerede er lastet.
  • Sjekk om klassen er en "system" -klasse.
  • Forsøk å hente klassen fra denne klasselasterens depot.
  • Definer klassen for VM.
  • Løs klassen.
  • Returner klassen til den som ringer.

SimpleClassLoader vises som følger, med beskrivelser av hva den gjør ispedd koden.

 offentlig synkronisert Class loadClass (String className, boolean resolutionIt) kaster ClassNotFoundException {Class result; byte classData []; System.out.println (">>>>>> Lasteklasse:" + className); / * Sjekk vår lokale hurtigbuffer av klasser * / resultat = (Klasse) klasser.get (klassenavn); hvis (resultat! = null) {System.out.println (">>>>>> returnerer hurtigbufret resultat."); returresultat; } 

Koden ovenfor er den første delen av loadClass metode. Som du kan se, tar det et klassenavn og søker i en lokal hash-tabell som klasselasteren vår opprettholder av klasser den allerede har returnert. Det er viktig å holde dette hasjbordet siden deg returner samme klasseobjektreferanse for samme klassenavn hver gang du blir bedt om det. Ellers vil systemet tro at det er to forskjellige klasser med samme navn og vil kaste et ClassCastException når du tildeler en objektreferanse mellom dem. Det er også viktig å holde cache fordi loadClass () metoden kalles rekursivt når en klasse blir løst, og du må returnere det bufrede resultatet i stedet for å jage det ned for en ny kopi.

/ * Sjekk med primordial class loader * / prøv {result = super.findSystemClass (className); System.out.println (">>>>>> returnerende systemklasse (i CLASSPATH)."); returresultat; } catch (ClassNotFoundException e) {System.out.println (">>>>>> Ikke en systemklasse."); } 

Som du kan se i koden ovenfor, er neste trinn å sjekke om den primordial class loader kan løse dette klassenavnet. Denne kontrollen er viktig for både sunnhet og sikkerhet i systemet. For eksempel hvis du returnerer din egen forekomst av java.lang.Objekt til den som ringer, så vil dette objektet ikke dele noen felles superklasse med noe annet objekt! Sikkerheten til systemet kan bli kompromittert hvis klasselasteren din returnerte sin egen verdi av java.lang.SecurityManager, som ikke hadde de samme kontrollene som den virkelige.

 / * Prøv å laste den fra depotet vårt * / classData = getClassImplFromDataBase (className); hvis (classData == null) {kast ny ClassNotFoundException (); } 

Etter de første kontrollene kommer vi til koden ovenfor, der den enkle klasselasteren får muligheten til å laste en implementering av denne klassen. De SimpleClassLoader har en metode getClassImplFromDataBase () som i vårt enkle eksempel bare prefikser katalogen "butikk" til klassenavnet og legger til utvidelsen ".impl". Jeg valgte denne teknikken i eksemplet, slik at det ikke ville være snakk om at urklasselaster skulle finne vår klasse. Merk at sun.applet.AppletClassLoader prefikser kodebase-URL fra HTML-siden der en applet lever til navnet, og deretter får en HTTP-forespørsel om å hente bytekodene.

 / * Definer det (analyser klassefilen) * / resultat = defineClass (classData, 0, classData.length); 

Hvis klasseimplementeringen ble lastet, er det nest siste trinnet å ringe defineClass () metode fra java.lang.ClassLoader, som kan betraktes som det første trinnet i klasseverifisering. Denne metoden er implementert i den virtuelle Java-maskinen og er ansvarlig for å verifisere at klassebytes er en lovlig Java-klassefil. Internt, den defineClass metoden fyller ut en datastruktur som JVM bruker for å holde kurs. Hvis klassedataene er feil, vil dette anropet føre til a ClassFormatError å bli kastet.

 hvis (resolveIt) {løseClass (resultat); } 

Det siste klasses lastespesifikke krav er å ringe resolClass () hvis den boolske parameteren løse det var sant. Denne metoden gjør to ting: For det første fører den til at alle klasser som det er referert til av denne klassen eksplisitt lastes inn og et prototypeobjekt for denne klassen blir opprettet; deretter påkaller den verifisereren å utføre dynamisk verifisering av legitimiteten til bytekodene i denne klassen. Hvis bekreftelsen mislykkes, vil denne metodeanropet kaste et LinkageError, den vanligste av disse er en VerifyError.

Merk at for alle klasser du vil laste, løse det variabel vil alltid være sant. Det er først når systemet rekursivt ringer loadClass () at den kan sette denne variabelen falsk fordi den vet at klassen den ber om allerede er løst.

 classes.put (className, result); System.out.println (">>>>>> Returnerer nylig lastet klasse."); returresultat; } 

Det siste trinnet i prosessen er å lagre klassen vi har lastet inn og løst i hash-tabellen vår, slik at vi kan returnere den om nødvendig, og deretter returnere Klasse referanse til den som ringer.

Selvfølgelig, hvis det var så enkelt, ville det ikke være mye mer å snakke om. Faktisk er det to problemer som klasselasterbyggere må håndtere, sikkerhet og snakk med klasser lastet av den tilpassede klasselasteren.

Sikkerhetshensyn

Når du har et program som laster vilkårlige klasser inn i systemet gjennom klasselaster, er applikasjonens integritet i fare. Dette skyldes klasselasterens kraft. La oss ta en stund å se på en av måtene en potensiell skurk kan bryte inn i søknaden din hvis du ikke er forsiktig.

I vår enkle klasselaster, hvis den første klasselaster ikke kunne finne klassen, lastet vi den fra vårt private depot. Hva skjer når datalageret inneholder klassen java.lang.FooBar ? Det er ingen klasse oppkalt java.lang.FooBar, men vi kunne installere en ved å laste den fra klasselageret. Denne klassen, i kraft av det faktum at den ville ha tilgang til hvilken som helst pakkebeskyttet variabel i java.lang pakke, kan manipulere noen følsomme variabler slik at senere klasser kan undergrave sikkerhetstiltak. Derfor er en av jobbene til enhver klasselaster å beskytt systemnavnet.

I vår enkle klasselaster kan vi legge til koden:

 hvis (className.startsWith ("java.")) kaster newClassNotFoundException (); 

like etter samtalen til findSystemClass ovenfor. Denne teknikken kan brukes til å beskytte en hvilken som helst pakke der du er sikker på at den lastede koden aldri vil ha grunn til å laste en ny klasse inn i en eller annen pakke.

Et annet risikoområde er at det passerte navnet må være et bekreftet gyldig navn. Tenk på et fiendtlig program som brukte klassenavnet ".. \ .. \ .. \ .. \ netscape \ temp \ xxx.class" som klassenavnet det ønsket å bli lastet inn. Det er klart at hvis klasselaster bare presenterte dette navnet til vår forenklede filsystemlaster, kan dette laste en klasse som faktisk ikke forventes av applikasjonen vår. Før du søker i vårt eget lager av klasser, er det derfor en god ide å skrive en metode som verifiserer integriteten til klassenavnene dine. Ring deretter den metoden like før du går til å søke i depotet ditt.

Bruker et grensesnitt for å bygge bro over gapet

Det andre ikke-intuitive problemet med å jobbe med klasselastere er manglende evne til å kaste et objekt som ble opprettet fra en lastet klasse til sin opprinnelige klasse. Du må kaste gjenstanden som returneres fordi den typiske bruken av en tilpasset klasselaster er omtrent som:

 CustomClassLoader ccl = ny CustomClassLoader (); Objekt o; Klasse c; c = ccl.loadClass ("someNewClass"); o = c.newInstance (); ((SomeNewClass) o) .someClassMethod (); 

Du kan imidlertid ikke kaste o til SomeNewClass fordi bare den tilpassede klasselasteren "vet" om den nye klassen den nettopp har lastet inn.

Det er to grunner til dette. For det første anses klassene i den virtuelle Java-maskinen å kunne kastes hvis de har minst en felles klassepeker. Imidlertid vil klasser lastet av to forskjellige klasselastere ha to forskjellige klassepekere og ingen klasser til felles (bortsett fra java.lang.Objekt som oftest). For det andre er ideen bak å ha en tilpasset klasselaster å laste klasser etter applikasjonen er distribuert slik at applikasjonen ikke kjenner et priory om klassene den vil laste inn. Dette dilemmaet løses ved å gi både applikasjonen og den lastede klassen en klasse til felles.

Det er to måter å opprette denne vanlige klassen på, enten den lastede klassen må være en underklasse av en klasse som applikasjonen har lastet fra sitt pålitelige depot, eller den lastede klassen må implementere et grensesnitt som ble lastet fra det pålitelige depotet. På denne måten har den lastede klassen og klassen som ikke deler hele navneplassen til den tilpassede klasselasteren, en klasse til felles. I eksemplet bruker jeg et grensesnitt som heter LocalModule, selv om du like gjerne kan gjøre dette til en klasse og underklasse det.

Det beste eksemplet på den første teknikken er en nettleser. Klassen definert av Java som er implementert av alle applets er java.applet.Applet. Når en klasse er lastet av AppletClassLoaderblir objektforekomsten som opprettes til en forekomst av Applet. Hvis denne rollebesetningen lykkes i det() metoden kalles. I mitt eksempel bruker jeg den andre teknikken, et grensesnitt.

Leker med eksemplet

For å avrunde eksemplet har jeg laget et par til

.java

filer. Disse er:

 offentlig grensesnitt LocalModule {/ * Start modulen * / ugyldig start (strengalternativ); } 
$config[zx-auto] not found$config[zx-overlay] not found