Klasse-laders in Java

1. Inleiding tot klasseladers

Klasse laders zijn verantwoordelijk voor Java-klassen tijdens runtime dynamisch laden naar de JVM (Java Virtual Machine). Ze maken ook deel uit van de JRE (Java Runtime Environment). Daarom hoeft de JVM niets te weten over de onderliggende bestanden of bestandssystemen om Java-programma's uit te voeren dankzij class loaders.

Bovendien worden deze Java-klassen niet allemaal tegelijk in het geheugen geladen, maar wanneer vereist door een toepassing. Dit is waar klassenladers in beeld komen. Ze zijn verantwoordelijk voor het laden van klassen in het geheugen.

In deze zelfstudie gaan we het hebben over verschillende soorten ingebouwde klasse-laders, hoe ze werken en een inleiding tot onze eigen aangepaste implementatie.

2. Soorten ingebouwde laders

Laten we beginnen met te leren hoe verschillende klassen worden geladen met behulp van verschillende klassenladers aan de hand van een eenvoudig voorbeeld:

public void printClassLoaders () gooit ClassNotFoundException {System.out.println ("Classloader van deze klasse:" + PrintClassLoader.class.getClassLoader ()); System.out.println ("Classloader of Logging:" + Logging.class.getClassLoader ()); System.out.println ("Classloader of ArrayList:" + ArrayList.class.getClassLoader ()); }

Bij uitvoering van de bovenstaande methode wordt afgedrukt:

Klasse loader van deze klasse: [email protected] Class loader van logging: [email protected] Class loader van ArrayList: null

Zoals we kunnen zien, zijn er hier drie verschillende klassenladers; applicatie, extensie en bootstrap (weergegeven als nul).

De applicatieklasse-loader laadt de klasse waarin de voorbeeldmethode zich bevindt. Een applicatie- of systeemklassenlader laadt onze eigen bestanden in het klassenpad.

Vervolgens laadt de extensie het Logboekregistratie klasse. Laders van uitbreidingsklassen laden klassen die een uitbreiding zijn van de standaard Java-kernklassen.

Ten slotte laadt de bootstrap het ArrayList klasse. Een bootstrap of primordial class loader is de ouder van alle anderen.

We kunnen echter zien dat de laatste uit, voor de ArrayList het laat zien nul in de uitvoer. Dit komt doordat de bootstrap class loader is geschreven in native code, niet in Java - dus het verschijnt niet als een Java class. Om deze reden zal het gedrag van de bootstrap class loader verschillen tussen JVM's.

Laten we nu meer in detail bespreken over elk van deze klassenladers.

2.1. Bootstrap Class Loader

Java-klassen worden geladen door een instantie van java.lang.ClassLoader. Class-loaders zijn echter zelf klassen. Daarom is de vraag wie de java.lang.ClassLoader zelf?

Dit is waar de bootstrap of primordial class loader in beeld komt.

Het is voornamelijk verantwoordelijk voor het laden van interne JDK-klassen, meestal rt.jar en andere kernbibliotheken in $ JAVA_HOME / jre / lib map. Bovendien, Bootstrap class loader dient als een ouder van alle andere ClassLoader gevallen.

Deze bootstrap class loader maakt deel uit van de core JVM en is geschreven in native code zoals aangegeven in het bovenstaande voorbeeld. Verschillende platforms hebben mogelijk verschillende implementaties van deze specifieke klassenlader.

2.2. Uitbreidingsklasse lader

De extension class loader is een kind van de bootstrap class loader en zorgt voor het laden van de extensies van de standaard core Java-klassen zodat het beschikbaar is voor alle applicaties die op het platform draaien.

De lader van de extensieklasse wordt meestal geladen vanuit de directory met JDK-extensies $ JAVA_HOME / lib / ext directory of een andere directory die wordt genoemd in de java.ext.dirs systeemeigenschap.

2.3. Systeemklasse lader

De systeem- of applicatieklasse-loader daarentegen zorgt ervoor dat alle applicatieniveauklassen in de JVM worden geladen. Het laadt bestanden gevonden in de classpath omgevingsvariabele, -klassenpad of -cp opdrachtregeloptie. Het is ook een kind van Extensions classloader.

3. Hoe werken klassenladers?

Class loaders maken deel uit van de Java Runtime Environment. Wanneer de JVM een klasse opvraagt, probeert de klassenlader de klasse te lokaliseren en de klassedefinitie in de runtime te laden met behulp van de volledig gekwalificeerde klassenaam.

De java.lang.ClassLoader.loadClass () methode is verantwoordelijk voor het laden van de klassendefinitie in runtime. Het probeert de klasse te laden op basis van een volledig gekwalificeerde naam.

Als de klasse nog niet is geladen, wordt het verzoek gedelegeerd naar de lader van de bovenliggende klasse. Dit proces gebeurt recursief.

Als de lader van de bovenliggende klasse de klasse uiteindelijk niet kan vinden, zal de onderliggende klasse bellen java.net.URLClassLoader.findClass () methode om klassen in het bestandssysteem zelf te zoeken.

Als de lader van de laatste kindklasse de klasse ook niet kan laden, gooit hij java.lang.NoClassDefFoundError of java.lang.ClassNotFoundException.

Laten we eens kijken naar een voorbeeld van uitvoer wanneer ClassNotFoundException wordt gegenereerd.

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

Als we de opeenvolging van gebeurtenissen doorlopen vanaf het moment dat we bellen java.lang.Class.forName ()kunnen we begrijpen dat het eerst probeert om de klasse te laden via de bovenliggende class loader en dan java.net.URLClassLoader.findClass () om de klas zelf te zoeken.

Als het de klas nog steeds niet vindt, gooit het een ClassNotFoundException.

Er zijn drie belangrijke kenmerken van klasse-laders.

3.1. Delegatiemodel

Klasse-laders volgen het delegatiemodel waar op verzoek om een ​​klas of hulpmiddel te vinden, a ClassLoader instantie zal het zoeken naar de klasse of resource delegeren aan de bovenliggende class loader.

Stel dat we een verzoek hebben om een ​​toepassingsklasse in de JVM te laden. De lader van de systeemklasse delegeert eerst het laden van die klasse naar de lader van de bovenliggende extensieklasse, die het op zijn beurt delegeert aan de lader van de bootstrap-klasse.

Alleen als de bootstrap en vervolgens de extensie class loader er niet in slaagt de klasse te laden, probeert de systeem class loader de klasse zelf te laden.

3.2. Unieke lessen

Als gevolg van het delegatiemodel is het gemakkelijk te verzekeren unieke lessen omdat we altijd proberen naar boven te delegeren.

Als de lader van de bovenliggende klasse de klasse niet kan vinden, probeert de huidige instantie dit zelf te doen.

3.3. Zichtbaarheid

Daarnaast, laders van onderliggende klassen zijn zichtbaar voor klassen die worden geladen door de laders van bovenliggende klassen.

Klassen die zijn geladen door de systeemklassenlader hebben bijvoorbeeld zicht op klassen die zijn geladen door de extensie en Bootstrap-klassenladers, maar niet omgekeerd.

Om dit te illustreren, als Klasse A wordt geladen door een applicatieklasse-lader en klasse B wordt geladen door de extensieklasse-lader, dan zijn zowel A- als B-klassen zichtbaar voor zover het andere klassen betreft die zijn geladen door de Applicatieklasse-lader.

Klasse B is desalniettemin de enige klasse die zichtbaar is voor zover het andere klassen betreft die door de lader van de uitbreidingsklasse worden geladen.

4. Aangepaste ClassLoader

De ingebouwde klassenlader zou in de meeste gevallen voldoende zijn als de bestanden zich al in het bestandssysteem bevinden.

In scenario's waarin we klassen van de lokale harde schijf of een netwerk moeten laden, moeten we mogelijk gebruik maken van aangepaste klassenladers.

In deze sectie behandelen we enkele andere gebruiksscenario's voor custom class-laders en laten we zien hoe u er een kunt maken.

4.1. Gebruiksscenario's voor laders van aangepaste klassen

Aangepaste klasse-laders zijn nuttig voor meer dan alleen het laden van de klasse tijdens runtime, een paar gebruiksscenario's kunnen zijn:

  1. Helpen bij het aanpassen van de bestaande bytecode, bijv. weefagenten
  2. Klassen creëren die dynamisch aangepast zijn aan de behoeften van de gebruiker. In JDBC wordt bijvoorbeeld schakelen tussen verschillende driverimplementaties gedaan door middel van dynamische klassenbelasting.
  3. Implementeren van een klasse-versie-mechanisme terwijl verschillende bytecodes worden geladen voor klassen met dezelfde namen en pakketten. Dit kan worden gedaan via URL-class-loader (jars laden via URL's) of aangepaste class-laders.

Er zijn meer concrete voorbeelden waarbij laders van aangepaste klassen van pas kunnen komen.

Browsers gebruiken bijvoorbeeld een aangepaste klassenlader om uitvoerbare inhoud van een website te laden. Een browser kan applets van verschillende webpagina's laden met behulp van afzonderlijke klassenladers. De appletviewer die wordt gebruikt om applets uit te voeren, bevat een ClassLoader die toegang heeft tot een website op een externe server in plaats van in het lokale bestandssysteem te kijken.

En laadt vervolgens de onbewerkte bytecodebestanden via HTTP en verandert ze in klassen binnen de JVM. Zelfs als deze applets hebben dezelfde naam, ze worden als verschillende componenten beschouwd als ze door verschillende klassenladers worden geladen.

Nu we begrijpen waarom aangepaste klasse-laders relevant zijn, gaan we een subklasse van ClassLoader om de functionaliteit van hoe de JVM klassen laadt uit te breiden en samen te vatten.

4.2. Onze lader op maat maken

Laten we voor illustratiedoeleinden zeggen dat we klassen uit een bestand moeten laden met behulp van een aangepaste klassenlader.

We moeten het ClassLoader class en overschrijf de findClass () methode:

openbare klasse CustomClassLoader breidt ClassLoader uit {@Override openbare klasse findClass (tekenreeksnaam) gooit ClassNotFoundException {byte [] b = loadClassFromFile (naam); return defineClass (name, b, 0, b.length); } private byte [] loadClassFromFile (String bestandsnaam) {InputStream inputStream = getClass (). getClassLoader (). getResourceAsStream (bestandsnaam.replace ('.', File.separatorChar) + ".class"); byte [] buffer; ByteArrayOutputStream byteStream = nieuwe ByteArrayOutputStream (); int nextValue = 0; probeer {while ((nextValue = inputStream.read ())! = -1) {byteStream.write (nextValue); }} catch (IOException e) {e.printStackTrace (); } buffer = byteStream.toByteArray (); terugkeerbuffer; }}

In het bovenstaande voorbeeld hebben we een aangepaste klassenlader gedefinieerd die de standaard klassenlader uitbreidt en een byte-array uit het opgegeven bestand laadt.

5. Begrip java.lang.ClassLoader

Laten we een paar essentiële methoden bespreken uit de java.lang.ClassLoader klas om een ​​duidelijker beeld te krijgen van hoe het werkt.

5.1. De loadClass () Methode

public Class loadClass (String naam, booleaanse oplossing) genereert ClassNotFoundException {

Deze methode is verantwoordelijk voor het laden van de klasse met een naamparameter. De parameter name verwijst naar de volledig gekwalificeerde klassenaam.

De Java Virtual Machine roept loadClass () methode om klassenreferenties op te lossen, instelling omzetten in waar. Het is echter niet altijd nodig om een ​​klasse op te lossen. Als we alleen hoeven te bepalen of de klasse bestaat of niet, wordt de parameter resolv ingesteld op false.

Deze methode dient als een toegangspunt voor de klassenlader.

We kunnen proberen de interne werking van het loadClass () methode uit de broncode van java.lang.ClassLoader:

beschermde Class loadClass (String naam, booleaanse oplossing) gooit ClassNotFoundException {synchronized (getClassLoadingLock (naam)) {// Controleer eerst of de klasse al is geladen Klasse c = findLoadedClass (naam); if (c == null) {long t0 = System.nanoTime (); probeer {if (parent! = null) {c = parent.loadClass (name, false); } anders {c = findBootstrapClassOrNull (naam); }} catch (ClassNotFoundException e) {// ClassNotFoundException gegooid if class not found // from the non-null parent class loader} if (c == null) {// Indien nog steeds niet gevonden, roep dan findClass aan in de volgorde // to vind de klas. c = findClass (naam); }} if (resolve) {resolveclass (c); } retourneer c; }}

De standaardimplementatie van de methode zoekt naar klassen in de volgende volgorde:

  1. Roept het findLoadedClass (tekenreeks) methode om te zien of de klasse al is geladen.
  2. Roept het loadClass (String) methode op de lader van de bovenliggende klasse.
  3. Roep het findClass (tekenreeks) methode om de klas te vinden.

5.2. De defineClass () Methode

beschermde laatste Klasse defineClass (String naam, byte [] b, int uit, int len) gooit ClassFormatError

Deze methode is verantwoordelijk voor de conversie van een array van bytes naar een instantie van een klasse. En voordat we de klas gebruiken, moeten we het oplossen.

Als de gegevens geen geldige klasse bevatten, wordt een ClassFormatError.

We kunnen deze methode ook niet negeren omdat deze als definitief is gemarkeerd.

5.3. De findClass () Methode

beschermde Klasse findClass (String naam) gooit ClassNotFoundException

Deze methode zoekt de klasse met de volledig gekwalificeerde naam als parameter. We moeten deze methode overschrijven in implementaties van aangepaste klassenlader die het delegatiemodel voor het laden van klassen volgen.

Ook, loadClass () roept deze methode aan als de lader van de bovenliggende klasse de gevraagde klasse niet kan vinden.

De standaardimplementatie gooit een ClassNotFoundException als geen ouder van de klassenlader de klasse vindt.

5.4. De getParent () Methode

openbare laatste ClassLoader getParent ()

Deze methode retourneert de bovenliggende class loader voor delegatie.

Sommige implementaties, zoals degene die eerder in Sectie 2 is gezien, gebruiken nul om de lader van de bootstrap-klasse weer te geven.

5.5. De getResource () Methode

openbare URL getResource (tekenreeksnaam)

Deze methode probeert een bron met de opgegeven naam te vinden.

Het zal eerst delegeren naar de bovenliggende class loader voor de resource. Als de ouder is nulwordt het pad van de in de virtuele machine ingebouwde klassenlader doorzocht.

Als dat niet lukt, zal de methode aanroepen findResource (tekenreeks) om de bron te vinden. De resourcenaam die als invoer is opgegeven, kan relatief of absoluut zijn ten opzichte van het klassenpad.

Het retourneert een URL-object voor het lezen van de bron, of null als de bron niet kon worden gevonden of als de aanvaller niet de juiste rechten heeft om de bron te retourneren.

Het is belangrijk op te merken dat Java bronnen laadt vanuit het klassenpad.

Tenslotte, het laden van bronnen in Java wordt als locatie-onafhankelijk beschouwd aangezien het niet uitmaakt waar de code wordt uitgevoerd, zolang de omgeving is ingesteld om de bronnen te vinden.

6. Context Classloaders

Over het algemeen bieden context-class-loaders een alternatieve methode voor het class-loading-delegatieschema dat in J2SE is geïntroduceerd.

Zoals we eerder hebben geleerd, classloaders in een JVM volgen een hiërarchisch model zodat elke class loader een enkele ouder heeft, met uitzondering van de bootstrap class loader.

Soms echter, wanneer JVM-kernklassen dynamisch klassen of bronnen moeten laden die door toepassingsontwikkelaars worden geleverd, kunnen we een probleem tegenkomen.

In JNDI wordt de kernfunctionaliteit bijvoorbeeld geïmplementeerd door bootstrap-klassen in rt.jar. Maar deze JNDI-klassen kunnen JNDI-providers laden die zijn geïmplementeerd door onafhankelijke leveranciers (geïmplementeerd in het toepassingsklassenpad). Dit scenario vraagt ​​om de bootstrap class loader (bovenliggende class loader) om een ​​klasse te laden die zichtbaar is voor de applicatielader (child class loader).

J2SE-delegatie werkt hier niet en om dit probleem te omzeilen, moeten we alternatieve manieren vinden om klassen te laden. En het kan worden bereikt met behulp van threadcontextladers.

De java.lang.Thread klasse heeft een methode getContextClassLoader () dat retourneert de ContextClassLoader voor de specifieke draad. De ContextClassLoader wordt geleverd door de maker van de thread bij het laden van bronnen en klassen.

Als de waarde niet is ingesteld, wordt deze standaard ingesteld op de context van de lader van de bovenliggende thread.

7. Conclusie

Class-loaders zijn essentieel om een ​​Java-programma uit te voeren. We hebben een goede introductie gegeven als onderdeel van dit artikel.

We hadden het over verschillende soorten class-laders, namelijk - Bootstrap, Extensions en System class-laders. Bootstrap dient als ouder voor al deze klassen en is verantwoordelijk voor het laden van de interne JDK-klassen. Extensies en het systeem laden daarentegen klassen uit respectievelijk de Java-extensiedirectory en het klassenpad.

Vervolgens hebben we het gehad over hoe klassenladers werken en hebben we enkele functies besproken, zoals delegatie, zichtbaarheid en uniekheid, gevolgd door een korte uitleg over hoe u een aangepaste versie kunt maken. Ten slotte hebben we een inleiding gegeven tot Context class-laders.

Codevoorbeelden zijn, zoals altijd, te vinden op GitHub.


$config[zx-auto] not found$config[zx-overlay] not found