Maison  >  Article  >  Java  >  Pourquoi écrire ClassLoader ? Compréhension approfondie du chargeur de classe en Java

Pourquoi écrire ClassLoader ? Compréhension approfondie du chargeur de classe en Java

php是最好的语言
php是最好的语言original
2018-07-27 09:36:402106parcourir

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.

Avant-propos

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

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.

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.

Pourquoi écrire ClassLoader

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.

Exemple de ClassLoader personnalisé

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.

Structure du ClassLoader

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

loadClassmé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.

resolveLe 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

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.

resolveClassMéthode

Comme 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.

Intégrez-le

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的编译机制。总的来说,当你请求一个类的时候,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方法并传参

Java2中ClassLoader的变化

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类加载器详解

Java虚拟机学习 - 类加载器(ClassLoader)

相关视频:

全面解析Java注解

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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn