ホームページ >Java >&#&チュートリアル >なぜ ClassLoader を書くのでしょうか? Javaのクラスローダーについての深い理解
クラスローダーとは何ですか?すべてのプログラミング言語の中で、Java は Java 仮想マシン上で実行されるという点で独特です。これは、コンパイルされたプログラムが、ターゲット マシンの形式以外の、プラットフォームに依存しない独自の形式でターゲット マシン上で実行されることを意味します。この形式は、多くの点で従来の実行可能プログラムとは大きく異なります。
Java ClassLoader は、Java オペレーティング システムの重要なコンポーネントですが、見落とされがちなコンポーネントです。実行時にクラス ファイルを検索してロードする役割を果たします。カスタム ClassLoader を作成すると、クラス ファイルがシステムにロードされる方法を完全に再定義できます。
このチュートリアルでは、Java の ClassLoader の概要を説明し、カスタム ClassLoader の例を示します。この ClassLoader は、コードをロードする前に自動的にコンパイルされます。 ClassLoader の機能とカスタム ClassLoader の作成方法を学びます。
このチュートリアルでは、読者は、単純なコマンドライン Java プログラムの作成、コンパイル、実行など、Java プログラミングの基本を理解している必要があります。
このチュートリアルを読むと、次の方法がわかります:
JVM の機能を拡張する
カスタム ClassLoader を作成する
ClassLoader を Java アプリケーションに統合する方法を学ぶ
使用する ClassLoader を変更するJava2バージョンに準拠しています
すべてのプログラミング言語の中でも、JavaはJava仮想マシン上で動作するという点で独特です。これは、コンパイルされたプログラムが、ターゲット マシンの形式以外の、プラットフォームに依存しない独自の形式でターゲット マシン上で実行されることを意味します。この形式は、多くの点で従来の実行可能プログラムとは大きく異なります。
Java プログラムと C または C++ プログラムの最大の違いは、Java プログラムが単一の実行可能ファイルではなく、多数の個別のクラス ファイルで構成されており、各クラス ファイルが Java クラスに対応していることです。
それだけでなく、これらのクラス ファイルは一度にメモリにロードされるのではなく、オンデマンドでロードされます。 ClassLoader は、クラスをメモリにロードする JVM の一部です。
また、Java ClassLoader は Java で書かれています。これは、JVM について詳しく知らなくても、独自の ClassLoader を簡単に作成できることを意味します。
JVM に既に ClassLoader がある場合、なぜ別の ClassLoader を記述する必要があるのでしょうか?良い質問ですね。デフォルトの ClassLoader は、ローカル ファイル システムからクラス ファイルをロードする方法しか知りません。一般的なシナリオでは、コードをローカルで記述し、それをローカルでコンパイルする場合は、これで完全に十分です。
ただし、JAVA 言語の最も斬新な機能の 1 つは、クラスをローカル ハード ドライブまたはインターネットの外部から取得できることです。たとえば、ブラウザはカスタム ClassLoader を使用して、Web サイトから実行可能コンテンツを取得します。
クラスファイルを取得する方法は他にもたくさんあります。クラス ファイルをローカルまたはオンラインでロードするだけでなく、クラス ローダーを使用して次のこともできます:
信頼できないコードを実行する前にデジタル署名を自動的に検証する
ユーザー指定のパスワードを使用した透過的な復号コード
カスタム動的クラスを作成するユーザー固有のニーズに基づいて
Java バイトコードを生成するものはすべてアプリケーションに統合できます。
アプレットを使用したことがあるなら、カスタム クラス ローダーを使用したことがあるはずです。
Sun が Java 言語をリリースしたとき、最もエキサイティングなことの 1 つは、このテクノロジがリモート Web サーバーからのコードのタイムリーなロードをどのように実行するかを観察することでした。興味深いのは、リモート Web サーバーから HTTP 接続を介してバイトコードを送信し、それをローカルで実行することです。
カスタム ClassLoader をサポートする Java 言語の機能により、このアイデアが可能になります。アプレットにはカスタム ClassLoader があり、ローカル ファイル システムからクラス ファイルをロードするのではなく、リモート Web サーバーからクラス ファイルを取得し、HTTP 経由で元のバイトコードをロードして、それを JVM 内のクラスに変換します。
ブラウザやアプレットのクラスローダーには、セキュリティ管理、異なるページ上のアプレットが相互に影響を与えないようにするなどの他の機能もあります。
次に、CompilingClassLoader(CCL)
というカスタム クラス ローダーを作成します。CCL は Java コードのコンパイルに役立ちます。これは基本的に、単純な make プログラムを実行中のシステムに直接構築するようなものです。 CompilingClassLoader(CCL)
、CCL会帮我们编译Java代码。它基本上就像是在运行系统中直接构建一个简单的make程序。
ClassLoader的基本目的是为类的请求提供服务。JVM需要一个类,于是它通过类的名字询问ClassLoader来加载这个类。ClassLoader试着返回一个代表该类的对象。
通过覆盖此过程不同阶段对应的方法,可以创建自定义的ClassLoader。
在本文的剩余部分,你会了解到ClassLoader中的一些关键方法。你会了解到每个方法的用途以及它在类加载过程中是如何调用的。你还会了解当你在自定义ClassLoader时需要完成的工作。
loadClass
方法##、ClassLoader.loadClass()
方法是ClassLoader的入口。它的方法标签如下:
Class loadClass(String name, boolean resolve)
name
参数代表JVM需要的类的名称,比如Foo
或是java.lang.Object
loadClass
method##、🎜🎜ClassLoader.loadClass()
メソッドは ClassLoader への入り口です。そのメソッド タグは次のとおりです: 🎜% java Foo arg1 arg2🎜
name
パラメータは、Foo
や java.lang.Object など、JVM に必要なクラスの名前を表します。 コード>。 🎜<p><code>resolve
パラメータは、クラスを解決する必要があるかどうかを示します。クラスの解析は、クラスの実行を完全に準備することとして理解できます。解析は必要ありません。 JVM がクラスの存在を確認するか、その親クラスを見つけるだけでよい場合は、解析する必要はありません。 resolve
参数说明类是否需要被解析。可以把类的解析理解为完全的准备好执行类。解析并不是必要的。如果JVM只需要确定该类存在或是找出其父类,则无需解析。
在java1.1版本以前,自定义ClassLoader只需要重写loadClass
方法。
defineClass
方法是整个ClassLoader的核心。此方法将原始字节数组转化为一个Class
对象。原始字节数组包含从本地或是远程得到的数据。
defineClass
负责处理JVM的许多复杂,神秘而且依赖于具体实现的部分。它将字节码解析为运行时的数据结构,检查其有效性等。不用担心,这些你不用自己实现。事实上,你根本没法重写它,因为该方法为final方法。
findSystemClass方法
findSysetmClass
方法从本地文件系统中加载文件。它在本地文件系统中查找类文件,如果存在,使用defineClass
将其从原始字节转化为类对象。这是JVM在运行Java应用程序时加载类的默认机制。
对于自定义的ClassLoader,我们只会在尝试了别的方法来加载类内容之后,才调用findSystemClass
方法。道理很简单:自定义的ClassLoader包含加载特殊类的一些步骤,但是并非所有的类都是特殊类。比如,即便ClassLoader需要从远程网站上获取一些类,还是有许多类需要从本地的Java库中加载。这些类并不是我们关注的重点,因此我们需要JVM用默认的方式来获取。
整个流程如下:
请求自定义ClassLoader加载一个类
查看远程服务器是否有该类
如果有,则获取并返回
如果没有,我们假设该类是位于本地的一个基础类,并调用findSystemClass
从文件系统中加载出来。
在大多数自定义的ClassLoader中,你需要先滴啊用findSystemClass
来减少对远程网站的访问,因为大多数Java类都位于本地的类库中。但是,在下一节中你会看到,在自动将应用代码编译之前,我们不希望JVM从本地文件系统加载类。
resolveClass
方法如前文所说,类的加载是可以部分进行(不进行解析)或是彻底进行的(进行解析)。当我们实现自己的loadClass
方法时,我们或许需要调用resolveClass
方法,这取决于loadClass
中的resolve
参数的值。
findLoadedClass
方法findLoadedClass
方法充当一个缓存调用机制:当loadClass
方法被调用时,他会调用这个方法来查看类是否已经被加载过了,省去了重复加载。这个方法应当最先被调用。
我们的例子中loadClass
执行以下几步(这里我们不会特别关注到底采用了什么神奇的方法来获取类文件。它可以是从本地,网络或者是压缩文件中获得的,总之我们获得了原始类文件的字节码):
调用findLoadedClass
查看是否已经加载过该类
如果没有,则使用神奇的魔法来获得原始字节码
如果获得字节码,调用defineClass
将其转化为Class
对象
如果没有获得字节码,则调用findSystemClass
,看是否能从本地文件系统获得类
如果resolve
值为true,则调用resolveClass
来解析Class
对象
如果还是没有找到类,则抛出ClassNotFoundException
否则,将类返回给调用者
CompilingClassLoader
CCL
的作用是确保代码已经被编译,并且是最新版本的。
以下是该类的描述:
当需要一个类时,查看该类是否在磁盘上,在当前的目录或是相应的子目录下
如果该类不存在,但是其源码存在,在调用Java编译器来生成类文件
如果类文件存在,查看他是否比源码的版本旧,如果低于源码的版本,则重新生成类文件
如果编译失败,或者其他的原因导致无法从源码中生成类文件,抛出ClassNotFoundException
如果还是没有类文件,那么它或许在其他的一些库中,调用findSystemClass
看是否有用
如果还是找不到类,抛出ClassNotFoundException
loadClass
メソッドをオーバーライドするだけで済みました。 defineClass
メソッドは、ClassLoader 全体の中核です。このメソッドは、元のバイト配列を Class
オブジェクトに変換します。生のバイト配列には、ローカルまたはリモートのソースから取得したデータが含まれます。
defineClass
は、JVM の多くの複雑で不可解な、実装に依存する部分を処理する責任があります。バイトコードをランタイムデータ構造に解析し、その有効性などをチェックします。心配しないでください。これを自分で実装する必要はありません。実際、このメソッドは最終的なものであるため、オーバーライドすることはできません。 🎜findSystemClass メソッド
findSysetmClass
メソッドは、ローカル ファイル システムからファイルを読み込みます。ローカル ファイル システムでクラス ファイルを検索し、存在する場合は、defineClass
を使用して、生のバイトからクラス オブジェクトに変換します。これは、Java アプリケーションの実行時にクラスをロードするための JVM のデフォルトのメカニズムです。 🎜🎜カスタム ClassLoader の場合、他のメソッドを試してクラスのコンテンツをロードした後でのみ、findSystemClass
メソッドを呼び出します。理由は簡単です。カスタム ClassLoader には特殊クラスをロードするためのいくつかのステップが含まれていますが、すべてのクラスが特殊クラスであるわけではありません。たとえば、ClassLoader がリモート Web サイトからいくつかのクラスを取得する必要がある場合でも、ローカル Java ライブラリからロードする必要があるクラスがまだたくさんあります。これらのクラスは私たちの焦点ではないため、JVM がデフォルトの方法でそれらを取得する必要があります。 🎜🎜プロセス全体は次のとおりです: 🎜findSystemClass
を呼び出してファイル システムからロードします。 🎜🎜findSystemClass
を使用する必要があります。ただし、次のセクションで説明するように、アプリケーション コードを自動的にコンパイルする前に、JVM がローカル ファイル システムからクラスをロードすることは望ましくありません。 🎜resolveClass
メソッドloadClass
メソッドを実装する場合、loadClass
の resolve
に応じて、resolveClass
メソッドを呼び出す必要がある場合があります。 > >パラメータの値。 🎜findLoadedClass
メソッドfindLoadedClass
メソッドはキャッシュ呼び出しメカニズムとして機能します。loadClass
メソッドが呼び出されると、 call このメソッドは、クラスがロードされたかどうかを確認するために使用され、繰り返しロードする必要がなくなります。このメソッドを最初に呼び出す必要があります。 🎜loadClass
が次の手順を実行します (ここでは、クラス ファイルを取得するためにどのような魔法のメソッドが使用されるかについては特別な注意を払いません。 from ローカル、ネットワーク、または圧縮ファイルから取得。つまり、元のクラス ファイルのバイトコードを取得しました): 🎜findLoadedClass
クラスが既にロードされているかどうかを確認します🎜🎜🎜🎜そうでない場合は、マジックを使用して元のバイトコードを取得します🎜🎜🎜🎜バイトコードを取得した場合は、defineClass
を呼び出して、それを Class に変換します
オブジェクト 🎜🎜🎜🎜 バイトコードが取得できない場合は、findSystemClass
を呼び出して、ローカル ファイル システムからクラスを取得できるかどうかを確認します 🎜🎜🎜🎜resolve code> 値が true の場合、<code>resolveClass
を呼び出して Class
オブジェクトを解決します🎜🎜🎜🎜それでもクラスが見つからない場合は、ClassNotFoundException
をスローします🎜 🎜🎜🎜それ以外の場合は、クラスを呼び出し元に返します🎜🎜
CompilingClassLoader
CCL
は、コードが確実にロードされたことを確認するために使用されます。コンパイル済みの最新バージョンです。 ClassNotFoundException
をスローします🎜🎜🎜🎜まだクラス ファイルがない場合は、他のライブラリにある可能性があります。findSystemClass
を呼び出して、それが役立つかどうかを確認してください🎜🎜🎜🎜クラスがまだ見つからない場合は、ClassNotFoundException をスローします
🎜🎜🎜🎜それ以外の場合は、クラスを返します🎜在深入研究之前,我们应该回过头来看一下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类加载器详解
相关视频:
以上がなぜ ClassLoader を書くのでしょうか? Javaのクラスローダーについての深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。