前の記事の分析を通じて、プロキシクラスのプロキシクラスファクトリーを通じてプロキシクラスが生成されることがわかります。この工場クラスは、プロキシゲネレータークラスのGenerateProxyclass()メソッドを呼び出して、プロキシクラスのバイトコードを生成します。 Proxygeneratorクラスは、sun.miscパッケージに保存されます。 OpenJDKソースコードを介してこのクラスを見つけることができます。このクラスの生成されたProxyclass()静的メソッドのコアコンテンツは、generatedClassFile()インスタンスメソッドを呼び出してクラスファイルを生成することです。 GenerateClassFile()メソッド内で行われていることを見てみましょう。
private byte [] generateclassfile(){//最初のステップは、すべてのメソッドをproxymethodオブジェクトに組み立てることです//最初にtoString、ハッシュコード、等などのプロキシメソッドを生成することです。 addproxymethod(equalsmethod、object.class); AddProxyMethod(toStringMethod、object.class); //各インターフェイスの各メソッドを転送し、(int i = 0; i <interfaces.length; i ++){methods [] methods = interfaces [i] .getmethods(); for(int j = 0; j <methods.length; j ++){addproxymethod(methods [j]、interfaces [i]); }} //同じ署名を持つプロキシメソッドの場合、メソッドの返品値が互換性があるかどうかを確認します(リスト<ProxyMethod>署名:proxymethods.values()){checkreturntypes(sigmethods); } //ステップ2、生成されるクラスファイルのすべてのフィールド情報とメソッド情報を組み立ててください{//コンストラクターメソッドmethods.add(generateconstructor()); //(list <proxymethod> signmethods:proxymethods.values())のキャッシュのプロキシメソッドを転送(proxymethod pm:signmethods){//プロキシクラスの静的フィールドを追加します。例:private static method m1; fields.add(new fieldInfo(pm.methodfieldname、 "ljava/lang/resprem/method;"、acc_private | acc_static)); //プロキシクラスMethods.Add(PM.GenerateMethod())のプロキシメソッドを追加}} // staticフィールド初期化methods.add(generatestaticInitializer())を追加する; } catch(ioException e){new internalError( "予期しないI/O例外"); } //検証方法とフィールドコレクションは、65535を超えることはできません。 } if(fields.size()> 65535){新しいIllegalargumentException( "フィールド制限を超えた"); } if(fields.size()> 65535){新しいIllegalargumentException( "フィールド制限を超えた"); } //ステップ3、最終クラスファイルに書き込みます//定数プールcp.getClass(dottoslash(className))にプロキシクラスの完全な名前があることを確認します。 //一定のプールにプロキシクラスの親クラスの完全な資格のある名前があり、親クラス名は「Java/Lang/Reflect/Proxy」CP.GetClass(SuperClassName)です。 //(int i = 0; i <interfaces.length; i ++){cp.getclass(dottoslash(interfaces [i] .getname())) } //ファイルの書き込みを開始する隣に、定数プールを設定してCp.setreadonly()のみを読み取ります。 bytearrayoutputStream bout = new bytearrayoutputStream(); dataoutputStream dout = new DataOutputStream(Bout); {// 1を試してください。マジックナンバーdout.writeint(0xcafebabe)に書き込みます。 // 2。セカンダリバージョン番号dout.writeshort(classfile_minor_version)に書き込みます。 // 3。メインバージョン番号dout.writeshort(classfile_major_version)に書き込みます。 // 4。定数プールcp.write(dout)に書き込みます。 // 5。書き込みアクセス修飾子dout.writeshort(acc_public | acc_final | acc_super); // 6。書き込みクラスインデックスDout.Writeshort(cp.getClass(dottoslash(className)))); // 7。親クラスのインデックスを書き込むと、生成されたプロキシクラスはプロキシdout.writeshort(cp.getclass(superclassname))から継承されます。 // 8。インターフェイスカウント値dout.writeshort(interfaces.length)を書き込む; // 9。 (int i = 0; i <interfaces.length; i ++){dout.writeshort(cp.getclass(dottoslash(interfaces [i] .getname())))for(int i = 0; i <interfaces.length; i ++)の書き込み} // 10。フィールドカウント値dout.writeshort(fields.size())を書き込む; // 11。 (fieldinfo f:fields)のフィールドコレクションを書き込む{f.write(dout); } // 12。書き込みメソッドカウント値dout.writeshort(methods.size()); // 13。 (methodinfo m:method)のメソッドコレクションを書き込み{m.write(dout); } // 14。プロパティカウント値を書き込むと、プロキシクラスファイルには属性がないため、0 dout.writeshort(0)です。 } catch(ioException e){new internalError( "予期しないI/O例外"); } //バイナリアレイに変換して出力return bout.tobytearray();}GenerateClassFile()メソッドは、クラスファイル構造に応じて動的にスプライスされていることがわかります。クラスファイルとは何ですか?ここでは、最初に書くJavaファイルが.javaで終了することを説明します。書いた後、コンパイラを介してコンパイルし、.classファイルを生成します。この.classファイルはクラスファイルです。 Javaプログラムの実行は、クラスファイルのみに依存しており、Javaファイルとは何の関係もありません。このクラスファイルは、クラスの情報を説明します。クラスを使用する必要がある場合、Java仮想マシンはこのクラスのクラスファイルを事前にロードし、初期化と関連する検証を実行します。 Java仮想マシンは、このクラスを使用する前に、これらのタスクを確実に完了することができます。 Java仮想マシンがどのようにロードされるかを気にせずに、安心して使用する必要があります。もちろん、クラスファイルを必ずしもJavaファイルをコンパイルしてコンパイルする必要はありません。テキストエディターを介してクラスファイルを直接書き込むこともできます。ここで、JDKダイナミックプロキシは、プログラムを通じてクラスファイルを動的に生成します。上記のコードに戻って、クラスファイルの生成が主に3つのステップに分割されていることを確認しましょう。
ステップ1:生成するすべてのプロキシメソッドを収集し、それらをproxymethodオブジェクトに包み、マップコレクションに登録します。
ステップ2:クラスファイル用に生成されるすべてのフィールド情報とメソッド情報を収集します。
ステップ3:上記の作業を完了した後、クラスファイルの組み立てを開始します。
クラスの中核部分はそのフィールドと方法であることを知っています。 2番目のステップに焦点を合わせて、プロキシクラスにどのようなフィールドとメソッドが生成するかを確認しましょう。 2番目のステップでは、次の4つのことが順番に行われました。
1.プロキシクラスのパラメーターコンストラクターを生成し、InvocationHandlerインスタンスに参照して渡し、親クラスのパラメーターコンストラクターを呼び出します。
2。プロキシメソッドのマップコレクションを反復し、各プロキシメソッドの対応するメソッドタイプの静的ドメインを生成し、フィールドコレクションに追加します。
3.プロキシメソッドのマップコレクションを反復し、各プロキシメソッドに対応するMethodInfoオブジェクトを生成し、メソッドコレクションに追加します。
4.プロキシクラスの静的初期化方法を生成します。この静的初期化法は、主に各プロキシメソッドの参照を対応する静的フィールドに割り当てます。
上記の分析を通じて、JDKダイナミックプロキシが最終的に次の構造を備えたプロキシクラスを生成することを大まかに知っています。
Public class proxy0はプロキシを拡張しますuserdao {//ステップ1、生成コンストラクター保護proxy0(rikocationhandler H){super(h); } //ステップ2、静的ドメインプライベート静的メソッドM1を生成します。 //ハッシュコードメソッドプライベート静的メソッドM2; //メソッドプライベート静的メソッドM3に等しくなります。 // ToStringメソッドプライベート静的メソッドM4; // ... //ステップ3、プロキシメソッドを生成@Override public int hashcode(){try {return(int)h.invoke(this、m1、null); } catch(throwable e){新しいundeClaredThrowableException(e); }} @Override public boolean equals(object obj){try {object [] args = new object [] {obj}; return(boolean)h.invoke(this、m2、args); } catch(throwable e){新しいundeClaredThrowableException(e); }} @Override public String toString(){try {return(string)h.invoke(this、m3、null); } catch(throwable e){新しいundeClaredThrowableException(e); }} @Override public void save(user user){try {//パラメーター配列を作成します。 H.Invoke(This、M4、Args); } catch(throwable e){新しいundeClaredThrowableException(e); }} //ステップ4、静的初期化メソッドstatic {try {class c1 = class.forname(object.class.getname()); class c2 = class.forname(userdao.class.getName()); M1 = c1.getMethod( "hashcode"、null); m2 = c1.getMethod( "Equals"、new class [] {object.class}); m3 = c1.getMethod( "toString"、null); M4 = c2.getMethod( "Save"、new class [] {user.class}); // ...} catch(例外e){e.printstacktrace(); }}}この時点で、JDKソースコードを階層化した分析と詳細な調査の後、動的に生成されたプロキシクラスの元の外観を復元し、以前の質問のいくつかもよく説明されました。
1.プロキシクラスは、デフォルトでPorxyクラスを継承します。 Javaは単一の継承のみをサポートするため、JDK Dynamic Proxyはインターフェイスのみを実装できます。
2。プロキシメソッドは、Invoke()InvocationHandlerメソッドを呼び出すため、Invoke()InvocationHandlerメソッドを書き換える必要があります。
3。invoke()メソッドを呼び出すと、プロキシインスタンス自体、ターゲットメソッドとターゲットメソッドパラメーターが渡されます。Invoke()メソッドのパラメーターがどのように生じるかを説明します。
プロキシクラスとして新しく構築されたproxy0を使用して再度テストすると、最終結果はJDKを使用して動的に生成されたプロキシクラスと同じであることがわかります。繰り返しになりますが、私たちの分析は信頼性が高く正確です。この時点で、JDKダイナミックプロキシシリーズの記事が終了すると発表されています。このシリーズの分析を通じて、著者は彼の心に長年の疑問を解決しました。私は、JDKダイナミックプロキシに対する読者の理解がさらに一歩していると信じています。ただし、紙の知識は常に浅いです。 JDKダイナミックプロキシテクノロジーをより適切にしたい場合は、読者はこの一連の記事を参照してJDKソースコードを自分で確認するか、著者と学習経験を交換し、著者の不適切な分析を指摘し、一緒に学び、一緒に進歩を遂げることができます。
上記はこの記事のすべての内容です。みんなの学習に役立つことを願っています。誰もがwulin.comをもっとサポートすることを願っています。