Ricerca…


Osservazioni

Un classloader è una classe il cui scopo principale è mediare la posizione e il caricamento delle classi utilizzate da un'applicazione. Un programma di caricamento classe può anche trovare e caricare risorse .

Le classi classloader standard possono caricare classi e risorse dalle directory nel file system e dai file JAR e ZIP. Possono anche scaricare e memorizzare in cache i file JAR e ZIP da un server remoto.

I programmi di caricamento di classe sono normalmente concatenati, in modo che la JVM proverà a caricare le classi dalle librerie di classi standard preferibilmente alle origini fornite dall'applicazione. I classloader personalizzati consentono al programmatore di modificare questo. Inoltre può fare cose come decodificare i file bytecode e la modifica bytecode.

Istanziare e usare un classloader

Questo esempio di base mostra come un'applicazione può creare un'istanza di un classloader e utilizzarla per caricare dinamicamente una classe.

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

Il programma di caricamento classi creato in questo esempio avrà come predefinito il classloader predefinito e tenterà prima di cercare qualsiasi classe nel classloader padre prima di cercare in "extra.jar". Se la classe richiesta è già stata caricata, la chiamata findClass restituirà il riferimento alla classe caricata in precedenza.

La chiamata findClass può fallire in molti modi. I più comuni sono:

  • Se la classe indicata non può essere trovata, la chiamata con lancio ClassNotFoundException .
  • Se la classe denominata dipende da un'altra classe che non può essere trovata, la chiamata genererà NoClassDefFoundError .

Implementazione di un classLoader personalizzato

Ogni caricatore personalizzato deve estendere direttamente o indirettamente la classe java.lang.ClassLoader . I punti di estensione principali sono i seguenti metodi:

  • findClass(String) - sovraccaricare questo metodo se il classloader segue il modello di delega standard per il caricamento della classe.
  • loadClass(String, boolean) - sovraccaricare questo metodo per implementare un modello di delega alternativo.
  • findResource e findResources : sovraccaricare questi metodi per personalizzare il caricamento delle risorse.

I defineClass metodi che sono responsabili per caricare effettivamente la classe da una matrice di byte sono final per evitare sovraccarichi. Qualsiasi comportamento personalizzato deve essere eseguito prima di chiamare defineClass .

Ecco un semplice che carica una classe specifica da un array di byte:

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);
        }
    }
}

Poiché abbiamo solo sovrascritto il metodo findClass , questo caricatore di classi personalizzato si comporterà come segue quando viene chiamato loadClass .

  1. Il metodo loadClass del classloader chiama findLoadedClass per vedere se una classe con questo nome è già stata caricata da questo classloader. Se ciò riesce, l'oggetto Class risultante viene restituito al richiedente.
  2. Il metodo loadClass quindi delega al classloader genitore chiamando la sua chiamata loadClass . Se il genitore può gestire la richiesta, restituirà un oggetto di Class che viene quindi restituito al richiedente.
  3. Se il classloader genitore non può caricare la classe, findClass chiama quindi il nostro metodo findClass override, passando il nome della classe da caricare.
  4. Se il nome richiesto corrisponde a this.classname , chiamiamo defineClass per caricare la classe effettiva this.classfile byte this.classfile . L'oggetto Class risultante viene quindi restituito.
  5. Se il nome non coincide, gettiamo ClassNotFoundException .

Caricamento di un file .class esterno

Per caricare una classe, dobbiamo prima definirla. La classe è definita da ClassLoader . C'è solo un problema, Oracle non ha scritto il codice di ClassLoader con questa funzione disponibile. Per definire la classe avremo bisogno di accedere a un metodo denominato defineClass() che è un metodo privato di ClassLoader .

Per accedervi, ciò che faremo è creare una nuova classe, ByteClassLoader , ed estenderla a ClassLoader . Ora che abbiamo esteso la nostra classe a ClassLoader , possiamo accedere ai metodi privati ​​di ClassLoader . Per rendere disponibile defineClass() , creeremo un nuovo metodo che funzionerà come un mirror per il metodo private defineClass() . Per chiamare il metodo privato avremo bisogno il nome della classe, name , i byte di classe, classBytes , offset del primo byte, che sarà 0 perché classBytes dati 'inizia alle classBytes[0] , e compensare dell'ultimo byte, che sarà classBytes.lenght perché rappresenta la dimensione dei dati, che sarà l'ultimo offset.

public class ByteClassLoader extends ClassLoader {

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

}

Ora abbiamo un metodo pubblico defineClass() . Può essere chiamato passando il nome della classe e i byte della classe come argomenti.

Diciamo che abbiamo classe denominata MyClass nel pacchetto stackoverflow ...

Per chiamare il metodo abbiamo bisogno dei byte di classe, quindi creiamo un oggetto Path che rappresenta il percorso della nostra classe utilizzando il metodo Paths.get() e passando il percorso della classe binary come argomento. Ora, possiamo ottenere i byte di classe con Files.readAllBytes(path) . Quindi creiamo un'istanza ByteClassLoader e usiamo il metodo che abbiamo creato, defineClass() . Abbiamo già i byte di classe ma per chiamare il nostro metodo abbiamo anche bisogno del nome della classe che è dato dal nome del pacchetto (punto) il nome canonico della classe, in questo caso stackoverflow.MyClass .

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

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

Nota : il metodo defineClass() restituisce un oggetto di Class<?> . Puoi salvarlo se vuoi.

Per caricare la classe, chiamiamo loadClass() e passiamo il nome della classe. Questo metodo può generare ClassNotFoundException quindi è necessario utilizzare un blocco cath try

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


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow