Klasselæssere i Java

1. Introduktion til klasselæssere

Klasselæssere er ansvarlige for indlæser Java-klasser under kørsel dynamisk til JVM (Virtuel Java-maskine). De er også en del af JRE (Java Runtime Environment). Derfor behøver JVM ikke at vide om de underliggende filer eller filsystemer for at køre Java-programmer takket være klasselæssere.

Disse Java-klasser er ikke indlæst i hukommelsen på én gang, men når det kræves af et program. Det er her klasselæssere kommer ind i billedet. De er ansvarlige for at indlæse klasser i hukommelsen.

I denne vejledning skal vi tale om forskellige typer indbyggede klasselæssere, hvordan de fungerer og en introduktion til vores egen brugerdefinerede implementering.

2. Typer af indbyggede klasselæssere

Lad os starte med at lære, hvordan forskellige klasser indlæses ved hjælp af forskellige klasselæssere ved hjælp af et simpelt eksempel:

public void printClassLoaders () kaster ClassNotFoundException {System.out.println ("Classloader i denne klasse:" + PrintClassLoader.class.getClassLoader ()); System.out.println ("Classloader of Logging:" + Logging.class.getClassLoader ()); System.out.println ("Classloader af ArrayList:" + ArrayList.class.getClassLoader ()); }

Når den ovenstående metode udføres, udskrives:

Klasselæsser af denne klasse: [email protected] Class loader of Logging: [email protected] Class loader of ArrayList: null

Som vi kan se, er der tre forskellige klasselæssere her; applikation, udvidelse og bootstrap (vises som nul).

Applikationsklasseindlæseren indlæser klassen, hvor eksempelmetoden er indeholdt. En applikations- eller systemklasselæsser indlæser vores egne filer i klassestien.

Derefter indlæser udvidelsen en Logning klasse. Forlængerklasse-læssere indlæser klasser, der er en udvidelse af standardkernen til Java-klasser.

Endelig indlæser bootstrap en ArrayList klasse. En bootstrap eller primordial class loader er forælder til alle de andre.

Vi kan dog se, at den sidste ud, for ArrayList det vises nul i output. Dette skyldes, at bootstrap-klasselæsseren er skrevet i native kode, ikke Java - så den vises ikke som en Java-klasse. På grund af denne grund vil adfærden for bootstrap-klasselæsseren variere på tværs af JVM'er.

Lad os nu diskutere mere detaljeret om hver af disse klasselæssere.

2.1. Bootstrap Class Loader

Java-klasser indlæses af en forekomst af java.lang.ClassLoader. Klasselæssere er dog selv klasser. Derfor er spørgsmålet, hvem der indlæser java.lang.ClassLoader sig selv?

Det er her bootstrap eller primordial class loader kommer ind i billedet.

Det er hovedsageligt ansvarlig for at indlæse JDK interne klasser, typisk rt.jar og andre kernebiblioteker placeret i $ JAVA_HOME / jre / lib-bibliotek. Derudover Bootstrap-klasselæsseren fungerer som forælder til alle de andre ClassLoader tilfælde.

Denne bootstrap klasse loader er en del af kernen JVM og er skrevet i native kode som påpeget i ovenstående eksempel. Forskellige platforme kan have forskellige implementeringer af netop denne klasselæsser.

2.2. Forlængerklasser

Det extension class loader er et barn af bootstrap class loader og tager sig af indlæsning af udvidelser af standardkerne Java-klasser så den er tilgængelig for alle applikationer, der kører på platformen.

Forlængerklasseindlæser belastes normalt fra JDK-udvidelsesmappen $ JAVA_HOME / lib / ekst bibliotek eller ethvert andet bibliotek, der er nævnt i java.ext.dirs systemegenskab.

2.3. Systemklasse Loader

Systemet eller applikationsklasselæsseren sørger på den anden side for at indlæse alle applikationsniveauklasser i JVM. Det indlæser filer, der findes i classpath-miljøvariablen, -klassesti eller -cp kommandolinjemulighed. Det er også et barn af Extensions classloader.

3. Hvordan fungerer klasselæssere?

Klasselæssere er en del af Java Runtime Environment. Når JVM anmoder om en klasse, forsøger klasselæsseren at finde klassen og indlæse klassedefinitionen i løbetiden ved hjælp af det fuldt kvalificerede klassenavn.

Det java.lang.ClassLoader.loadClass () metoden er ansvarlig for at indlæse klassedefinitionen i runtime. Det forsøger at indlæse klassen baseret på et fuldt kvalificeret navn.

Hvis klassen ikke allerede er indlæst, delegerer den anmodningen til den overordnede klasselæsser. Denne proces sker rekursivt.

Til sidst, hvis forældreklasselæsseren ikke finder klassen, ringer barneklassen java.net.URLClassLoader.findClass () metode til at lede efter klasser i selve filsystemet.

Hvis den sidste børneklasser ikke er i stand til at indlæse klassen heller, kaster den java.lang.NoClassDefFoundError eller java.lang.ClassNotFoundException.

Lad os se på et eksempel på output, når ClassNotFoundException kastes.

java.lang.ClassNotFoundException: com.baeldung.classloader.SampleClassLoader at java.net.URLClassLoader.findClass (URLClassLoader.java:381) at java.lang.ClassLoader.loadClass (ClassLoader.java:424) at java.lang.ClassLoader. loadClass (ClassLoader.java:357) på java.lang.Class.forName0 (Native Method) på java.lang.Class.forName (Class.java:348)

Hvis vi gennemgår rækkefølgen af ​​begivenheder lige fra opkald java.lang.Class.forName (), kan vi forstå, at det først forsøger at indlæse klassen gennem overordnet klasselæsser og derefter java.net.URLClassLoader.findClass () at kigge efter selve klassen.

Når den stadig ikke finder klassen, kaster den en ClassNotFoundException.

Der er tre vigtige funktioner i klasselæssere.

3.1. Delegationsmodel

Klasselæssere følger delegationsmodellen hvor på anmodning om at finde en klasse eller ressource, a ClassLoader instans delegerer søgningen efter klassen eller ressourcen til den overordnede klasselæsser.

Lad os sige, at vi har en anmodning om at indlæse en applikationsklasse i JVM. Systemklasselæsseren delegerer først indlæsningen af ​​denne klasse til sin overordnede udvidelsesklasselæsser, som igen delegerer den til bootstrap-klasselæsseren.

Kun hvis bootstrap og derefter udvidelsesklasselæsseren ikke indlæser klassen, forsøger systemklasselæsseren at indlæse klassen selv.

3.2. Unikke klasser

Som en konsekvens af delegationsmodellen er det let at sikre unikke klasser, da vi altid prøver at delegere opad.

Hvis forældreklasselæsseren ikke er i stand til at finde klassen, forsøger den nuværende instans kun at gøre det selv.

3.3. Sigtbarhed

Ud over, børneklasselæssere er synlige for klasser, der læsses af dets overordnede klasselæssere.

For eksempel har klasser, der er indlæst af systemklasselæsseren, synlighed i klasser, der er indlæst af udvidelses- og Bootstrap-klasselæssere, men ikke omvendt.

For at illustrere dette, hvis klasse A indlæses af en applikationsklasselæsser og klasse B indlæses af udvidelsesklasselæsseren, så er både A- og B-klasser synlige for så vidt angår andre klasser, der er indlæst af applikationsklasselæsseren.

Klasse B er ikke desto mindre den eneste klasse, der er synlig for så vidt angår andre klasser, der læsses af udvidelsesklasselæsseren.

4. Custom ClassLoader

Den indbyggede klasselæsser er tilstrækkelig i de fleste tilfælde, hvor filerne allerede findes i filsystemet.

I scenarier, hvor vi har brug for at indlæse klasser ud af den lokale harddisk eller et netværk, skal vi dog muligvis bruge brugertilpassede klasselæssere.

I dette afsnit dækker vi nogle andre anvendelsessager til tilpassede klasselæssere, og vi demonstrerer, hvordan man opretter en.

4.1. Brugertilfælde til brugerdefinerede klasselæssere

Brugerdefinerede klasselæssere er nyttige til mere end bare at indlæse klassen under kørselstid, nogle få brugstilfælde kan omfatte:

  1. Hjælper med at ændre den eksisterende bytekode, f.eks. vævemidler
  2. Oprettelse af klasser, der er dynamisk tilpasset brugerens behov. fx i JDBC sker skift mellem forskellige driverimplementeringer gennem dynamisk klasseindlæsning.
  3. Implementering af en klasseversionsmekanisme, mens forskellige belastningskoder indlæses for klasser med samme navne og pakker. Dette kan gøres enten via URL-klasselæsser (load-krukker via URL'er) eller tilpassede klasselastere.

Der er flere konkrete eksempler, hvor tilpassede klasselæssere kan være nyttige.

Browsere bruger for eksempel en brugerdefineret klasselæsser til at indlæse eksekverbart indhold fra et websted. En browser kan indlæse applets fra forskellige websider ved hjælp af separate klasselæssere. Den appletfremviser, der bruges til at køre applets, indeholder en ClassLoader der får adgang til et websted på en ekstern server i stedet for at kigge i det lokale filsystem.

Og derefter indlæser de rå bytecode-filer via HTTP og omdanner dem til klasser inde i JVM. Selv om disse applets har samme navn, de betragtes som forskellige komponenter, hvis de indlæses af forskellige klasselæssere.

Nu hvor vi forstår, hvorfor tilpassede klasselæssere er relevante, lad os implementere en underklasse af ClassLoader for at udvide og sammenfatte funktionaliteten af, hvordan JVM indlæser klasser.

4.2. Oprettelse af vores brugerdefinerede klasselæsser

Af illustrationshensyn, lad os sige, at vi skal indlæse klasser fra en fil ved hjælp af en brugerdefineret klasselæsser.

Vi er nødt til at udvide ClassLoader klasse og tilsidesætte findClass () metode:

offentlig klasse CustomClassLoader udvider ClassLoader {@Override public Class findClass (String name) kaster ClassNotFoundException {byte [] b = loadClassFromFile (navn); return defineClass (navn, b, 0, b.længde); } privat byte [] loadClassFromFile (String fileName) {InputStream inputStream = getClass (). getClassLoader (). getResourceAsStream (fileName.replace ('.', File.separatorChar) + ".class"); byte [] buffer; ByteArrayOutputStream byteStream = ny ByteArrayOutputStream (); int nextValue = 0; prøv {while ((nextValue = inputStream.read ())! = -1) {byteStream.write (nextValue); }} fange (IOException e) {e.printStackTrace (); } buffer = byteStream.toByteArray (); returbuffer; }}

I ovenstående eksempel definerede vi en brugerdefineret klasselæsser, der udvider standardklasselæsseren og indlæser et byte-array fra den angivne fil.

5. Forståelse java.lang.ClassLoader

Lad os diskutere et par vigtige metoder fra java.lang.ClassLoader klasse for at få et klarere billede af, hvordan det fungerer.

5.1. Det loadClass () Metode

public Class loadClass (String name, boolean resolution) kaster ClassNotFoundException {

Denne metode er ansvarlig for at indlæse klassen med et navneparameter. Navnparameteren henviser til det fuldt kvalificerede klassenavn.

Java Virtual Machine påberåber sig loadClass () metode til at løse klassereferencer indstilling løse til rigtigt. Det er dog ikke altid nødvendigt at løse en klasse. Hvis vi kun har brug for at afgøre, om klassen eksisterer eller ej, er løsningsparameteren indstillet til falsk.

Denne metode fungerer som et indgangspunkt for klasselæsseren.

Vi kan prøve at forstå det interne arbejde i loadClass () metode fra kildekoden til java.lang.ClassLoader:

beskyttet Class loadClass (strengnavn, boolsk løsning) kaster ClassNotFoundException {synkroniseret (getClassLoadingLock (navn)) {// Kontroller først, om klassen allerede er indlæst Class c = findLoadedClass (name); hvis (c == null) {lang t0 = System.nanoTime (); prøv {if (parent! = null) {c = parent.loadClass (name, false); } andet {c = findBootstrapClassOrNull (navn); }} fangst (ClassNotFoundException e) {// ClassNotFoundException kastet, hvis klasse ikke blev fundet // fra den ikke-nulige overordnede klasselæsser} hvis (c == null) {// Hvis den stadig ikke findes, så påkald findClass i rækkefølge // find klassen. c = findClass (navn); }} hvis (løse) {løse Klasse (c); } returnere c; }}

Standardimplementeringen af ​​metoden søger efter klasser i følgende rækkefølge:

  1. Påberåber sig findLoadedClass (streng) metode til at se, om klassen allerede er indlæst.
  2. Påberåber sig loadClass (streng) metode på forældreklasselæsseren.
  3. Påkald findClass (String) metode til at finde klassen.

5.2. Det defineClass () Metode

beskyttet endelig klasse defineClass (strengnavn, byte [] b, int off, int len) kaster ClassFormatError

Denne metode er ansvarlig for konverteringen af ​​en række bytes til en forekomst af en klasse. Og inden vi bruger klassen, er vi nødt til at løse det.

Hvis data ikke indeholder en gyldig klasse, kaster de en ClassFormatError.

Vi kan heller ikke tilsidesætte denne metode, da den er markeret som endelig.

5.3. Det findClass () Metode

beskyttet klasse findClass (strengnavn) kaster ClassNotFoundException

Denne metode finder klassen med det fuldt kvalificerede navn som parameter. Vi er nødt til at tilsidesætte denne metode i brugerdefinerede klasselæsserimplementeringer, der følger delegeringsmodellen til indlæsning af klasser.

Også, loadClass () påkalder denne metode, hvis forældreklasselæsseren ikke kunne finde den ønskede klasse.

Standardimplementeringen kaster en ClassNotFoundException hvis ingen forældre til klasselæsseren finder klassen.

5.4. Det getParent () Metode

offentlig endelig ClassLoader getParent ()

Denne metode returnerer den overordnede klasselæsser til delegering.

Nogle implementeringer som den set før i afsnit 2. brug nul til at repræsentere bootstrap-klasselæsseren.

5.5. Det getResource () Metode

offentlig URL getResource (strengnavn)

Denne metode forsøger at finde en ressource med det givne navn.

Det vil først delegere til forældreklasselæsseren for ressourcen. Hvis forældrene er det nulsøges stien til klasselæsseren, der er indbygget i den virtuelle maskine.

Hvis det mislykkes, vil metoden påberåbe sig findResource (streng) for at finde ressourcen. Ressourcenavnet, der er angivet som input, kan være relativ eller absolut i forhold til klassestien.

Det returnerer et URL-objekt til læsning af ressourcen eller null, hvis ressourcen ikke kunne findes, eller hvis påkalderen ikke har tilstrækkelige privilegier til at returnere ressourcen.

Det er vigtigt at bemærke, at Java indlæser ressourcer fra klassestien.

Langt om længe, ressourceindlæsning i Java betragtes som placeringsuafhængig da det ikke betyder noget, hvor koden kører, så længe miljøet er indstillet til at finde ressourcerne.

6. Context Classloaders

Generelt giver kontekstklasselæssere en alternativ metode til klasseladningsdelegeringsordningen, der blev introduceret i J2SE.

Som vi har lært før, klasselæssere i en JVM følger en hierarkisk model, således at hver klasselæsser har en enlig forælder med undtagelse af bootstrap klasselæsseren.

Men nogle gange når JVM-kerneklasser skal dynamisk indlæse klasser eller ressourcer leveret af applikationsudviklere, kan vi dog støde på et problem.

For eksempel implementeres kernefunktionaliteten i JNDI af bootstrap-klasser i rt.jar. Men disse JNDI-klasser kan indlæse JNDI-udbydere implementeret af uafhængige leverandører (implementeret i applikationsklassestien). Dette scenario opfordrer bootstrap-klasselæsseren (overordnet klasselæsser) til at indlæse en klasse, der er synlig for applikationsindlæser (underordnet klasselæsser).

J2SE-delegation fungerer ikke her, og for at omgå dette problem er vi nødt til at finde alternative måder at indlæse på klassen på. Og det kan opnås ved hjælp af thread context loaders.

Det java.lang.Tråd klasse har en metode getContextClassLoader () der returnerer ContextClassLoader til den bestemte tråd. Det ContextClassLoader leveres af skaberen af ​​tråden ved indlæsning af ressourcer og klasser.

Hvis værdien ikke er indstillet, er den som standard klasselæsserkonteksten for den overordnede tråd.

7. Konklusion

Klasselæssere er vigtige for at udføre et Java-program. Vi har givet en god introduktion som en del af denne artikel.

Vi talte om forskellige typer klasselæssere, nemlig Bootstrap, Extensions og System class loaders. Bootstrap fungerer som forælder for dem alle og er ansvarlig for at indlæse JDK's interne klasser. Udvidelser og system indlæser på den anden side klasser fra henholdsvis Java-udvidelsesbiblioteket og klassestien.

Derefter talte vi om, hvordan klasselæssere fungerer, og vi diskuterede nogle funktioner som delegering, synlighed og unikhed efterfulgt af en kort forklaring på, hvordan man opretter en brugerdefineret. Endelig leverede vi en introduktion til Context class loaders.

Kodeprøver kan som altid findes på GitHub.