Osztályterhelők Java-ban

1. Bevezetés az osztályos rakodókba

Az osztályos rakodók felelősek futás közbeni Java osztályok dinamikus betöltése a JVM-be (Java virtuális gép). Továbbá a JRE (Java Runtime Environment) részei. Ezért a JVM-nek nem kell tudnia az alapul szolgáló fájlokról vagy fájlrendszerekről ahhoz, hogy a Java programokat futtathassa az osztályterhelőknek köszönhetően.

Ezeket a Java osztályokat nem egyszerre töltik be a memóriába, hanem ha egy alkalmazás megköveteli. Itt jönnek a képbe az osztályos rakodók. Ők felelősek az osztályok memóriába töltéséért.

Ebben az oktatóanyagban a beépített osztályos rakodók különféle típusairól, azok működéséről és a saját egyéni megvalósításunk bemutatásáról fogunk beszélni.

2. A beépített osztályos rakodók típusai

Kezdjük azzal, hogy megtanuljuk, hogyan töltik be a különböző osztályokat a különböző osztályos betöltők segítségével egy egyszerű példa segítségével:

public void printClassLoaders () dobja a ClassNotFoundException {System.out.println ("Ennek az osztálynak az osztályterjesztője:" + PrintClassLoader.class.getClassLoader ()); System.out.println ("Naplózás osztálybetöltője:" + Logging.class.getClassLoader ()); System.out.println ("Az ArrayList osztálytöltője:" + ArrayList.class.getClassLoader ()); }

A fenti módszer végrehajtása után kinyomtatja:

Az osztály osztálybetöltője: [e-mail védett] Naplózás osztálybetöltője: [e-mail védett] Az ArrayList osztálybetöltője: null

Mint láthatjuk, itt három különböző osztályú rakodó van; alkalmazás, kiterjesztés és bootstrap (mint nulla).

Az alkalmazásosztály betöltő azt az osztályt tölti be, ahol a példa metódus található. Egy alkalmazás vagy rendszerosztály betöltő saját fájljainkat tölti be az osztályútvonalra.

Ezután az egyik kiterjesztés betölti a Fakitermelés osztály. A kiterjesztési osztályú betöltők olyan osztályok, amelyek a standard Java osztályok kiterjesztését jelentik.

Végül a bootstrap betölti a Tömb lista osztály. A bootstrap vagy az első osztályú betöltő az összes többi szülője.

Láthatjuk azonban, hogy a Tömb lista megjeleníti nulla a kimenetben. Ez azért van, mert a bootstrap osztály betöltője natív kódban van megírva, nem Java-ban - tehát nem jelenik meg Java osztályként. Emiatt a bootstrap osztályú betöltő viselkedése a JVM-ekben eltérő lesz.

Most beszéljünk részletesebben ezekről az osztályos rakodókról.

2.1. Bootstrap osztályú rakodó

A Java osztályokat egy java.lang.ClassLoader. Az osztályos rakodók azonban maguk is osztályok. Ezért kérdés, hogy ki tölti be a java.lang.ClassLoader maga?

Itt jelenik meg a képen a bootstrap vagy az első osztályú betöltő.

Főként a JDK belső osztályainak betöltéséért felelős rt.jar és más központi könyvtárak találhatók $ JAVA_HOME / jre / lib könyvtár. Ezenkívül A Bootstrap osztályterhelő az összes többi szülőjeként szolgál ClassLoader példányok.

Ez a bootstrap osztályú betöltő az alapvető JVM része, és natív kódban van megírva amint arra a fenti példa rámutatott. Különböző platformok eltérő megvalósítással rendelkezhetnek az adott osztályos betöltővel.

2.2. Extension Class Loader

A A extension class loader a bootstrap osztály betöltőjének gyermeke, és gondoskodik a standard Java osztályok kiterjesztéseinek betöltéséről hogy a platformon futó összes alkalmazás számára elérhető legyen.

A kiterjesztési osztályú betöltő általában a JDK kiterjesztések könyvtárából töltődik be $ JAVA_HOME / lib / ext könyvtár vagy bármely más könyvtár, amelyet a java.ext.dirs rendszer tulajdonság.

2.3. Rendszerosztály-betöltő

A rendszer vagy az alkalmazásosztály betöltője viszont gondoskodik az összes alkalmazásszintű osztály betöltéséről a JVM-be. Betölti az classpath környezeti változóban található fájlokat, -útvonal vagy -cp parancssori opció. Ezenkívül az Extensions classloader gyermeke.

3. Hogyan működnek az osztályrakodók?

Az osztályterhelők a Java Runtime Environment részét képezik. Amikor a JVM osztályt kér, az osztályterhelő megpróbálja megkeresni az osztályt, és az osztálydefiníciót a futásidőbe a teljesen minősített osztálynév segítségével tölti be.

A java.lang.ClassLoader.loadClass () A módszer felelős az osztálydefiníció futásidejű betöltéséért. Megpróbálja betölteni az osztályt egy teljesen minősített név alapján.

Ha az osztály még nincs betöltve, akkor a kérelmet a szülő osztály betöltőjére ruházza át. Ez a folyamat rekurzív módon történik.

Végül, ha a szülő osztály betöltője nem találja meg az osztályt, akkor a gyermek osztály felhívja java.net.URLClassLoader.findClass () módszer az osztályok keresésére magában a fájlrendszerben.

Ha az utolsó gyermekosztály betöltője sem képes betölteni az osztályt, akkor dob java.lang.NoClassDefFoundError vagy java.lang.ClassNotFoundException.

Nézzünk meg egy példát a kimenetre, amikor a ClassNotFoundException dobásra kerül.

java.lang.ClassNotFoundException: com.baeldung.classloader.SampleClassLoader at java.net.URLClassLoader.findClass (URLClassLoader.java:381) at java.lang.ClassLoader.loadClass (ClassLoader.java:4ang) at java. loadClass (ClassLoader.java:357) a java.lang.Class.forName0 (natív módszer) a java.lang.Class.forName (Class.java:348)

Ha a hívástól kezdve végigmegyünk az események sorrendjén java.lang.Class.forName (), megérthetjük, hogy először megpróbálja betölteni az osztályt a szülő class loader segítségével, majd java.net.URLClassLoader.findClass () hogy maga az osztály keresse meg.

Amikor még mindig nem találja meg az osztályt, akkor dob egy ClassNotFoundException.

Az osztályos rakodóknak három fontos jellemzője van.

3.1. Delegációs modell

Az osztályterhelők követik a delegálás modelljét, ahol kérésre osztály vagy erőforrás megtalálásához, a ClassLoader példány átruházza az osztály vagy erőforrás keresését a szülő osztály betöltőjére.

Tegyük fel, hogy kérésünk van egy alkalmazásosztály betöltésére a JVM-be. A rendszerszintű betöltő először átruházza az osztály betöltését a szülő kiterjesztésű osztály betöltőjére, amely viszont a bootstrap osztályú betöltőre ruházza át.

Csak akkor, ha a bootstrap, majd a kiterjesztésű osztály betöltője sikertelenül tölti be az osztályt, a rendszerosztály betöltője megpróbálja betölteni az osztályt.

3.2. Egyedi osztályok

A delegálási modell következtében könnyű biztosítani egyedi osztályok, mivel mindig megpróbálunk felfelé delegálni.

Ha a szülő osztály betöltője nem tudja megtalálni az osztályt, akkor az aktuális példány csak akkor próbálja meg ezt megtenni.

3.3. Láthatóság

Továbbá, a gyermekosztályos rakodógépek láthatók azoknak az osztályoknak, amelyeket a szülőosztályos rakodógépek betöltenek.

Például a rendszerosztály-betöltő által betöltött osztályok láthatók a kiterjesztés és a Bootstrap osztálytöltők által betöltött osztályokba, de nem fordítva.

Ennek szemléltetésére, ha az A osztályt egy alkalmazásosztály-betöltő tölti be, a B-osztályt pedig a kiterjesztésű osztálytöltő tölti be, akkor az A- és a B-osztály egyaránt látható, amennyiben az alkalmazásosztály-betöltő által betöltött más osztályokat érinti.

Mindazonáltal a B osztály az egyetlen látható osztály, ami a kiterjesztésű osztályos rakodó által betöltött más osztályokat illeti.

4. Egyéni ClassLoader

A beépített osztálybetöltő a legtöbb esetben elegendő lenne, ha a fájlok már a fájlrendszerben vannak.

Azonban olyan esetekben, amikor osztályokat kell töltenünk a helyi merevlemezről vagy egy hálózatból, előfordulhat, hogy egyedi osztályú betöltőket kell használnunk.

Ebben a szakaszban az egyéni osztályos rakodók további felhasználási eseteit ismertetjük, és bemutatjuk, hogyan lehet ilyeneket létrehozni.

4.1. Egyedi osztályú rakodók használati esetei

Az egyéni osztályterhelők nemcsak az osztály futás közbeni betöltéséhez hasznosak, néhány felhasználási eset a következőket tartalmazhatja:

  1. Segítség a meglévő bájtkód módosításában, pl. szövő szerek
  2. Osztályok létrehozása dinamikusan a felhasználó igényeinek megfelelően. például a JDBC-ben a különböző meghajtómegvalósítások közötti váltás dinamikus osztályterheléssel történik.
  3. Osztályverziós mechanizmus megvalósítása, miközben különböző bájtkódokat tölt be azonos nevű és csomagú osztályokhoz. Ez megtehető vagy URL osztály betöltővel (töltse be az üvegeket URL-eken keresztül), vagy egyéni osztály betöltőkön keresztül.

Vannak konkrétabb példák, ahol az egyedi osztályú rakodók jól jöhetnek.

A böngészők például egy egyéni osztályterhelővel futtatható tartalmat töltenek be egy webhelyről. A böngésző külön osztálytöltőkkel tölthet be különféle weboldalakról kisalkalmazásokat. Az kisalkalmazások futtatására használt kisalkalmazás-nézõ a ClassLoader amely a helyi fájlrendszer keresése helyett egy távoli szerveren ér el egy webhelyet.

Ezután betölti a nyers bytecode fájlokat HTTP-n keresztül, és osztályokká alakítja őket a JVM-en belül. Még akkor is, ha ezek kisalkalmazásoknak ugyanaz a neve, különböző összetevőknek számítanak, ha különböző osztályú betöltők töltik be őket.

Most, hogy megértettük, miért relevánsak az egyedi osztályú rakodók, valósítsunk meg egy alosztályt ClassLoader bővíteni és összefoglalni a JVM osztályok betöltésének funkcióit.

4.2. Custom Class Loader létrehozása

Tegyük fel, hogy szemléltetés céljából az osztályokat egy fájlból kell betölteni egy egyéni osztály betöltővel.

Ki kell terjesztenünk a ClassLoader osztály és felülírja a findClass () módszer:

public class A CustomClassLoader kiterjeszti a ClassLoader programot {@Orride public Class findClass (karakterlánc neve) dobja a ClassNotFoundException {byte [] b = loadClassFromFile (név); return defineClass (név, b, 0, b.hossz); } privát bájt [] loadClassFromFile (String fájlnév) {InputStream inputStream = getClass (). getClassLoader (). getResourceAsStream (fájlnév.replace ('.', File.separatorChar) + ".osztály"); bájt [] puffer; ByteArrayOutputStream byteStream = új ByteArrayOutputStream (); int nextValue = 0; próbáld meg a {while ((nextValue = inputStream.read ())! = -1) {byteStream.write (nextValue); }} catch (IOException e) {e.printStackTrace (); } puffer = byteStream.toByteArray (); visszatérő puffer; }}

A fenti példában meghatároztunk egy egyéni osztályterhelőt, amely kiterjeszti az alapértelmezett osztálybetöltőt és betölt egy bájt tömböt a megadott fájlból.

5. Megértés java.lang.ClassLoader

Beszéljünk meg néhány alapvető módszert a java.lang.ClassLoader osztályban, hogy tisztább képet kapjon a működéséről.

5.1. A loadClass () Módszer

public Class loadClass (karakterlánc neve, logikai felbontás) dobja a ClassNotFoundException {

Ez a módszer felelős a névparamétert kapott osztály betöltéséért. A name paraméter a teljesen minősített osztálynévre vonatkozik.

A Java virtuális gép meghív loadClass () módszer az osztályhivatkozások megoldására igaz. Azonban nem mindig szükséges megoldani egy osztályt. Ha csak azt kell megállapítanunk, hogy létezik-e az osztály vagy sem, akkor a resol paraméter értéke hamis.

Ez a módszer az osztályterhelő belépési pontjaként szolgál.

Megpróbálhatjuk megérteni a loadClass () módszer a forráskódból java.lang.ClassLoader:

védett Class loadClass (karakterlánc neve, logikai felbontás) dobja a ClassNotFoundException {szinkronizált (getClassLoadingLock (név)) {// Először ellenőrizze, hogy az osztály már be van-e töltve. if (c == null) {long t0 = System.nanoTime (); próbáld ki az {if (szülő! = null) {c = szülő.loadClass (név, hamis); } else {c = findBootstrapClassOrNull (név); }} catch (ClassNotFoundException e) {// ClassNotFoundException dobott, ha az osztály nem található // a nem null szülő osztály betöltőjéből} if (c == null) {// Ha még mindig nem található, akkor a // keresse meg az osztályt. c = findClass (név); }} if (feloldás) {feloldásClass (c); } return c; }}

A módszer alapértelmezett megvalósítása osztályokat keres a következő sorrendben:

  1. Meghívja a findLoadedClass (karakterlánc) metódus, hogy megnézze, az osztály már betöltődött-e.
  2. Meghívja a loadClass (karakterlánc) metódus a szülő osztály betöltőn.
  3. Hívja meg a findClass (karakterlánc) módszer az osztály megtalálásához.

5.2. A defineClass () Módszer

védett végső Class defineClass (karakterlánc neve, bájt [] b, int ki, int len) dobja a ClassFormatError

Ez a módszer felelős a bájtömbök osztály példányává történő átalakításáért. És mielőtt használnánk az osztályt, meg kell oldanunk.

Ha az adatok nem tartalmaznak érvényes osztályt, akkor a ClassFormatError.

Emellett nem írhatjuk felül ezt a módszert, mivel véglegesként van megjelölve.

5.3. A findClass () Módszer

védett Class findClass (karakterlánc neve) dobja a ClassNotFoundException parancsot

Ez a módszer megtalálja az osztályt, amelynek paramétere a teljesen minősített név. Ezt a módszert felül kell írnunk az egyéni osztálybetöltő-implementációkban, amelyek az átruházási osztályok követik a delegációs modellt.

Is, loadClass () meghívja ezt a módszert, ha a szülő osztály betöltője nem találja a kért osztályt.

Az alapértelmezett megvalósítás a ClassNotFoundException ha az osztályterhelő egyik szülője sem találja meg az osztályt.

5.4. A getParent () Módszer

nyilvános végleges ClassLoader getParent ()

Ez a módszer visszaküldi a szülő osztály betöltőjét delegálásra.

Néhány megvalósítás, például az, amelyet korábban a 2. szakaszban láttunk nulla hogy bemutassa a bootstrap osztályú betöltőt.

5.5. A getResource () Módszer

nyilvános URL getResource (karakterlánc neve)

Ez a módszer megpróbálja megtalálni a megadott névvel rendelkező erőforrást.

Először az erőforrás szülő osztály betöltőjére ruházza át. Ha a szülő az nulla, a virtuális gépbe épített osztályterhelő útvonalát keresi.

Ha ez nem sikerül, akkor a metódus meghívásra kerül findResource (String) hogy megtalálja az erőforrást. A bemenetként megadott erőforrás neve lehet relatív vagy abszolút az osztályútvonalhoz képest.

Visszaad egy URL-objektumot az erőforrás beolvasására, vagy null értéket, ha az erőforrás nem található, vagy ha a meghívó nem rendelkezik megfelelő jogosultságokkal az erőforrás visszaadásához.

Fontos megjegyezni, hogy a Java betölti az erőforrásokat az osztályútvonalról.

Végül, az erőforrások betöltése a Java-ban helyfüggetlen mivel nem számít, hol fut a kód, amíg a környezet be van állítva az erőforrások megtalálásához.

6. Context Classloaderek

Általában a kontextusos osztálybetöltők alternatív módszert kínálnak a J2SE-ben bevezetett osztályterhelési átruházási sémával szemben.

Mint már korábban megtanultuk, A JVM osztálytervezõi hierarchikus modellt követnek, így minden osztályterhelõnek egyedüli szülője van, a bootstrap osztálybetöltő kivételével.

Azonban néha, amikor a JVM alaposztályainak dinamikusan be kell tölteniük az alkalmazásfejlesztők által biztosított osztályokat vagy erőforrásokat, problémába ütközhetünk.

Például a JNDI-ben az alapvető funkcionalitást a rendszerindító osztályok valósítják meg rt.jar. Ezek a JNDI osztályok azonban betölthetik a JNDI szolgáltatókat, amelyeket független szállítók valósítottak meg (az alkalmazás osztályterületén telepítve). Ez a forgatókönyv arra kéri a bootstrap osztályos betöltőt (szülő osztály betöltő), hogy töltsön be egy, az alkalmazás betöltője számára látható osztályt (gyermek osztály betöltő).

A J2SE delegálása itt nem működik, és a probléma kiküszöbölése érdekében meg kell találnunk az osztálybetöltés alternatív módjait. Ez pedig a szál kontextus betöltőinek segítségével érhető el.

A java.lang.Thread osztálynak van módszere getContextClassLoader () hogy visszaadja a ContextClassLoader az adott szálhoz. A ContextClassLoader a szál készítője biztosítja az erőforrások és osztályok betöltésekor.

Ha az érték nincs beállítva, akkor alapértelmezés szerint a szülő osztály betöltőjének kontextusa lesz.

7. Következtetés

Az osztályterhelők elengedhetetlenek a Java program futtatásához. A cikk részeként bemutattunk egy jó bevezetést.

Különböző típusú osztályos rakodókról beszéltünk, nevezetesen: Bootstrap, Extensions és System osztályú rakodókról. A Bootstrap szülő mindannyiuk számára, és felelős a JDK belső osztályainak betöltéséért. A kiterjesztések és a rendszer viszont osztályokat tölt be a Java kiterjesztések könyvtárából, illetve az osztályútvonalból.

Ezután beszéltünk arról, hogy az osztályterhelők hogyan működnek, és megvitattunk néhány funkciót, például a delegálást, a láthatóságot és az egyediséget, majd rövid magyarázatot adtunk egy egyedi létrehozásának módjára. Végül bemutattuk a Context osztály betöltőit.

A kódminták, mint mindig, a GitHubon találhatók.