Zoeken…


Opmerkingen

Een classloader is een klasse waarvan het primaire doel is om te bemiddelen bij de locatie en het laden van klassen die door een toepassing worden gebruikt. Een klassenlader kan ook bronnen zoeken en laden.

De standaard classloader-klassen kunnen klassen en bronnen laden uit mappen in het bestandssysteem en uit JAR- en ZIP-bestanden. Ze kunnen ook JAR- en ZIP-bestanden downloaden en opslaan in een cache van een externe server.

Classloaders zijn normaal gesproken geketend, zodat de JVM zal proberen klassen uit de standaardklassebibliotheken te laden in plaats van door de applicatie geleverde bronnen. Met aangepaste classloaders kan de programmeur dit wijzigen. Het kan ook dingen doen zoals het decoderen van bytecode-bestanden en bytecode-aanpassing.

Instantiëren en een classloader gebruiken

Dit basisvoorbeeld laat zien hoe een toepassing een classloader kan instantiëren en gebruiken om een klasse dynamisch te laden.

URL[] urls = new URL[] {new URL("file:/home/me/extras.jar")};
Classloader loader = new URLClassLoader(urls);
Class<?> myObjectClass = loader.findClass("com.example.MyObject");

De classloader die in dit voorbeeld is gemaakt, heeft de standaardclassloader als bovenliggend element en probeert eerst elke klasse in de bovenliggende classloader te vinden voordat wordt gekeken naar "extra.jar". Als de gevraagde klasse al is geladen, findClass aanroep findClass de verwijzing naar de eerder geladen klasse.

Het gesprek findClass kan op verschillende manieren mislukken. De meest voorkomende zijn:

  • Als de genoemde klasse niet kan worden gevonden, de aanroep met throw ClassNotFoundException .
  • Als de genoemde klasse afhankelijk is van een andere klasse die niet kan worden gevonden, wordt NoClassDefFoundError .

Een aangepaste classLoader implementeren

Elke aangepaste lader moet de klasse java.lang.ClassLoader direct of indirect uitbreiden. De belangrijkste uitbreidingspunten zijn de volgende methoden:

  • findClass(String) - overbelast deze methode als uw classloader het standaard delegatiemodel volgt voor het laden van klassen.
  • loadClass(String, boolean) - overbelast deze methode om een alternatief delegatiemodel te implementeren.
  • findResource en findResources - overbelast deze methoden om het laden van bronnen aan te passen.

De defineClass methoden die verantwoordelijk zijn voor het daadwerkelijk laden van de klasse uit een bytearray zijn final om overbelasting te voorkomen. Eventueel aangepast gedrag moet worden uitgevoerd voordat defineClass .

Hier is een eenvoudige die een specifieke klasse uit een byte-array laadt:

public class ByteArrayClassLoader extends ClassLoader {
    private String classname;
    private byte[] classfile;

    public ByteArrayClassLoader(String classname, byte[] classfile) {
        this.classname = classname;
        this.classfile = classfile.clone();
    }

    @Override
    protected Class findClass(String classname) throws ClassNotFoundException {
        if (classname.equals(this.classname)) {
            return defineClass(classname, classfile, 0, classfile.length);
        } else {
            throw new ClassNotFoundException(classname);
        }
    }
}

Omdat we alleen de methode findClass hebben overschreven, findClass deze aangepaste klassenlader zich als volgt wanneer loadClass wordt aangeroepen.

  1. De classloader's loadClass methode oproepen findLoadedClass om te zien of een klasse met deze naam al door deze klasse loader is geladen. Als dat lukt, wordt het resulterende Class object teruggestuurd naar de aanvrager.
  2. De methode loadClass delegeert vervolgens naar de bovenliggende classloader door de aanroep loadClass roepen. Als de ouder het verzoek kan verwerken, retourneert deze een Class object dat vervolgens wordt teruggestuurd naar de aanvrager.
  3. Als de bovenliggende classloader de klasse niet kan laden, roept findClass onze override findClass methode aan, waarbij de naam van de te laden klasse wordt findClass .
  4. Als de gevraagde naam overeenkomt met this.classname , roepen we defineClass aan om de werkelijke klasse uit de this.classfile defineClass te laden. Het resulterende Class object wordt vervolgens geretourneerd.
  5. Als de naam niet overeenkomt, gooien we ClassNotFoundException .

Een extern .class-bestand laden

Om een klasse te laden, moeten we deze eerst definiëren. De klasse wordt gedefinieerd door de ClassLoader . Er is maar één probleem, Oracle heeft de ClassLoader code niet geschreven met deze functie beschikbaar. Om de klasse te definiëren, hebben we toegang nodig tot een methode met de naam defineClass() , een ClassLoader van ClassLoader .

Om toegang te krijgen, maken we een nieuwe klasse, ByteClassLoader , en breiden deze uit naar ClassLoader . Nu we onze klasse hebben uitgebreid naar ClassLoader , hebben we toegang tot de ClassLoader de ClassLoader . Om defineClass() beschikbaar te maken, maken we een nieuwe methode die als een spiegel zal fungeren voor de private methode defineClass() . Tot het private methode zullen we de naam van de klasse, moet bellen name , de klasse bytes, classBytes , de eerste byte te compenseren, die zal worden 0 , omdat classBytes data begint bij classBytes[0] , en de laatste byte te compenseren, die zal worden classBytes.lenght omdat dit de grootte van de gegevens vertegenwoordigt, die de laatste offset is.

public class ByteClassLoader extends ClassLoader {

    public Class<?> defineClass(String name, byte[] classBytes) {
        return defineClass(name, classBytes, 0, classBytes.length);
    }

}

Nu hebben we een openbare methode defineClass() . Het kan worden opgeroepen door de naam van de klasse en de klassebytes als argumenten door te geven.

Laten we zeggen dat we klasse MyClass in het pakket stackoverflow ...

Om de methode aan te roepen hebben we de klasse-bytes nodig, dus maken we een Path object dat het pad van onze klasse vertegenwoordigt door de methode Paths.get() gebruiken en het pad van de binaire klasse als argument door te geven. Nu kunnen we de Files.readAllBytes(path) met Files.readAllBytes(path) . Dus maken we een ByteClassLoader instantie en gebruiken we de methode die we hebben gemaakt, defineClass() . We hebben al de class-bytes, maar om onze methode aan te roepen, hebben we ook de klassenaam nodig die wordt gegeven door de pakketnaam (punt), de canonieke naam van de klasse, in dit geval stackoverflow.MyClass .

Path path = Paths.get("MyClass.class");

ByteClassLoader loader = new ByteClassLoader();
loader.defineClass("stackoverflow.MyClass", Files.readAllBytes(path);

Opmerking : de methode defineClass() retourneert een object Class<?> . Je kunt het opslaan als je wilt.

Om de klasse te laden, roepen we gewoon loadClass() en geven we de klassenaam door. Deze methode kan een ClassNotFoundException dus we moeten een try cath-blok gebruiken

try{
    loader.loadClass("stackoverflow.MyClass");
} catch(ClassNotFoundException e){
    e.printStackTrace();
}


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow