Java Language
classloader
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
efindResources
: 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
.
- Il metodo
loadClass
del classloader chiamafindLoadedClass
per vedere se una classe con questo nome è già stata caricata da questo classloader. Se ciò riesce, l'oggettoClass
risultante viene restituito al richiedente. - Il metodo
loadClass
quindi delega al classloader genitore chiamando la sua chiamataloadClass
. Se il genitore può gestire la richiesta, restituirà un oggetto diClass
che viene quindi restituito al richiedente. - Se il classloader genitore non può caricare la classe,
findClass
chiama quindi il nostro metodofindClass
override, passando il nome della classe da caricare. - Se il nome richiesto corrisponde a
this.classname
, chiamiamodefineClass
per caricare la classe effettivathis.classfile
bytethis.classfile
. L'oggettoClass
risultante viene quindi restituito. - 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();
}