パート 1。この記事は読む必要がありますか?
Java クラス ローダーは Java システムの動作に不可欠ですが、無視されることがよくあります。 Java クラス ローダーは、実行時にクラスを検索してロードします。カスタム クラス ローダーは、クラスのロード方法を完全に変更し、Java 仮想マシンを好みに合わせてカスタマイズできます。この記事では、Java クラス ローダーを簡単に紹介し、その後、カスタム クラス ローダーの構築例を通してそれを説明します。このクラス ローダーは、クラスをロードする前にコードを自動的にコンパイルします。クラスローダーが実際に何をするのか、そして独自のクラスローダーを作成する方法を学びます。 Java の基本的な知識、コマンドライン Java プログラムの作成、コンパイル、実行方法、および Java クラス ファイルの基本概念を知っていれば、この記事の内容を理解できます。この記事を読むと、次のことができるようになります。
* Java仮想マシンの機能を拡張
* カスタムクラスローダーを作成する
* カスタム クラス ローダーをアプリケーションに統合する方法
* Java 2 と互換性があるようにクラスローダーを変更します。
パート 2. はじめに クラスローダーとは何ですか?
Java と他の言語の違いは、Java は Java 仮想マシン (JVM) 上で実行されることです。これは、コンパイルされたコードが、特定のマシンで実行される形式ではなく、プラットフォームに依存しない形式で保存されることを意味します。この形式には、従来の実行可能コード形式とは多くの重要な違いがあります。具体的には、C プログラムや C++ プログラムとは異なり、Java プログラムは独立した実行可能ファイルではなく、多数の個別のクラス ファイルで構成され、各クラス ファイルは Java クラスに対応します。 さらに、これらのクラス ファイルはすぐにはメモリにロードされませんが、プログラムが必要とするときにロードされます。 クラス ローダーは、Java 仮想マシンでクラスをメモリにロードするために使用されるツールです。さらに、Java クラス ローダーも Java で実装されています。この方法により、Java 仮想マシンについて深く理解していなくても、独自のクラス ローダーを簡単に作成できます。
なぜクラスローダーを作成するのでしょうか?
Java Virtual Gold にはすでにクラスローダーがあるのですが、他のクラスローダーを自分で作成する必要がありますか? 良い質問です。デフォルトのクラスローダーは、ローカルシステムからクラスをロードする方法のみを知っています。プログラムが完全にネイティブでコンパイルされている場合、通常はデフォルトのクラス ローダーが適切に機能します。しかし、Java の最も興味深い点の 1 つは、ローカルだけでなくネットワークからクラスをロードするのがいかに簡単であるかということです。
たとえば、ブラウザはカスタム クラス ローダーを通じてクラスをロードできます。 クラスをロードする方法もたくさんあります。 Java の最も興味深い点の 1 つは、単にローカルまたはネットワークからだけでなく、Java をカスタマイズできることです。
* 信頼できないコードを実行する前にデジタル署名を自動的に検証します
* ユーザーが提供したパスワードに基づいてコードを復号化します
* ユーザーのニーズに応じてクラスを動的に作成します。JDK (Java Software Development Kit) アプレットビューア (小規模アプリケーション ブラウザ) などを使用している場合は、カスタム クラス ローダーの例をバイトコードの形式でアプリケーションに簡単に統合できます。
Java 埋め込みブラウザの場合は、すでにカスタム クラス ローダーを使用しています。 Sun が最初に Java 言語をリリースしたとき、最も興奮したことの 1 つは、Java がリモート Web サイトからダウンロードしたコードをどのように実行するかを観察することでした。リモートサイトからHTTP経由で実行
P 接続によって送信されるバイトコードは少し奇妙に見えます。 Java にはカスタム クラス ローダーをインストールする機能があるため、これが機能します。アプレット ブラウザにはクラス ローダーが含まれています。このクラス ローダーはローカルで Java クラスを検索するのではなく、リモート サーバーにアクセスし、HTTP 経由で元のバイトコード ファイルをロードし、それを Java 仮想マシン内の Java クラスに変換します。もちろん、クラス ローダーは他にも多くのことを行います。安全でない Java クラスをブロックし、異なるページ上の異なるアプレットが相互に干渉しないようにします。 Luke Gorrie が作成したパッケージである Echidna は、複数の Java アプリケーションを Java 仮想マシンで安全に実行できるようにするオープン Java ソフトウェア パッケージです。カスタム クラス ローダーを使用して各アプリケーションにクラス ファイルのコピーを提供することで、アプリケーション間の干渉を防ぎます。
クラス ローダーの例 クラス ローダーがどのように機能するか、および独自のクラス ローダーを定義する方法がわかったので、CompilingClassLoader (CCL) という名前のカスタム クラス ローダーを作成します。 CCL がコンパイル作業を行ってくれるので、自分で手動でコンパイルする必要はありません。 これは基本的に、ランタイム環境に組み込まれる「make」プログラムを持つことと同じです。
注: 次のステップに進む前に、いくつかの関連概念を理解する必要があります。
このシステムは、JDK バージョン 1.2 (Java 2 プラットフォームと呼ばれるもの) で大幅に改善されました。この記事は JDK 1.0 および 1.1 で書かれていますが、それ以降のバージョンでもすべて機能します。 Java2 では ClassLoader も改良されました。
詳しい紹介は第5部で行います。
パート 3. ClassLoader の構造の概要 クラス ローダーの基本的な目的は、Java クラスのリクエストに対応することです。 Java 仮想マシンがクラスを必要とする場合、Java 仮想マシンはクラス ローダーにクラス名を与え、クラス ローダーは対応するクラス インスタンスを返そうとします。カスタム クラス ローダーは、さまざまな段階で対応するメソッドをオーバーライドすることで作成できます。次に、クラスローダーの主なメソッドのいくつかについて学びます。これらのメソッドが何を行うのか、またクラス ファイルをロードするときにどのように機能するのかを理解できます。また、カスタム クラス ローダーを作成するときにどのようなコードを記述する必要があるかもわかります。次のパートでは、この知識とカスタム CompilingCl を活用します。
assLoader は連携して動作します。
メソッド
ClassLoader.loadClass() は ClassLoader のエントリ ポイントです。メソッドのシグネチャは次のとおりです。
クラスloadClass(文字列名、ブール値解決);
パラメータ名は、Foo や java.lang.Object など、Java 仮想マシンに必要なクラスの完全名 (パッケージ名を含む) を指定します。
solve パラメーターは、クラスを解決する必要があるかどうかを指定します。クラスの解決は、実行の準備が完全に整っていると理解できます。通常、解析は必要ありません。 Java 仮想マシンがこのクラスが存在するかどうかだけを知りたい場合、またはその親クラスを知りたい場合は、解析はまったく必要ありません。 Java 1.1 およびその以前のバージョンでは、クラス ローダーをカスタマイズする場合、サブクラスでオーバーライドする必要があるメソッドは、loadClass メソッドだけです。
(ClassLoader は Java1.2 で変更され、メソッド findClass() が提供されました)。
メソッド定義クラス
defineClass は ClassLoader の非常に謎めいたメソッドです。このメソッドは、バイト配列からクラス インスタンスを構築します。データを含むこの生のバイト配列は、ファイル システムまたはネットワークから取得される場合があります。 defineClass は、Java 仮想マシンの複雑さ、謎、プラットフォームへの依存性を示しています。バイトコードを解釈してランタイム データ構造に変換し、有効性をチェックします。ただし、心配しないでください。これを行う必要はありません。実際には、まったくオーバーライドすることはできません。
これは、メソッドがキーワード Final によって変更されているためです。
メソッドfindSystemClass
findSystemClass メソッドは、ローカル システムからファイルをロードします。ローカル システム上でクラス ファイルを検索し、見つかった場合は呼び出します。
defineClass は、元のバイト配列をクラス オブジェクトに変換します。これは、Java アプリケーションの実行時に Java 仮想マシンがクラスをロードするためのデフォルトのメカニズムです。カスタム クラス ローダーの場合、ロードに失敗した後でのみ findSystemClass を使用する必要があります。 理由は簡単です。クラス ローダーはクラス ロードの特定の手順を実行する責任を負いますが、すべてのクラスを実行するわけではありません。例えば、
クラス ローダーがリモート サイトからいくつかのクラスをロードしたとしても、ローカル システムからロードする必要がある基本クラスがまだたくさんあります。
これらのクラスは私たちにとっては関係ないので、Java 仮想マシンにデフォルトの方法、つまりローカル システムからクラスをロードさせます。これが findSystemClass の動作です。全体のプロセスは大まかに次のとおりです。
* Java 仮想マシンは、クラスをロードするためにカスタム クラス ローダーを要求します。
※リモートサイトにロードが必要なクラスがあるか確認します。
* 存在する場合は、このクラスを取得します。
* そうでない場合は、このクラスが基本クラス ライブラリにあると考え、findSystemClass を呼び出してファイル システムからロードします。
ほとんどのカスタム クラス ローダーでは、リモートから検索する時間を節約するために、最初に findSystemClass を呼び出す必要があります。
実際、次のセクションで説明するように、コードが自動的にコンパイルされたことが確実な場合にのみ、Java 仮想マシンはローカル ファイル システムからクラスをロードできます。
メソッド
前述したように、クラス レコードは、部分ロード (解析なし) と完全ロード (解析を含む) に分類できます。カスタム クラス ローダーを作成するときは、resolveClass を呼び出す必要がある場合があります。
メソッドfindLoadedClass
findLoadedClass はキャッシュを実装します。クラスをロードするために loadClass が必要な場合、最初にこのメソッドを呼び出してクラスがロードされているかどうかを確認し、すでにロードされているクラスが再ロードされるのを防ぐことができます。このメソッドは最初に呼び出す必要があります。これらのメソッドがどのように構成されているかを見てみましょう。
loadClass の実装例では、次の手順を実行します。 (クラス ファイルを取得するための特定のテクノロジは指定しません。ネットワークから、圧縮パッケージから、または動的にコンパイルされたものである可能性があります。いずれの場合も、取得するのは元のバイトコード ファイルです)
* findLoadedClass を呼び出して、このクラスがロードされているかどうかを確認します。
* ロードされていない場合は、何らかの方法で元のバイト配列を取得します。
※配列を取得できたらdefineClassを呼び出してクラスオブジェクトに変換します。
※元のバイト配列が取得できない場合は、findSystemClassを呼び出してローカルファイルシステムから記録できるか確認してください。
* パラメータresolveがtrueの場合、resolveClassを呼び出してクラスオブジェクトを解決します。
※クラスが見つからない場合はClassNotFoundExceptionをスローします。
* それ以外の場合は、このクラスを返します。
クラス ローダーの実用的な知識をより包括的に理解したので、カスタム クラス ローダーを作成できます。次のセクションでは、CCL について説明します。
パート 4.ClassLoader のコンパイル
CCL は、クラス ローダーの機能を示します。CCL の目的は、コードを自動的にコンパイルおよび更新できるようにすることです。仕組みは次のとおりです。
※クラスのリクエストがあった場合、まずディスクのカレントディレクトリおよびサブディレクトリにクラスファイルが存在するか確認してください。
※クラスファイルがなく、ソースコードファイルがある場合は、Javaコンパイラを呼び出してコンパイルし、クラスファイルを生成します。
※クラスファイルが既に存在する場合は、クラスファイルがソースコードファイルよりも古いかどうかを確認してください。クラス ファイルがソース コード ファイルよりも古い場合は、Java コンパイラを呼び出してクラス ファイルを再生成します。
※コンパイルに失敗した場合、またはその他の理由でソースファイルからクラスファイルを生成できない場合は、例外 ClassNotFou をスローします。
ndException。
* このクラスをまだ取得していない場合は、他のクラス ライブラリに存在する可能性があります。findSystemClass を呼び出して、それが見つかるかどうかを確認します。
※見つからない場合はClassNotFoundExceptionをスローします。
* それ以外の場合は、このクラスを返します。
Java コンパイルはどのように実装されますか?
先に進む前に、Java のコンパイル プロセスを理解する必要があります。通常、Java コンパイラは、指定されたクラスのみをコンパイルします。指定されたクラスで必要な場合は、他の関連クラスもコンパイルします。 CCL は、アプリケーションでコンパイルする必要があるクラスを 1 つずつコンパイルします。ただし、一般的に言えば、コンパイラが最初のクラスをコンパイルした後、
CCL は、他の必要な関連クラスが実際にコンパイルされていることを検出します。なぜ? Java コンパイラは、私たちが行ったのと同様のルールを使用します。クラスが存在しない場合、またはソース ファイルが更新された場合、クラスはコンパイルされます。 Java コンパイラは基本的に CCL よりも一歩進んでおり、ほとんどの作業は Java コンパイラによって実行されます。 CCL がこれらのクラスをコンパイルしているようです。
ほとんどの場合、メイン関数クラスでコンパイラを呼び出していることがわかります。それだけです。単純な呼び出しで十分です。 ただし、これらのクラスが最初に出現したときにコンパイルされないという特殊なケースがあります。 Class.forName メソッドを使用して名前に基づいてクラスをロードする場合、Java コンパイラはそのクラスが必要かどうかを認識しません。この場合、
CCL がコンパイラを再度呼び出してクラスをコンパイルしていることがわかります。セクション 6 のコードは、このプロセスを示しています。
CompilationClassLoader の使用
CCL を使用するには、プログラムを直接実行することはできません。次のような特別な方法で実行する必要があります。
% java Foo arg1 arg2
次のように実行します。
% java CCLRun Foo arg1 arg2
CCLRun は、CompilingClassLoader を作成し、それを使用してメイン関数クラスをロードする特別なスタブ プログラムです。これにより、プログラム全体が CompilingClassLoader によって確実にロードされます。 CCLRun は Ja を利用します
va リフレクション API は、main 関数クラスの main 関数を呼び出し、この関数にパラメータを渡します。詳細については、パート 6 のソース コードを参照してください。
例を実行して、プロセス全体がどのように機能するかを示してみましょう。
メイン プログラムは Foo というクラスで、Bar クラスのインスタンスを作成します。この Bar インスタンスは、パッケージ baz 内に存在するクラス Baz のインスタンスを作成します。これは、CCL がサブパッケージからクラスをロードする方法を示しています。 Bar はクラス名に基づいてクラス Boo もロードします
、これもCCLによって行われます。すべてのクラスがロードされ、実行する準備ができています。このプログラムを実行するには、第 6 章のソース コードを使用します。 CCLRun と CompilingClassLoader をコンパイルします。他のクラス (Foo、Bar、Baz、a) をコンパイルしないように注意してください。
nd Boo)、そうでない場合、CCL は機能しません。
% java CCLRun Foo arg1 arg2
CCL: Foo.java をコンパイルしています...
フー! 引数1 引数2
バー! 引数1 引数2
引数1 引数2
CCL: Boo.java をコンパイルしています...
ブー!
コンパイラが Foo.java に対して初めて呼び出され、Bar と baz.Baz も一緒にコンパイルされることに注意してください。そしてブーのように
チャネルをロードする必要がある場合、CCL はコンパイラを再度呼び出してコンパイルします。
パート 5. Java 2 におけるクラス ローダーの改善の概要 Java 1.2 以降のバージョンでは、クラス ローダーが大幅に改善されました。古いコードはまだ機能しますが、新しいシステムのおかげで実装が簡単になりました。この新しいモデルはプロキシ委任モデルです。これは、クラス ローダーがクラスを見つけられない場合、その親クラス ローダーにクラスを見つけるように要求することを意味します。システム クラス ローダーは、すべてのクラス ローダーの祖先です。システム クラス ローダーは、デフォルトで、つまりローカル ファイル システムからクラスをロードします。通常、loadClass メソッドをオーバーライドすると、クラスをロードするためにいくつかの方法が試行されます。多くのクラス ローダーを作成すると、この複雑なメソッドにいくつかの変更を繰り返すだけであることがわかります。 Java 1.2 のloadClassのデフォルト実装には、クラスを検索する最も一般的な方法が含まれており、findClassメソッドとloadClassをオーバーライドしてfindClassメソッドを適切に呼び出すことができます。この利点は、loadClass をオーバーライドする必要がなく、findClass をオーバーライドするだけで済むため、作業負荷が軽減されることです。
新しいメソッド: findClass
このメソッドは、loadClass のデフォルト実装によって呼び出されます。 findClass の目標は、すべてのクラス ローダー固有のコードを含めることです。
コードを繰り返す必要はありません (指定されたメソッドが失敗した場合にシステム クラス ローダーを呼び出すなど)。
新しいメソッド: getSystemClassLoader
findClass メソッドとloadClass メソッドをオーバーライドするかどうかに関係なく、getSystemClassLoader メソッドは (findSystemClass を介した間接的なアクセスではなく) システム クラス ローダーに直接アクセスできます。
新しいメソッド: getParent
リクエストを親クラスローダーに委任するために、このメソッドを通じてこのクラスローダーの親クラスローダーを取得できます。カスタム クラス ローダーの特定のメソッドがクラスを見つけられない場合、リクエストを親クラス ローダーに委任できます。クラス ローダーの親クラス ローダーには、クラス ローダーを作成するコードが含まれています。
パート 6. ソースコード
ClassLoader.java のコンパイル
以下は、CompilingClassLoader.java ファイルの内容です。
java.io.* をインポートします。
/*
CompilingClassLoader は Java ソース ファイルを動的にコンパイルします。 .class ファイルが存在するかどうか、および .class ファイルがソース ファイルより古いかどうかがチェックされます。
*/
パブリック クラス CompilingClassLoader extends ClassLoader
{
// ファイル名を指定し、ファイルの内容全体をディスクから読み取り、バイト配列を返します。
private byte[] getBytes( String filename ) throws IOException {
// ファイルサイズを取得します。
ファイル file = 新しいファイル( ファイル名 );
長い長さ = file.length();
// ファイルの内容を保存するのに十分な配列を作成します。
バイト raw[] = 新しいバイト [(int)len];
//ファイルを開く
FileInputStream fin = new FileInputStream( file );
// コンテンツをすべて読み込みます。読み取れない場合はエラーが発生します。
int r = fin.read(raw);
if (r != len)
throw new IOException( "すべてを読み取ることができません、 "+r+" != "+len );
// ファイルを閉じることを忘れないでください。
fin.close();
// この配列を返します。
生のまま返す。
}
// 指定された Java ソース ファイルをコンパイルするプロセスを生成し、コンパイルが成功した場合は true を返し、そうでない場合は true を返します。
// falseを返します。
プライベートブールコンパイル(String javaFile)はIOExceptionをスローします{
// 現在の進行状況を表示
System.out.println( "CCL: "+javaFile+" をコンパイル中..." );
//コンパイラを起動する
プロセス p = Runtime.getRuntime().exec( "javac "+javaFile );
// コンパイルが終了するまで待ちます
試す {
p.waitFor();
catch( 中断例外 ie ) { System.out.println( ie );
// 戻りコードをチェックして、コンパイル エラーがあるかどうかを確認します。
int ret = p.exitValue();
//コンパイルが成功したかどうかを返します。
戻り値==0;
}
// クラス ローダーのコア コード - クラスをロードすると、必要に応じてソース ファイルが自動的にコンパイルされます。
public ClassloadClass(文字列名、ブール値解決)
ClassNotFoundException をスローします {
//私たちの目的はクラスオブジェクトを取得することです。
クラスクラス = null;
// まず、このクラスが処理されたかどうかを確認します。
クラス = findLoadedClass( 名前 );
//System.out.println( "findLoadedClass: "+clas );
// クラス名からパス名を取得します。例: java.lang.Object => java/lang/Object
String fileStub = name.replace( ''''.'''', ''''/'''' );
// ソース ファイルとクラス ファイルを指すオブジェクトを構築します。
文字列 javaFilename = fileStub+".java";
文字列クラスファイル名 = fileStub+".class";
ファイル javaFile = 新しいファイル( javaFilename );
ファイル classFile = new File( classFilename );
//System.out.println( "j "+javaFile.lastModified()+" c "
//+classFile.lastModified() );
// まず、コンパイルが必要かどうかを判断します。ソースファイルは存在するがクラスファイルが存在しない、または両方が存在するがソースファイルが存在しない場合
// 新しいもので、コンパイルする必要があることを示します。
if (javaFile.exists() &&(!classFile.exists() ||
javaFile.lastModified() > classFile.lastModified())) {
試す {
// コンパイルします。コンパイルが失敗した場合は、失敗の理由を宣言する必要があります (古いクラスを使用するだけでは十分ではありません)。
if (!compile( javaFilename ) || !classFile.exists()) {
throw new ClassNotFoundException( "コンパイルに失敗しました: "+javaFilename );
}
} catch(IOException ie) {
// コンパイル中に IO エラーが発生する可能性があります。
新しい ClassNotFoundException(ie.toString()) をスローします。
}
}
// 正しくコンパイルされているか、コンパイルの必要がないことを確認してから、生のバイトのロードを開始します。
試す {
// バイトを読み取ります。
byte raw[] = getBytes( classFilename );
//クラスオブジェクトに変換
clas =defineClass(name, raw, 0, raw.length );
} catch(IOException ie) {
// これは失敗を意味するものではありません。おそらく、扱っているクラスは java.lang.Object などのローカル クラス ライブラリにあると考えられます。
}
//System.out.println( "defineClass: "+clas );
//おそらくクラス ライブラリ内にあり、デフォルトの方法でロードされます。
if (class==null) {
clas = findSystemClass( 名前 );
}
//System.out.println( "findSystemClass: "+clas );
// パラメータresolveがtrueの場合、必要に応じてクラスを解釈します。
if (&& clas != null を解決)
解決クラス(クラス);
// クラスが取得されていない場合は、何らかの問題が発生しています。
if (クラス == null)
新しい ClassNotFoundException( name ) をスローします。
// それ以外の場合は、このクラス オブジェクトを返します。
クラスを返します。
}
}
CCRun.java
CCRun.java ファイルは次のとおりです。
インポート java.lang.reflect.*;
/*
CCLRun は、CompilingClassLoader を通じてクラスをロードし、プログラムを実行します。
*/
パブリック クラス CCLRun
{
static public void main( String args[] ) throws Exception {
//最初のパラメータは、ユーザーが実行したいメイン関数クラスを指定します。
文字列 progClass = args[0];
//次のパラメータは、このメイン関数クラスに渡されるパラメータです。
String progArgs[] = new String[args.length-1];
System.arraycopy( args, 1, progArgs, 0, progArgs.length );
// CompilingClassLoader を作成します
CompilingClassLoader ccl = new CompilingClassLoader();
// CCL を介してメイン関数クラスをロードします。
クラス clas = ccl.loadClass( progClass );
// リフレクションを使用してメイン関数を呼び出し、パラメータを渡します。
// main 関数のパラメータの型を表すクラス オブジェクトを生成します。
クラス mainArgType[] = { (new String[0]).getClass() };
// クラス内の標準 main 関数を検索します。
メソッド main = clas.getMethod( "main", mainArgType );
// パラメータ リストを作成します - この場合は文字列の配列です。
オブジェクト argsArray[] = { progArgs };
// main関数を呼び出します。
main.invoke( null, argsArray );
}
}
Foo.java
以下はファイル Foo.java の内容です。
パブリッククラス Foo
{
static public void main( String args[] ) throws Exception {
System.out.println( "foo! "+args[0]+" "+args[1] );
new Bar( args[0], args[1] );
}
}
バー.java
以下は、Bar.java ファイルの内容です。
インポートバズ*;
パブリッククラスバー
{
public Bar( String a, String b ) {
System.out.println( "bar! "+a+" "+b );
新しい Baz( a, b );
試す {
クラス booClass = Class.forName( "Boo" );
オブジェクト boo = booClass.newInstance();
} catch(例外 e) {
e.printStackTrace();
}
}
}
baz/Baz.java
以下は、ファイル baz/Baz.java の内容です。
パッケージバズ。
パブリッククラスバズ
{
public Baz( String a, String b ) {
System.out.println( "baz! "+a+" "+b );
}
}
Boo.java
ファイル Boo.java の内容は次のとおりです。
パブリッククラスBoo
{
publicBoo() {
System.out.println( "ブー!" );
}
}
パート 7. 概要 概要 この記事を読んだ後、カスタム クラス ローダーを作成すると、Java 仮想マシンの内部を深く掘り下げることができることがわかりました。クラス ファイルは任意のリソースからロードしたり、動的に生成したりできるため、これらの関数を拡張することで興味のある多くのことを行うことができ、いくつかの強力な関数を完成させることもできます。
ClassLoader に関するその他のトピック この記事の冒頭で述べたように、カスタム クラス ローダーは Java 組み込みブラウザおよびアプレット ブラウザで重要な役割を果たします。