Java Language
Chargeurs de Classes
Recherche…
Remarques
Un classloader est une classe dont le but principal est d'assurer la médiation de l'emplacement et du chargement des classes utilisées par une application. Un chargeur de classe peut également rechercher et charger des ressources .
Les classes de chargeur de classe standard peuvent charger des classes et des ressources à partir de répertoires du système de fichiers et de fichiers JAR et ZIP. Ils peuvent également télécharger et mettre en cache des fichiers JAR et ZIP à partir d'un serveur distant.
Les chargeurs de classe sont normalement enchaînés, de sorte que la JVM essaye de charger les classes des bibliothèques de classes standard de préférence aux sources fournies par l'application. Les chargeurs de classes personnalisés permettent au programmeur de modifier cela. Le peut également faire des choses telles que le décryptage des fichiers bytecode et la modification bytecode.
Instancier et utiliser un chargeur de classe
Cet exemple de base montre comment une application peut instancier un chargeur de classe et l'utiliser pour charger dynamiquement une classe.
URL[] urls = new URL[] {new URL("file:/home/me/extras.jar")};
Classloader loader = new URLClassLoader(urls);
Class<?> myObjectClass = loader.findClass("com.example.MyObject");
Le classloader créé dans cet exemple aura le classloader par défaut en tant que parent, et essaiera d'abord de trouver une classe dans le classloader parent avant de chercher dans "extra.jar". Si la classe demandée a déjà été chargée, l'appel findClass
renverra la référence à la classe précédemment chargée.
L'appel findClass
peut échouer de différentes manières. Les plus courants sont:
- Si la classe nommée ne peut pas être trouvée, l'appel avec jeter
ClassNotFoundException
. - Si la classe nommée dépend d'une autre classe introuvable, l'appel lancera
NoClassDefFoundError
.
Implémentation d'un classLoader personnalisé
Chaque chargeur personnalisé doit étendre directement ou indirectement la classe java.lang.ClassLoader
. Les principaux points d'extension sont les méthodes suivantes:
-
findClass(String)
- surcharge cette méthode si votre chargeur de classe suit le modèle de délégation standard pour le chargement de classe. -
loadClass(String, boolean)
- surcharge cette méthode pour implémenter un autre modèle de délégation. -
findResource
etfindResources
- surchargent ces méthodes pour personnaliser le chargement des ressources.
Les méthodes defineClass
qui sont chargées de charger réellement la classe à partir d'un tableau d'octets sont final
pour éviter la surcharge. Tout comportement personnalisé doit être effectué avant d'appeler defineClass
.
Voici un simple qui charge une classe spécifique à partir d'un tableau d'octets:
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);
}
}
}
Comme nous avons uniquement remplacé la méthode findClass
, ce chargeur de classe personnalisé se comportera comme suit lorsque loadClass
est appelé.
- La méthode
loadClass
du chargeur deloadClass
appellefindLoadedClass
pour voir si une classe portant ce nom a déjà été chargée par ce chargeur de classe. Si cela réussit, l'objetClass
résultant est renvoyé au demandeur. - La méthode
loadClass
délègue ensuite au chargeur de classes parent en appelant son appelloadClass
. Si le parent peut traiter la demande, il retournera un objetClass
qui est ensuite renvoyé au demandeur. - Si le chargeur de classe parent ne peut pas charger la classe,
findClass
appelle alors notre méthodefindClass
substitution, en transmettant le nom de la classe à charger. - Si le nom demandé correspond à
this.classname
, nous appelonsdefineClass
pour charger la classe réelle à partir duthis.classfile
octetsthis.classfile
. L'objetClass
résultant est ensuite renvoyé. - Si le nom ne correspond pas, nous lançons
ClassNotFoundException
.
Chargement d'un fichier .class externe
Pour charger une classe, il faut d'abord la définir. La classe est définie par le ClassLoader
. Il n'y a qu'un seul problème, Oracle n'a pas écrit le ClassLoader
du ClassLoader
avec cette fonctionnalité disponible. Pour définir la classe, nous devrons accéder à une méthode appelée defineClass()
qui est une méthode privée du ClassLoader
.
Pour y accéder, nous allons créer une nouvelle classe, ByteClassLoader
, et l'étendre à ClassLoader
. Maintenant que nous avons étendu notre classe à ClassLoader
, nous pouvons accéder aux méthodes privées du ClassLoader
. Pour rendre defineClass()
disponible, nous allons créer une nouvelle méthode qui agira comme un miroir pour la defineClass()
private defineClass()
. Pour appeler la méthode privée , nous aurons besoin du nom de la classe, le name
, les octets de classe, classBytes
, crédits compensatoires du premier octet, qui sera 0
parce que classBytes
les données commence à classBytes[0]
, et le décalage de dernier octet, qui sera classBytes.lenght
car il représente la taille des données, qui sera le dernier décalage.
public class ByteClassLoader extends ClassLoader {
public Class<?> defineClass(String name, byte[] classBytes) {
return defineClass(name, classBytes, 0, classBytes.length);
}
}
Maintenant, nous avons une defineClass()
publique defineClass()
. Il peut être appelé en passant le nom de la classe et les octets de la classe comme arguments.
Disons que nous avons une classe nommée MyClass
dans le paquet stackoverflow
...
Pour appeler la méthode, nous avons besoin des octets de classe, nous créons donc un objet Path
représentant notre chemin de classe en utilisant la méthode Paths.get()
et en passant le chemin de la classe binaire en argument. Maintenant, nous pouvons obtenir les octets de classe avec Files.readAllBytes(path)
. Nous créons donc une instance de ByteClassLoader
et utilisons la méthode que nous avons créée, defineClass()
. Nous avons déjà les octets de classe, mais pour appeler notre méthode, nous avons également besoin du nom de classe qui est donné par le nom du paquet (point), le nom canonique de la classe, dans ce cas stackoverflow.MyClass
.
Path path = Paths.get("MyClass.class");
ByteClassLoader loader = new ByteClassLoader();
loader.defineClass("stackoverflow.MyClass", Files.readAllBytes(path);
Remarque : La méthode defineClass()
renvoie un objet Class<?>
. Vous pouvez le sauvegarder si vous le souhaitez.
Pour charger la classe, il suffit d'appeler loadClass()
et de passer le nom de la classe. Cette méthode peut lancer une ClassNotFoundException
; nous devons donc utiliser un bloc try cath
try{
loader.loadClass("stackoverflow.MyClass");
} catch(ClassNotFoundException e){
e.printStackTrace();
}