Heim  >  Artikel  >  Java  >  Warum ClassLoader schreiben? Tiefes Verständnis des Klassenladers in Java

Warum ClassLoader schreiben? Tiefes Verständnis des Klassenladers in Java

php是最好的语言
php是最好的语言Original
2018-07-27 09:36:402106Durchsuche

Was ist ClassLoader? Unter allen Programmiersprachen ist Java einzigartig, da es auf der Java Virtual Machine läuft. Dies bedeutet, dass das kompilierte Programm auf dem Zielcomputer in einer eindeutigen, plattformunabhängigen Form ausgeführt wird, die nicht dem Format des Zielcomputers entspricht. Dieses Format unterscheidet sich in vielerlei Hinsicht stark von herkömmlichen ausführbaren Programmen.

Vorwort

Java ClassLoader ist eine wichtige, aber oft übersehene Komponente im Java-Betriebssystem. Es ist dafür verantwortlich, Klassendateien zur Laufzeit zu finden und zu laden. Durch das Erstellen eines benutzerdefinierten ClassLoaders kann die Art und Weise, wie Klassendateien in das System geladen werden, völlig neu definiert werden.

Dieses Tutorial bietet einen allgemeinen Überblick über den ClassLoader von Java und gibt ein Beispiel für einen benutzerdefinierten ClassLoader. Dieser ClassLoader wird automatisch kompiliert, bevor der Code geladen wird. Sie erfahren, was ein ClassLoader tut und wie Sie einen benutzerdefinierten ClassLoader erstellen.

Für dieses Tutorial müssen die Leser über grundlegende Kenntnisse der Java-Programmierung verfügen, einschließlich der Erstellung, Kompilierung und Ausführung einfacher Java-Befehlszeilenprogramme.

Nachdem Sie dieses Tutorial gelesen haben, wissen Sie, wie Sie:

  • die Funktionalität der JVM erweitern

  • einen Benutzerdefiniert erstellen ClassLoader

  • Erfahren Sie, wie Sie ClassLoader in Java-Anwendungen integrieren

  • Ändern Sie ClassLoader, um es an die Java2-Version anzupassen

Was ist ClassLoader?

Unter allen Programmiersprachen ist Java insofern einzigartig, als es auf der Java Virtual Machine läuft. Dies bedeutet, dass das kompilierte Programm auf dem Zielcomputer in einer eindeutigen, plattformunabhängigen Form ausgeführt wird, die nicht dem Format des Zielcomputers entspricht. Dieses Format unterscheidet sich in vielerlei Hinsicht stark von herkömmlichen ausführbaren Programmen.

Der größte Unterschied zwischen einem Java-Programm und einem C- oder C++-Programm besteht darin, dass es sich nicht um eine einzelne ausführbare Datei handelt, sondern aus vielen separaten Klassendateien besteht, wobei jede Klassendatei einer Java-Klasse entspricht.

Darüber hinaus werden diese Klassendateien nicht auf einmal, sondern bei Bedarf in den Speicher geladen. ClassLoader ist der Teil der JVM, der Klassen in den Speicher lädt.

Darüber hinaus ist Java ClassLoader in Java geschrieben. Dies bedeutet, dass Sie ganz einfach Ihren eigenen ClassLoader erstellen können, ohne weitere Details über die JVM zu kennen.

Warum ClassLoader schreiben

Wenn die JVM bereits einen ClassLoader hat, warum müssen Sie dann einen weiteren schreiben? Gute Frage, der Standard-ClassLoader weiß nur, wie man Klassendateien aus dem lokalen Dateisystem lädt. In allgemeinen Szenarien ist es völlig ausreichend, Code lokal zu schreiben und lokal zu kompilieren.

Eines der neuartigsten Merkmale der JAVA-Sprache ist jedoch, dass Kurse von der lokalen Festplatte oder außerhalb des Internets abgerufen werden können. Beispielsweise verwendet der Browser einen benutzerdefinierten ClassLoader, um ausführbare Inhalte von der Website abzurufen.

Es gibt viele andere Möglichkeiten, Klassendateien zu erhalten. Zusätzlich zum lokalen oder Online-Laden von Klassendateien können Sie Klassenlader auch verwenden, um:

  • Digitale Signaturen automatisch zu überprüfen, bevor nicht vertrauenswürdiger Code ausgeführt wird

  • Verwenden Sie den vom Benutzer bereitgestellten passworttransparenten Entschlüsselungscode.

  • Erstellen Sie benutzerdefinierte dynamische Klassen basierend auf den spezifischen Anforderungen des Benutzers.

Jede Generation. Der Inhalt von Java-Bytecode kann in Ihre Anwendung integriert werden.

Beispiel für einen benutzerdefinierten ClassLoader

Wenn Sie jemals ein Applet verwendet haben, müssen Sie einen benutzerdefinierten Klassenlader verwendet haben.

Als Sun die Java-Sprache veröffentlichte, war es eines der aufregendsten Dinge, zu beobachten, wie die Technologie das rechtzeitige Laden von Code von einem Remote-Webserver durchführte. Sie senden Bytecode über eine HTTP-Verbindung von einem Remote-Webserver und führen ihn lokal aus, was spannend ist.

Die Fähigkeit der Java-Sprache, benutzerdefinierte ClassLoader zu unterstützen, macht diese Idee möglich. Es gibt einen benutzerdefinierten ClassLoader im Applet. Er lädt die Klassendatei nicht aus dem lokalen Dateisystem, sondern ruft sie vom Remote-Webserver ab, lädt den ursprünglichen Bytecode über HTTP und konvertiert ihn dann in eine Klasse in der JVM.

Klassenlader in Browsern und Applets haben auch andere Funktionen: Sicherheitsverwaltung, verhindern, dass sich Applets auf verschiedenen Seiten gegenseitig beeinflussen usw.

Als nächstes erstellen wir einen benutzerdefinierten Klassenlader namens CompilingClassLoader(CCL), und CCL hilft uns beim Kompilieren von Java-Code. Es ist im Grunde so, als würde man ein einfaches Make-Programm direkt in das laufende System einbauen.

ClassLoader-Struktur

Der Hauptzweck von ClassLoader besteht darin, Dienste für Klassenanfragen bereitzustellen. Die JVM benötigt eine Klasse und fordert daher den ClassLoader auf, die Klasse anhand ihres Namens zu laden. ClassLoader versucht, ein Objekt zurückzugeben, das die Klasse darstellt.

Ein benutzerdefinierter ClassLoader kann durch Überschreiben der Methoden erstellt werden, die den verschiedenen Phasen dieses Prozesses entsprechen.

Im Rest dieses Artikels erfahren Sie mehr über einige wichtige Methoden in ClassLoader. Sie erfahren, was jede Methode bewirkt und wie sie beim Laden der Klasse aufgerufen wird. Außerdem erfahren Sie, was zu tun ist, wenn Sie einen ClassLoader anpassen. Die

loadClassMethode ## und die

ClassLoader.loadClass()-Methode sind die Eingänge zum ClassLoader. Sein Methoden-Tag lautet wie folgt:

Class loadClass(String name, boolean resolve)

name Der Parameter stellt den Namen der von der JVM benötigten Klasse dar, z. B. Foo oder java.lang.Object.

resolveDer Parameter gibt an, ob die Klasse analysiert werden muss. Das Parsen von Klassen kann als vollständige Vorbereitung der Klasse auf die Ausführung verstanden werden. Ein Parsen ist nicht erforderlich. Wenn die JVM nur die Existenz der Klasse oder ihre übergeordnete Klasse ermitteln muss, ist keine Analyse erforderlich.

Vor Java1.1 musste der benutzerdefinierte ClassLoader nur die loadClass-Methode überschreiben. Die Methode

defineClass-Methode

defineClass ist der Kern des gesamten ClassLoader. Diese Methode konvertiert das ursprüngliche Byte-Array in ein Class-Objekt. Das Rohbyte-Array enthält lokal oder remote erhaltene Daten.

defineClass ist für die Handhabung vieler komplexer, mysteriöser und umsetzungsabhängiger Teile der JVM verantwortlich. Es analysiert den Bytecode in Laufzeitdatenstrukturen, prüft deren Gültigkeit usw. Keine Sorge, Sie müssen dies nicht selbst umsetzen. Tatsächlich können Sie es überhaupt nicht überschreiben, da die Methode endgültig ist. Die Methode

findSystemClass方法

findSysetmClass lädt Dateien aus dem lokalen Dateisystem. Es sucht nach einer Klassendatei im lokalen Dateisystem und konvertiert sie, falls vorhanden, mit defineClass von Rohbytes in ein Klassenobjekt. Dies ist der Standardmechanismus der JVM zum Laden von Klassen beim Ausführen einer Java-Anwendung.

Für den benutzerdefinierten ClassLoader rufen wir die Methode findSystemClass erst auf, nachdem wir andere Methoden ausprobiert haben, um den Klasseninhalt zu laden. Der Grund ist einfach: Ein benutzerdefinierter ClassLoader enthält einige Schritte zum Laden spezieller Klassen, aber nicht alle Klassen sind spezielle Klassen. Selbst wenn ClassLoader beispielsweise einige Klassen von der Remote-Website abrufen muss, müssen immer noch viele Klassen aus der lokalen Java-Bibliothek geladen werden. Diese Klassen stehen nicht in unserem Fokus, daher benötigen wir die JVM, um sie auf die Standardmethode abzurufen.

Der gesamte Prozess ist wie folgt:

  • Fordern Sie einen benutzerdefinierten ClassLoader an, um eine Klasse zu laden

  • Überprüfen Sie, ob der Remote-Server hat die Klasse

  • Wenn ja, holen und zurückgeben

  • Wenn nicht, gehen wir davon aus, dass die Klasse eine lokale Basisklasse ist und rufen aus dem Dateisystem geladen. findSystemClass

In den meisten benutzerdefinierten ClassLoadern müssen Sie zuerst

verwenden, um den Zugriff auf Remote-Websites zu reduzieren, da sich die meisten Java-Klassen in lokalen Klassenbibliotheken befinden. Wie Sie im nächsten Abschnitt sehen werden, möchten wir jedoch nicht, dass die JVM Klassen aus dem lokalen Dateisystem lädt, bevor sie den Anwendungscode automatisch kompiliert. findSystemClass

MethoderesolveClass

Wie bereits erwähnt, kann das Laden von Klassen teilweise (ohne Parsing) oder vollständig (mit Parsing) erfolgen. Wenn wir unsere eigene

-Methode implementieren, müssen wir möglicherweise die loadClass-Methode aufrufen, abhängig vom Wert des resolveClass-Parameters in loadClass. Die resolve

-Methode findLoadedClass

-Methode fungiert als Caching-Aufrufmechanismus: Wenn die findLoadedClass-Methode aufgerufen wird, ruft sie diese Methode auf, um zu überprüfen, ob die Klasse geladen wurde . Eliminiert wiederholtes Laden. Diese Methode sollte zuerst aufgerufen werden. loadClass

Integrieren

In unserem Beispiel

führen Sie die folgenden Schritte aus (hier werden wir nicht besonders darauf achten, welche magische Methode verwendet wird, um die Klassendatei zu erhalten. Sie kann von lokal stammen, Aus dem Netzwerk oder aus komprimierten Dateien erhalten, kurz gesagt, wir haben den Bytecode der ursprünglichen Klassendatei erhalten): loadClass

  • Rufen Sie

    auf, um zu überprüfen, ob die Klasse geladen wurde findLoadedClass

  • Wenn nicht, verwenden Sie Magie, um den ursprünglichen Bytecode zu erhalten

  • Wenn Sie den Bytecode erhalten, rufen Sie

    auf, um ihn in ein defineClass Objekt Class umzuwandeln

  • Wenn kein Bytecode abgerufen wird, rufen Sie

    auf, um zu sehen, ob die Klasse findSystemClass

  • aus dem lokalen Dateisystem abgerufen werden kann, wenn

    >Wenn Der Wert ist wahr. Rufen Sie resolve auf, um das resolveClass-Objekt zu analysieren. Class

  • Wenn die Klasse immer noch nicht gefunden wird, werfen Sie

    ClassNotFoundException

  • Andernfalls geben Sie die Klasse an den Aufrufer zurück

CompilingClassLoader

wird verwendet, um sicherzustellen, dass der Code kompiliert wurde und die neueste Version ist. CCLDas Folgende ist eine Beschreibung der Klasse:

  • Wenn eine Klasse benötigt wird, überprüfen Sie, ob sich die Klasse auf der Festplatte, im aktuellen Verzeichnis oder im entsprechenden Unterverzeichnis befindet

  • Wenn die Klasse nicht existiert, aber ihr Quellcode existiert, rufen Sie den Java-Compiler auf, um die Klassendatei zu generieren

  • Wenn die Klassendatei existiert, prüfen Sie, ob es ist Die Quellcodeversion ist alt. Wenn sie niedriger als die Quellcodeversion ist, generieren Sie die Klassendatei neu.

  • Wenn die Kompilierung fehlschlägt oder andere Gründe die Generierung der Klassendatei verhindern Werfen Sie aus dem Quellcode

    ClassNotFoundException

  • Wenn noch keine Klassendatei vorhanden ist, befindet sie sich möglicherweise in einigen anderen Bibliotheken. Rufen Sie

    auf, um zu sehen, ob sie nützlich ist findSystemClass

  • Wenn die Klasse immer noch nicht gefunden wird, werfen Sie

    ClassNotFoundException

  • , andernfalls geben Sie die Klasse

    zurück

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注解

Das obige ist der detaillierte Inhalt vonWarum ClassLoader schreiben? Tiefes Verständnis des Klassenladers in Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn