序文
「ダイナミックエージェント」は実際には設計モードのプロキシモードに由来し、プロキシモードはプロキシオブジェクトを使用してユーザー要求を完了し、ユーザーの実際のオブジェクトへのアクセスをブロックします。
最も簡単な例を挙げると、「FQ」外国のWebサイトにアクセスする必要があります。すべての外国のIPSがあるわけではないため、ブロックされていない外国人ホストにリクエストデータグラムを送信し、外国人ホストを設定して応答メッセージを受け取った後、国内ホストに転送することでリクエストを転送できます。
この例では、外国人ホストはプロキシオブジェクトであり、壁に落とされるホストは実際のオブジェクトです。実際のオブジェクトに直接アクセスすることはできませんが、プロキシを介して間接的にアクセスできます。
プロキシモードの利点の1つは、すべての外部リクエストがプロキシオブジェクトを通過することであり、プロキシオブジェクトが実際のオブジェクトに真にアクセスできるかどうかを制御する権利があることです。違法な要求がある場合、プロキシオブジェクトは実際のオブジェクトに実際にトラブルなしで完全に拒否できます。
プロキシモードの最も典型的なアプリケーションの1つは、Springフレームワークです。 SpringのAOPは、アスペクト指向のプログラミングを使用して、関連するログの例外やその他の情報から実際のビジネスロジックを分離します。ビジネスロジックを要求するたびに、プロキシオブジェクトに対応します。必要な許可チェックとログ印刷の実行に加えて、このプロキシオブジェクトは実際のビジネスロジック処理ブロックです。
静的プロキシ
プロキシモデルには、「静的プロキシ」と「動的プロキシ」という2つの主要な実装者がいます。これら2つの本質的な違いは、以前のプロキシクラスではプログラマーによる手動エンコードが必要であり、後者のプロキシクラスが自動的に生成されることです。したがって、これが「静的プロキシ」の概念をほとんど聞いていない理由です。もちろん、自然に「動的プロキシ」を理解する方が簡単です。
明確にする必要があることの1つは、プロキシオブジェクトが実際のオブジェクトのすべてのメソッドをプロキシにプロキシであること、つまり、プロキシオブジェクトがコールの実際のオブジェクトと少なくとも同じメソッド名を提供する必要があることです。したがって、プロキシオブジェクトは、親クラスのメソッドを含む実際のオブジェクトが所有するすべてのメソッドを定義する必要があります。
簡単な静的プロキシの例を見てみましょう。
問題を説明するために、Iserviceインターフェイスを定義し、実際のクラスをインターフェイスを継承して実装して、実際のクラスに2つの方法があるようにします。
では、実際のオブジェクトのプロキシを完了するために、プロキシクラスをどのように定義する必要がありますか?
一般的に言えば、プロキシクラスの本質は、実際のクラスのすべてのメソッドを定義し、メソッド内に他の操作を追加し、最後に実際のクラスのメソッドを呼び出すことです。
プロキシクラスは、実際のクラスのすべてのメソッド、つまり実際のクラスのメソッドとまったく同じメソッドをプロキシする必要があり、実際のクラスのメソッドはこれらのメソッド内で間接的に呼び出されます。
したがって、一般的に言えば、プロキシクラスは、実際のクラスのすべての親メソッド署名、つまり最初にすべての親メソッドを近似するために、実際のクラスのすべてのインターフェイスと親クラスを直接継承することを選択します。
次に、プロキシは実際のクラスの親の方法ではありません。ここの例では、Doserviceメソッドは実際のクラス独自の方法です。また、プロキシクラスは、同じメソッドシグネチャーを使用して、それをプロキシを使用するメソッドを定義する必要があります。
このようにして、プロキシクラスが完了したとしても、実際のクラスのすべての方法は、将来プロキシクラスを通じてプロキシできます。このような:
public static void main(string [] args){realclass realclass = new realclass(); Proxyclass Proxyclass = new Proxyclass(RealClass); proxyclass.sayhello(); proxyclass.doservice();}Proxyclassは、プロキシクラスオブジェクトとして、実際のクラスのすべてのメソッドをプロキシでき、これらのメソッドが実行される前に「取るに足らない」情報を印刷できます。
これは基本的にプロキシモデルの基本的な実装アイデアですが、動的プロキシとこの種の静的プロキシの違いは、動的プロキシがメソッド定義を1つずつ必要とせず、仮想マシンがこれらのメソッドを自動的に生成することです。
JDKダイナミックプロキシメカニズム
動的プロキシと静的プロキシを区別するのは、動的プロキシのプロキシクラスが実行時に仮想マシンによって動的に作成され、仮想マシンがアンインストールされたときにクリアされることです。
上記の静的プロキシで使用されているクラスを再利用して、JDKの動的プロキシが特定のクラスのインスタンスのすべての方法をどのように近接することができるかを確認します。
ハンドラー処理クラスを定義します。
メイン関数の動的プロキシAPIは、JDKを呼び出してプロキシクラスインスタンスを生成します。
まだかなりのコードが含まれています。少しずつ分析しましょう。まず、RealClassはインターフェイスISERVICEをプロキシクラスとして実装し、独自のメソッドデザサービスを内部的に定義します。
次に、インターフェイスのInvocationHandlerを継承し、独自に宣言されたInvokeメソッドを実装する処理クラスを定義します。さらに、実際のオブジェクトを保存するためにメンバーフィールドを宣言する必要があります。つまり、プロキシオブジェクトは、基本的には実際のオブジェクトの関連する方法に基づいているためです。
この呼び出し方法の役割とさまざまな正式なパラメーターの重要性に関して、プロキシソースコードを反映するときに詳細な分析を実施します。
最後に、処理クラスを定義し、基本的にJDKに基づいて動的プロキシを実行します。コア法は、プロキシクラスのnewProxyInstanceメソッドです。この方法には3つのパラメーターがあります。 1つはクラスローダー、2つ目はプロキシクラスによって実装されたすべてのインターフェイスのコレクションで、3つ目はカスタマイズされたプロセッサクラスです。
仮想マシンは、実行時に提供するクラスローダーを使用し、指定されたすべてのインターフェイスクラスをメソッド領域にロードし、これらのインターフェイスのメソッドを反映および読み取り、プロセッサクラスを組み合わせてプロキシタイプを生成します。
最後の文は少し抽象的かもしれません。プロセッサクラスと組み合わせてプロキシタイプを生成する方法」?この点で、仮想マシンの起動パラメーターを指定し、プロキシクラスの生成されたクラスファイルを保存します。
-dsun.misc.proxygenerator.savegeneratedfiles = true
サードパーティのツールを介してこのクラスファイルを逆コンパイルしますが、多くのコンテンツがあります。分析を分割します:
まず、このプロキシクラスの名前は非常にランダムです。プログラムで生成される複数のプロキシクラスがある場合、「$ proxy + number」はクラス名です。
次に、このプロキシクラスは、指定したプロキシクラスとインターフェイスIserviceを継承していることがわかります(以前は複数のインターフェイスが指定されていた場合、複数のインターフェイスがここに継承されます)。
次に、このコンストラクターにはInvocationHandler Typeパラメーターが必要であり、コンストラクターの本体は、このInvocationHandlerインスタンスを保存用の親クラスプロキシの対応するフィールドに渡すことがわかります。これは、すべてのプロキシクラスが親クラスとしてプロキシを親クラスとして使用する必要がある理由の1つでもあります。この小さなデザインは、JDKベースのダイナミックプロキシの致命的な不利な点につながることを後で知っています。これは後で導入されるでしょう。
このコンテンツは、プロキシクラスの比較的重要な部分でもあります。仮想マシンがプロキシクラスを静的に初期化するときに実行されます。この大きなコードは、インターフェイス内のすべてのメソッドを反映する機能を完了し、すべての反射方法はメソッドタイプのフィールドに対応して保存されます。
さらに、仮想マシンはオブジェクトの3つの一般的なメソッドを反映しています。つまり、プロキシクラスはオブジェクトから継承された実際のオブジェクトもプロキシになります。
最後の部分では、仮想マシンが静的初期化コードブロックに基づいてプロキシ化されるすべての方法を反映し、それらのプロキシメソッドを生成することです。
これらのメソッドは多くのコードのように見えますが、実際には、親クラスのプロキシからインスタンス化中に保存されているプロセッサクラスを取り出し、Invokeメソッドを呼び出しているコードの1行にすぎません。
メソッドのパラメーターは基本的に同じです。最初のパラメーターは現在のプロキシクラスインスタンスです(過去にこのパラメーターを渡すことは役に立たないことが証明されています)、2番目のパラメーターはメソッドメソッドインスタンス、3番目のパラメーターはメソッドの正式なパラメーターセットです。そうでない場合、それはnullです。
それでは、カスタマイズされたプロセッサクラスを見てみましょう。
すべてのプロキシクラスメソッドは、プロセッサクラスのInvokeメソッドを呼び出し、プロキシクラスの現在の方法で渡します。このInvokeメソッドは、メソッドを正常に呼び出すか、メソッド呼び出しをスキップするか、さらにはメソッドが実際に呼び出される前後に余分なことを行うことを選択できます。
これは、JDKダイナミックプロキシの中心的なアイデアです。通話プロセス全体を簡単に要約しましょう。
まず、プロセッサクラスの定義が不可欠であり、実際のオブジェクト、つまりプロキシクラスインスタンスに関連付けられている必要があります。
次に、外部からプロキシクラスの任意のメソッドを呼び出し、分解されたソースコードから、代わりにプロキシクラスメソッドがプロセッサの呼び出しメソッドを呼び出し、メソッドの署名とメソッドの正式なパラメーターセットを渡すことがわかっています。
最後に、メソッドと呼ばれることができるかどうかは、通常、プロセッサがメソッドボディを呼び出すかどうかによって、実際にメソッドメソッドを呼び出します。
実際、JDKに基づいて実装された動的プロキシには欠陥があり、これらの欠陥は簡単に修正できないため、CGLIBは人気があります。
いくつかの欠陥と欠陥
単一のプロキシメカニズム
上記の例が利用できないことに気付いたかどうかはわかりません。仮想マシンによって生成されたプロキシクラスは、独自のプロセッサクラスインスタンスを公開するためにプロキシクラスを継承します。それはどういう意味ですか?
Javaの単一のルート継承は、プロキシクラスが他のクラスを継承できなくなるため、プロキシクラスの親クラスのメソッドは自然に取得できないことを示しています。つまり、プロキシクラスは実際のクラスの親クラスのメソッドをプロキシできません。
これとは別に、別の詳細があります。あなたがそれに気づいたのだろうか。私はこのように書いた。
ここでのSayhelloメソッドは、インターフェイスIserviceが実装されていますが、DoserviceメソッドはRealClassの独自のメソッドです。ただし、プロキシクラスからこの方法は表示されません。つまり、この方法はプロキシではありません。
したがって、JDKの動的プロキシメカニズムは単一であり、プロキシクラスのインターフェイスコレクションでのみプロキシメソッドのみができます。
友好的な返品価値
newProxyInstanceはプロキシクラス「$ proxy0」のインスタンスを返しますが、オブジェクトタイプとして返され、オブジェクトインスタンスを「$ proxy0」タイプに強制することはできません。
このオブジェクトインスタンスは実際には「$ proxy0」タイプであることはわかっていますが、「$ proxy0」タイプはコンピレーション期間中は存在しません。コンパイラは当然、存在しないタイプに強制することはできません。したがって、一般に、このプロキシクラスによって実装されたインターフェイスの1つであることのみを強制します。
RealClass RC = new RealClass(); MyHanlder Hanlder = new Myhanlder(rc); iservice obj =(iservice)proxy.newproxyinstance(rc.getClass()。getClassLoader()、new class [] {] {iservice.class}、hanlder); obj.sayelo();プログラムの実行出力:
プロキシの始まり...... Hello World ...プロキシエンディング......
その後、質問が再び来ます。プロキシクラスが複数のインターフェイスを実装している場合、どのインターフェイスタイプを強制する必要がありますか?ここで、プロキシクラスがインターフェイスAとBを実装すると仮定すると、最後のインスタンスがAに強制された場合、プロキシクラスによって実装されたインターフェイスBのすべてのメソッドを呼び出すことはできません。
これは直接結果につながります。どのメソッドがインターフェースであるかを知る必要があります。メソッドを呼び出す前に対応するインターフェイスに強制する場合、それは非常に非友好的です。
上記は、JDKに基づいた動的プロキシメカニズムではエレガントではないと思われるものです。もちろん、その利点はこれらの欠点よりも間違いなく大きいです。次の記事では、さまざまなフレームワークで広く使用されているCGLIBダイナミックプロキシライブラリを紹介します。その基礎となる層は、バイトコード操作フレームワークASMに基づいており、JDKの単一プロキシの欠点を完全に解決するために、継承に依存していません。
記事のすべてのコード、画像、ファイルは、私のgithubのクラウドに保存されます。
(https://github.com/singleyam/overview_java)
ローカルでダウンロードすることもできます。
要約します
上記は、この記事のコンテンツ全体です。この記事の内容には、すべての人の研究や仕事に特定の参照値があることを願っています。ご質問がある場合は、メッセージを残してコミュニケーションをとることができます。 wulin.comへのご支援ありがとうございます。