Qu'est-ce que ClassLoader ? Parmi tous les langages de programmation, Java est unique en ce sens qu'il s'exécute sur la machine virtuelle Java. Cela signifie que le programme compilé s'exécutera sur la machine cible sous une forme unique, indépendante de la plate-forme, autre que le format de la machine cible. Ce format est très différent des programmes exécutables traditionnels à bien des égards.
Java ClassLoader est un composant crucial mais souvent négligé du système d'exploitation Java. Il est responsable de la recherche et du chargement des fichiers de classe au moment de l'exécution. La création d'un ClassLoader personnalisé peut redéfinir complètement la façon dont les fichiers de classe sont chargés dans le système.
Ce tutoriel fournit un aperçu général du ClassLoader de Java et donne un exemple de ClassLoader personnalisé. Ce ClassLoader se compilera automatiquement avant de charger le code. Vous apprendrez ce que fait un ClassLoader et comment créer un ClassLoader personnalisé.
Ce didacticiel nécessite que les lecteurs aient une compréhension de base de la programmation Java, notamment la création, la compilation et l'exécution de programmes Java simples en ligne de commande.
Après avoir lu ce tutoriel, vous saurez comment :
Étendre les fonctionnalités de la JVM
Créer un fichier personnalisé ClassLoader
Découvrez comment intégrer ClassLoader dans les applications Java
Modifiez ClassLoader pour vous conformer à la version Java2
Parmi tous les langages de programmation, Java est unique en ce sens qu'il s'exécute sur la machine virtuelle Java. Cela signifie que le programme compilé s'exécutera sur la machine cible sous une forme unique, indépendante de la plate-forme, autre que le format de la machine cible. Ce format est très différent des programmes exécutables traditionnels à bien des égards.
La plus grande différence entre un programme Java et un programme C ou C++ est qu'il ne s'agit pas d'un seul fichier exécutable, mais se compose de nombreux fichiers de classe distincts, chaque fichier de classe correspondant à une classe Java.
De plus, ces fichiers de classe ne sont pas chargés en mémoire en une seule fois, mais à la demande. ClassLoader est la partie de JVM qui charge les classes en mémoire.
De plus, Java ClassLoader est écrit en Java. Cela signifie que vous pouvez facilement créer votre propre ClassLoader sans connaître plus de détails sur la JVM.
Si la JVM possède déjà un ClassLoader, pourquoi avez-vous besoin d'en écrire un autre ? Bonne question, le ClassLoader par défaut sait uniquement comment charger les fichiers de classe à partir du système de fichiers local. Dans les scénarios généraux, lorsque vous écrivez du code localement et que vous le compilez localement, cela est tout à fait suffisant.
Cependant, l'une des fonctionnalités les plus novatrices du langage JAVA est que les cours peuvent être obtenus à partir du disque dur local ou en dehors d'Internet. Par exemple, le navigateur utilise un ClassLoader personnalisé pour obtenir le contenu exécutable du site Web.
Il existe de nombreuses autres façons d'obtenir des fichiers de classe. En plus de charger des fichiers de classe localement ou en ligne, vous pouvez également utiliser des chargeurs de classe pour :
Vérifier automatiquement les signatures numériques avant d'exécuter du code non fiable
Utilisez le code de déchiffrement transparent par mot de passe fourni par l'utilisateur
Créez des classes dynamiques personnalisées en fonction des besoins spécifiques de l'utilisateur
Toute génération Le contenu du bytecode Java peut être intégré à votre application.
Si vous avez déjà utilisé une applet, vous devez avoir utilisé un chargeur de classe personnalisé.
Lorsque Sun a lancé le langage Java, l'une des choses les plus passionnantes était d'observer comment la technologie effectuait le chargement en temps opportun du code à partir d'un serveur Web distant. Ils envoient du bytecode via une connexion HTTP à partir d'un serveur Web distant et l'exécutent localement, ce qui est passionnant.
La capacité du langage Java à prendre en charge un ClassLoader personnalisé rend cette idée possible. Il existe un ClassLoader personnalisé dans l'applet. Il ne charge pas le fichier de classe à partir du système de fichiers local, mais l'obtient à partir du serveur Web distant, charge le bytecode d'origine via Http, puis le convertit en classe dans le jvm.
Les chargeurs de classes dans les navigateurs et les applets ont également d'autres fonctions : gestion de la sécurité, empêcher les applets de différentes pages de s'affecter les unes les autres, etc.
Ensuite, nous créerons un chargeur de classe personnalisé appelé CompilingClassLoader(CCL)
, et CCL nous aidera à compiler le code Java. C'est essentiellement comme créer un simple programme make directement dans le système en cours d'exécution.
L'objectif fondamental de ClassLoader est de fournir des services pour les demandes de classe. La JVM a besoin d'une classe, elle demande donc au ClassLoader de charger la classe par son nom. ClassLoader tente de renvoyer un objet représentant la classe.
Un ClassLoader personnalisé peut être créé en remplaçant les méthodes correspondant aux différentes étapes de ce processus.
Dans le reste de cet article, vous découvrirez quelques méthodes clés de ClassLoader. Vous apprendrez ce que fait chaque méthode et comment elle est appelée lors du chargement de la classe. Vous apprendrez également ce qui doit être fait lorsque vous personnalisez un ClassLoader. La
loadClass
méthode ## et la méthode ClassLoader.loadClass()
sont les entrées de ClassLoader. Ses balises de méthode sont les suivantes :
Class loadClass(String name, boolean resolve)
name
Le paramètre représente le nom de la classe requise par la JVM, telle que Foo
ou java.lang.Object
.
resolve
Le paramètre indique si la classe doit être analysée. L'analyse de classe peut être comprise comme la préparation complète de la classe à l'exécution. L'analyse n'est pas nécessaire. Si la JVM a uniquement besoin de déterminer l'existence de la classe ou de connaître sa classe parent, il n'est pas nécessaire de l'analyser.
Avant java1.1, le ClassLoader personnalisé n'avait besoin que de remplacer la méthode loadClass
. La méthode
defineClass
est le cœur de l'ensemble du ClassLoader. Cette méthode convertit le tableau d'octets d'origine en un objet Class
. Le tableau d'octets bruts contient des données obtenues localement ou à distance.
defineClass
est responsable de la gestion de nombreuses parties complexes, mystérieuses et dépendantes de la mise en œuvre de la JVM. Il analyse le bytecode en structures de données d'exécution, vérifie leur validité, etc. Ne vous inquiétez pas, vous n’êtes pas obligé de le mettre en œuvre vous-même. En fait, vous ne pouvez pas du tout le remplacer car la méthode est définitive. La méthode
findSystemClass方法
findSysetmClass
charge les fichiers à partir du système de fichiers local. Il recherche un fichier de classe dans le système de fichiers local et, s'il est présent, utilise defineClass
pour le convertir d'octets bruts en objet de classe. Il s'agit du mécanisme par défaut de la JVM pour charger les classes lors de l'exécution d'une application Java.
Pour le ClassLoader personnalisé, nous n'appellerons la méthode findSystemClass
qu'après avoir essayé d'autres méthodes pour charger le contenu de la classe. La raison est simple : un ClassLoader personnalisé contient quelques étapes pour charger des classes spéciales, mais toutes les classes ne sont pas des classes spéciales. Par exemple, même si ClassLoader doit récupérer certaines classes du site Web distant, de nombreuses classes doivent encore être chargées à partir de la bibliothèque Java locale. Ces classes ne sont pas notre objectif, nous avons donc besoin de la JVM pour les obtenir par défaut.
L'ensemble du processus est le suivant :
Demander un ClassLoader personnalisé pour charger une classe
Vérifiez si le serveur distant a la classe
Si oui, récupérez et retournez
Sinon, nous supposons que la classe est une classe de base locale et appelons findSystemClass
depuis chargé depuis le système de fichiers.
Dans la plupart des ClassLoaders personnalisés, vous devez d'abord utiliser findSystemClass
pour réduire l'accès aux sites Web distants, car la plupart des classes Java se trouvent dans des bibliothèques de classes locales. Cependant, comme vous le verrez dans la section suivante, nous ne voulons pas que la JVM charge les classes du système de fichiers local avant de compiler automatiquement le code de l'application.
resolveClass
MéthodeComme mentionné précédemment, le chargement de classe peut être effectué partiellement (sans analyse) ou complètement (avec analyse). Lorsque nous implémentons notre propre méthode loadClass
, nous devrons peut-être appeler la méthode resolveClass
, en fonction de la valeur du paramètre loadClass
dans resolve
. La méthode
findLoadedClass
findLoadedClass
agit comme un mécanisme d'appel de mise en cache : lorsque la méthode loadClass
est appelée, elle appellera cette méthode pour vérifier si la classe a été chargée . Élimine les chargements répétés. Cette méthode doit être appelée en premier.
Dans notre exemple loadClass
effectuez les étapes suivantes (ici nous ne prêterons pas une attention particulière à la méthode magique utilisée pour obtenir le fichier de classe. Il peut provenir de local, Obtenu à partir du réseau ou de fichiers compressés, bref nous avons obtenu le bytecode du fichier de classe d'origine) :
Appelez findLoadedClass
pour vérifier si la classe a été chargée
Sinon, utilisez la magie pour obtenir le bytecode brut
Si vous obtenez le bytecode, appelez defineClass
pour le convertir en Class
Objet
Si aucun bytecode n'est obtenu, appelez findSystemClass
pour voir si la classe
peut être obtenue à partir du système de fichiers local si resolve
>Si la valeur est vraie, appelez resolveClass
pour analyser l'objet Class
Si la classe n'est toujours pas trouvée, lancez ClassNotFoundException
Sinon, renvoyez la classe à l'appelant
CompilingClassLoader
CCL
permet de s'assurer que le code a été compilé et qu'il s'agit de la dernière version.
Ce qui suit est une description de la classe :
Lorsqu'une classe est nécessaire, vérifiez si la classe est sur le disque, dans le répertoire courant ou le sous-répertoire correspondant
Si la classe n'existe pas, mais que son code source existe, appelez le compilateur Java pour générer le fichier de classe
Si le fichier de classe existe, vérifiez si c'est La version du code source est ancienne. Si elle est inférieure à la version du code source, régénérez le fichier de classe
Si la compilation échoue ou si d'autres raisons empêchent la génération du fichier de classe. à partir du code source, lancez ClassNotFoundException
S'il n'y a toujours pas de fichier de classe, alors il se trouve peut-être dans d'autres bibliothèques, appelez findSystemClass
pour voir s'il est utile
Si c'est toujours Classe introuvable, lancez ClassNotFoundException
sinon, retournez la classe
在深入研究之前,我们应该回过头来看一下Java的编译机制。总的来说,当你请求一个类的时候,Java不只是编译各种类信息,它还编译了别的相关联的类。
CCL会按需一个接一个的编译相关的类。但是,当CCL编译完一个类之后试着去编译其它相关类的时候会发现,其它的类已经编译完成了。为什么呢?Java编译器遵循一个规则:如果一个类不存在,或者它相对于源码已经过时了,就需要编译它。从本质上讲,Java编译器先CCL一步完成了大部分的工作。
CCL在编译类的时候会打印其编译的应用程序。在大多数场景里面,你会看到它在程序的主类上调用编译器。
但是,有一种情况是不会在第一次调用时编译所有类的的。如果你通过类名Class.forNasme
加载一个类,Java编译器不知道该类需要哪些信息。在这种场景下,你会看到CCL会再次运行Java编译器。
CompilingClassLoader
为了使用CCL,我们需要用一种独特的方式启动程序。正常的启动程序如下:
% java Foo arg1 arg2
而我们启动方式如下:
% java CCLRun Foo arg1 arg2
CCLRun是一个特殊的桩程序,它会创建一个CompilingClassLoader并使用它来加载程序的main方法,确保整个程序的类会通过CompilingClassLoader加载。CCLRun使用Java反射API来调用main方法并传参
Java1.2以后ClassLoader有一些变动。原有版本的ClassLoader还是兼容的,而且在新版本下开发ClassLoader更容易了
新的版本下采用了delegate模型。ClassLoader可以将类的请求委托给父类。默认的实现会先调用父类的实现,在自己加载。但是这种模式是可以改变的。所有的ClassLoader的根节点是系统ClassLoader。它默认会从文件系统中加载类。
loadClass
默认实现一个自定义的loadClass
方法通常会尝试用各种方法来获得一个类的信息。如果你写了大量的ClassLoader,你会发现基本上是在重复写复杂而变化不大的代码。
java1.2的loadClass
的默认实现中允许你直接重写findClass
方法,loadClass
将会在合适的时候调用该方法。
这种方式的好处在于你无须重写loadClass
方法。
findClass
该方法会被loadClass
的默认实现调用。findClass
是为了包含ClassLoader所有特定的代码,而无需写大量重负的其他代码
getSystenClassLoader
无论你是否重写了findClass
或是loadClass
方法,getSystemClassLoader
允许你直接获得系统的ClassLoader(而不是隐式的用findSystemClass
获得)
getParent
该方法允许类加载器获取其父类加载器,从而将请求委托给它。当你自定义的加载器无法找到类时,可以使用该方法。父类加载器是指包含创建该类加载代码的加载器。
// $Id$ import java.io.*; /* A CompilingClassLoader compiles your Java source on-the-fly. It checks for nonexistent .class files, or .class files that are older than their corresponding source code. */ public class CompilingClassLoader extends ClassLoader { // Given a filename, read the entirety of that file from disk // and return it as a byte array. private byte[] getBytes( String filename ) throws IOException { // Find out the length of the file File file = new File( filename ); long len = file.length(); // Create an array that's just the right size for the file's // contents byte raw[] = new byte[(int)len]; // Open the file FileInputStream fin = new FileInputStream( file ); // Read all of it into the array; if we don't get all, // then it's an error. int r = fin.read( raw ); if (r != len) throw new IOException( "Can't read all, "+r+" != "+len ); // Don't forget to close the file! fin.close(); // And finally return the file contents as an array return raw; } // Spawn a process to compile the java source code file // specified in the 'javaFile' parameter. Return a true if // the compilation worked, false otherwise. private boolean compile( String javaFile ) throws IOException { // Let the user know what's going on System.out.println( "CCL: Compiling "+javaFile+"..." ); // Start up the compiler Process p = Runtime.getRuntime().exec( "javac "+javaFile ); // Wait for it to finish running try { p.waitFor(); } catch( InterruptedException ie ) { System.out.println( ie ); } // Check the return code, in case of a compilation error int ret = p.exitValue(); // Tell whether the compilation worked return ret==0; } // The heart of the ClassLoader -- automatically compile // source as necessary when looking for class files public Class loadClass( String name, boolean resolve ) throws ClassNotFoundException { // Our goal is to get a Class object Class clas = null; // First, see if we've already dealt with this one clas = findLoadedClass( name ); //System.out.println( "findLoadedClass: "+clas ); // Create a pathname from the class name // E.g. java.lang.Object => java/lang/Object String fileStub = name.replace( '.', '/' ); // Build objects pointing to the source code (.java) and object // code (.class) String javaFilename = fileStub+".java"; String classFilename = fileStub+".class"; File javaFile = new File( javaFilename ); File classFile = new File( classFilename ); //System.out.println( "j "+javaFile.lastModified()+" c "+ // classFile.lastModified() ); // First, see if we want to try compiling. We do if (a) there // is source code, and either (b0) there is no object code, // or (b1) there is object code, but it's older than the source if (javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) { try { // Try to compile it. If this doesn't work, then // we must declare failure. (It's not good enough to use // and already-existing, but out-of-date, classfile) if (!compile( javaFilename ) || !classFile.exists()) { throw new ClassNotFoundException( "Compile failed: "+javaFilename ); } } catch( IOException ie ) { // Another place where we might come to if we fail // to compile throw new ClassNotFoundException( ie.toString() ); } } // Let's try to load up the raw bytes, assuming they were // properly compiled, or didn't need to be compiled try { // read the bytes byte raw[] = getBytes( classFilename ); // try to turn them into a class clas = defineClass( name, raw, 0, raw.length ); } catch( IOException ie ) { // This is not a failure! If we reach here, it might // mean that we are dealing with a class in a library, // such as java.lang.Object } //System.out.println( "defineClass: "+clas ); // Maybe the class is in a library -- try loading // the normal way if (clas==null) { clas = findSystemClass( name ); } //System.out.println( "findSystemClass: "+clas ); // Resolve the class, if any, but only if the "resolve" // flag is set to true if (resolve && clas != null) resolveClass( clas ); // If we still don't have a class, it's an error if (clas == null) throw new ClassNotFoundException( name ); // Otherwise, return the class return clas; } }
import java.lang.reflect.*; /* CCLRun executes a Java program by loading it through a CompilingClassLoader. */ public class CCLRun { static public void main( String args[] ) throws Exception { // The first argument is the Java program (class) the user // wants to run String progClass = args[0]; // And the arguments to that program are just // arguments 1..n, so separate those out into // their own array String progArgs[] = new String[args.length-1]; System.arraycopy( args, 1, progArgs, 0, progArgs.length ); // Create a CompilingClassLoader CompilingClassLoader ccl = new CompilingClassLoader(); // Load the main class through our CCL Class clas = ccl.loadClass( progClass ); // Use reflection to call its main() method, and to // pass the arguments in. // Get a class representing the type of the main method's argument Class mainArgType[] = { (new String[0]).getClass() }; // Find the standard main method in the class Method main = clas.getMethod( "main", mainArgType ); // Create a list containing the arguments -- in this case, // an array of strings Object argsArray[] = { progArgs }; // Call the method main.invoke( null, argsArray ); } }
public class Foo { static public void main( String args[] ) throws Exception { System.out.println( "foo! "+args[0]+" "+args[1] ); new Bar( args[0], args[1] ); } }
import baz.*; public class Bar { public Bar( String a, String b ) { System.out.println( "bar! "+a+" "+b ); new Baz( a, b ); try { Class booClass = Class.forName( "Boo" ); Object boo = booClass.newInstance(); } catch( Exception e ) { e.printStackTrace(); } } }
package baz; public class Baz { public Baz( String a, String b ) { System.out.println( "baz! "+a+" "+b ); } }
public class Boo { public Boo() { System.out.println( "Boo!" ); } }
相关文章:
基于Java类的加载方式之classloader类加载器详解
相关视频:
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!