1。プロキシモードの定義
プロキシオブジェクトをオブジェクトに提供すると、プロキシオブジェクトは元のオブジェクトへのアクセスを制御します。つまり、クライアントは元のオブジェクトを直接操作するのではなく、プロキシオブジェクトを介して元のオブジェクトを間接的に操作します。
有名なプロキシパターンの例は、参照カウントです。複雑なオブジェクトの複数のコピーが必要な場合、プロキシパターンをメタモードと組み合わせてメモリの量を減らすことができます。典型的なアプローチは、複雑なオブジェクトと複数のプロキシを作成することです。各プロキシは、元のオブジェクトを参照しています。エージェントに作用する操作は、元のオブジェクトに転送されます。すべてのエージェントが存在しないと、複雑なオブジェクトが削除されます。
プロキシモデルを理解するのは簡単ですが、実際には人生にはプロキシモデルがあります。
鉄道駅で列車のチケットを購入することもできますが、列車のチケット販売事務所でも購入することもできます。ここの列車のチケット販売事務所は、駅でのチケット購入の代理店です。つまり、販売アウトレットでチケット購入リクエストを発行します。販売アウトレットは鉄道駅にリクエストを送信し、鉄道駅は購入に対する成功した応答を販売アウトレットに送り、販売アウトレットは再びあなたに伝えます。
ただし、チケットは販売アウトレットでのみ購入できますが、払い戻しはできませんが、チケットは駅で購入できます。そのため、エージェントがサポートする操作は、委託されたオブジェクトの操作とは異なる場合があります。
プログラムを書くときに遭遇する別の例を挙げましょう。
既存のプロジェクトがある場合(ソースコードがありませんが、それを呼び出すことができます)、int compute(string exp1)を呼び出して接尾辞式の計算を実装できます。このプロジェクトを使用してInfix式の計算を実装する場合は、プロキシクラスを作成して、計算(String exp2)を定義できます。このEXP2パラメーターは、INFIX式です。したがって、既存のプロジェクトのcompute()を呼び出してから、既存のプロジェクトのcompute()を呼び出す前に、infix式を接尾辞式(プリプロセス)に変換する必要があります。もちろん、返品値を受け取り、ファイルの保存(PostProcess)などの他の操作を実行することもできます。このプロセスでは、プロキシモードを使用します。
コンピューターを使用する場合、プロキシモードアプリケーションにも遭遇します。
リモートプロキシ:中国のGFWのため、Facebookにアクセスできません。壁を閲覧することでアクセスできます(プロキシのセットアップ)。アクセスプロセスは次のとおりです。
(1)ユーザーはHTTPリクエストをプロキシに送信します
(2)プロキシはhttpリクエストをWebサーバーに送信します
(3)WebサーバーはプロキシにHTTP応答を送信します
(4)プロキシはHTTP応答をユーザーに送り返します
2。静的プロキシ
いわゆる静的プロキシは、プロキシオブジェクトの一連の操作を完了するために、コンピレーション段階でプロキシクラスが生成されることを意味します。以下は、プロキシパターンの構造クラス図です。
1。プロキシモデルの参加者
プロキシモードには4つの役割があります。
トピックインターフェイス:つまり、プロキシクラスによって実装された行動インターフェイス。
ターゲットオブジェクト:つまり、オブジェクトがプロキシ化されています。
プロキシオブジェクト:実際のトピッククラスをカプセル化するために使用されるプロキシクライアントは、次のことです。プロキシパターンのクラス図構造です。
2。エージェントモデルを実装するためのアイデア
プロキシオブジェクトとターゲットオブジェクトの両方が同じ動作インターフェイスを実装します。
プロキシクラスとターゲットクラスは、インターフェイスロジックを個別に実装します。
プロキシクラスのコンストラクターにターゲットオブジェクトをインスタンス化します。
プロキシクラスのターゲットオブジェクトの行動インターフェイスを呼び出します。
クライアントがターゲットオブジェクトの動作インターフェイスを呼び出したい場合、プロキシクラスを介してのみ動作できます。
3.静的プロキシの例
以下は、静的プロキシを説明する怠zyな読み込み例です。サービスシステムを開始すると、特定のクラスをロードするのに長い時間がかかる場合があります。パフォーマンスを向上させるために、システムを開始するときに、この複雑なクラスを初期化するのではなく、代わりにプロキシクラスを初期化することがよくあります。これにより、リソースを消費するメソッドが分離のためにプロキシを使用して分離し、システムの起動速度を高速化し、ユーザーの待ち時間を短縮できます。
トピックインターフェイスを定義します
パブリックインターフェイス件名{public void sayshello(); public void saysgoodbye();}ターゲットクラスを定義し、トピックインターフェイスを実装します
public class realsubject explence subject {public void sayshello(){system.out.println( "hello world"); } public void saysgoodbye(){system.out.println( "goodbye world"); }}ターゲットオブジェクトをプロキシするためにプロキシクラスを定義します。
パブリッククラスStaticProxy実装件名{private RealSubject RealSubject = null; public staticProxy(){} public void sayshello(){//その時点でロードされます。 } realSubject.sayhello(); } // saygoodbyeメソッドは同じです...}クライアントを定義します
public class client {public static void main(string [] args){staticproxy sp = new staticProxy(); sp.sayhello(); sp.saygoodbye(); }}上記は、静的プロキシの簡単なテスト例です。実用的ではないかもしれません。ただし、そうではありません。プロキシを使用して、ターゲットオブジェクトメソッドを変換することもできます。たとえば、データベース接続プールに一連の接続が作成されます。接続がまれに開かれるようにするために、これらの接続はほとんど閉じられていません。ただし、オープン接続を閉じる習慣は常にあります。このようにして、プロキシモードを使用して接続インターフェイスの閉じるメソッドを再ロキシを使用し、実際に接続#closeメソッドを実行するのではなく、データベース接続プールにリサイクルすることができます。他にも多くの例があり、自分で体験する必要があります。
3。ダイナミックエージェント
動的プロキシとは、実行時にプロキシクラスを動的に生成することを指します。つまり、プロキシクラスのバイトコードが生成され、実行時に現在のプロキシのクラスローダーにロードされます。静的処理クラスと比較して、動的クラスには多くの利点があります。
実際のトピックのために完全に同一のカプセル化クラスを書く必要はありません。トピックインターフェイスに多くのメソッドがある場合、各インターフェイスのプロキシメソッドを作成することも面倒です。インターフェイスが変更された場合、実際のテーマとプロキシクラスを変更する必要がありますが、これはシステムのメンテナンスを助長しません。
いくつかの動的プロキシ生成方法を使用すると、実行時にプロキシクラスの実行ロジックを策定することで、システムの柔軟性が大幅に向上します。
動的プロキシを生成するには多くの方法があります。JDKには動的プロキシ、CGLIB、Javassistなどが付属しています。これらの方法には、独自の利点と欠点があります。この記事では、主にJDKでの動的プロキシおよびソースコード分析の使用について説明します。
JDKでの動的プロキシの使用を説明する例を次に示します。
パブリッククラスDynamicProxyは、InvocationHandlerを実装しています{private realSubject = null; public object invoke(Object Proxy、Method Method、object args){if(realsubject == null){realSubject = new RealSubject(); } method.invoke(realSubject、args); RealSubjectを返します。 }}クライアントコードの例
public class client {public static void main(strings [] args){subject subject =(subject)proxy.newinstance(classLoader.getSystemLoader()、realSubject.class.getInterfaces()、new DynamicProxy(); subject.sayhello(); subject.saygoodbye(); }}上記のコードからわかるように、JDKで動的プロキシを使用する必要があります。 Static Method Proxy.Newinstance(classloader、interfaces []、invokehandler)を使用して、動的プロキシクラスを作成します。 NewInstanceメソッドには、クラスローダーを表す3つのパラメーター、プロキシクラスを実装したいインターフェイスのリスト、およびInvokeHandlerインターフェイスを実装するインスタンスがあります。動的プロキシは、各メソッドの実行プロセスを処理するためのInvokeメソッドに渡しました。
JDKダイナミックプロキシでは、プロキシがインターフェイスである必要がありますが、単純なクラスではできません。 JDKダイナミックプロキシによって生成されたプロキシクラスはプロキシクラスを継承し、プロキシクラスは渡されたすべてのインターフェイスリストを実装します。したがって、タイプはインターフェイスタイプにキャストできます。以下は、プロキシの構造図です。
プロキシはすべて静的な方法であることがわかります。そのため、プロキシクラスがインターフェイスを実装していない場合、プロキシタイプであり、インスタンスメソッドがありません。
もちろん、参加する場合は、特定のインターフェイスを実装していないクラスをプロキシする必要があり、このクラスのメソッドは他のインターフェイスで定義されたメソッドと同じであり、反射を使用して簡単に実装できます。
パブリッククラスDynamicProxyはInvokeHandlerを実装しています{// Private TargetClass TargetClass = nullをプロキシにするクラス; //このクラスの初期化public dynamicProxy(TargetClass TargetClass){this.targetClass =ターゲットクラス; } public object invoke(Object Proxy、Method Method、Object args){//反射を使用して、プロキシメソッドmymethod = targetclass.getClass()。getDeclaredMethod(method.getName()、method.getParametertypes()); mymethod.setAccessible(true); mymethod.invoke(ターゲットクラス、args)を返します。 }}4。JDKダイナミックプロキシソースコード分析(JDK7)
上記の例を見た後、動的プロキシの使用方法を知っています。ただし、プロキシクラスの作成方法、Invokeメソッドなどと呼ばれるものについてはまだ霧がかかります。次の分析
1.プロキシオブジェクトはどのように作成されますか?
まず、proxy.newinstanceメソッドのソースコードをご覧ください。
public staticオブジェクトNewProxyInstance(classloaderローダー、クラス<? final securitymanager sm = system.getSecurityManager(); if(sm!= null){checkproxyaccess(reflection.getCallerClass()、Loader、intfs); } //プロキシクラスの生成<? // ... OK最初の後半を見てみましょう}ソースコードから、プロキシクラスの生成はgetProxyclass0メソッドに依存していることがわかります。次に、getProxyclass0ソースコードを見てみましょう。
Private Static Class <? } //ここで注意して、Proxyclasscache.get(Loader、Interfaces)を返すために、次の説明を詳細に説明します。 }
proxyclasscache.getの説明は次のとおりです。インターフェイスリストを実装するプロキシクラスが既に存在する場合は、キャッシュから直接取得します。それが存在しない場合、ProxyclassFactoryによって生成されます。
proxyclasscache.getのソースコードを見る前に、Proxyclasscacheを簡単に理解しましょう。
private static final weakcache <classloader、class <?> []、class <?>> proxyclasscache = new weakcache <>(new keyFactory()、new ProxyclassFactory());
ProxyclasscacheはWeakCacheタイプのキャッシュです。そのコンストラクターには2つのパラメーターがあります。そのうちの1つは、プロキシクラスを生成するために使用されるプロキシクラスファクトリーです。以下は、proxyclasscache.getのソースコードです。
最終クラスのweakcache <k、p、v> {... public v get(k key、p parameter){}}ここでkはキーを表し、pはパラメーターを表し、vは値を表します
public v get(k key、p parameter){// java7 nullobject判断方法、パラメーターが空の場合、指定されたメッセージの例外がスローされます。空でない場合は、返します。 objects.requirenonnull(パラメーター); // expungestaleentries()をキャッシュするために一般的に使用される弱い参照を保持する弱体ハッシュマップのデータ構造をクリーンします。 //キューオブジェクトからcachekeyを取得cachekey = cachekey.valueof(key、refqueue); //サプライヤーを怠zyな読み込みで埋めます。同時は、スレッドセーフマップconcurrentmap <object、supplier <v >> valuesmap = map.get(cachekey); if(valuesmap == null){concurrentmap <object、supplier <v >> oldvaluesmap = map.putifabsent(cachekey、valuesmap = new concurrenthashmap <>()); if(oldvaluesmap!= null){valuesmap = oldvaluesmap; }} // Subkeyを作成し、ValuesMap ObjectからSubkeyによって保存されている可能性のあるサプライヤー<V>を取得します。サプライヤー<v> supply = valuesmap.get(subkey);工場工場= null; while(true){if(supplier!= null){//サプライヤーから値を取得します。この値は、工場またはキャッシュの実現かもしれません。 //次の3つの文は、InvokeHandlerを実装し、必要な情報を含むクラスを返すコアコードです。 v value = supplier.get(); if(value!= null){return値; }} //キャッシュのサプライヤーはありません//またはnullを返したサプライヤー(クリアされたcachevalue //またはcachevalueのインストールに成功しなかった工場になる可能性があります)//次のプロセスは、サプライヤーの入力プロセスです(ファクトリー== null){//ファクトリーを作成します}(ループの機能は、InvokeHandlerを実装するクラスを継続的に取得することです。このクラスは、キャッシュから取得するか、ProxyFactoryClassから生成できます。
Factoryは、サプライヤー<v>インターフェイスを実装する内部クラスです。このクラスはGETメソッドをオーバーライドし、型ProxyFactoryClassのインスタンスメソッドがGETメソッドで呼び出されます。この方法は、プロキシクラスを作成する本当の方法です。 ProxyFactoryClassのソースコードを見てみましょう#適用方法:
public class <?> apply(classloader roader、class <? for(class <? try {//各interfaceclass = class.forname(intf.getName()、false、loader)について情報をロードします。 } catch(classNotFoundException e){} //独自のクラスロードでロードされたクラスが渡されたクラスに等しくない場合、(interfaceclass!= intf){show new Illegalargumentexception(intf + "がクラスローダーから表示されない場合」 } //受信がインターフェイスタイプでない場合if(!interfaceclass.isinterface()){throw new IllegalargumentException(interfaceclass.getName() + "はインターフェイスではありません"); } //インターフェイスが繰り返されるかどうかを確認します(interfacesset.put(interfaceclass、boolean.true)!= null){新しいIllegalargumentException( "repeated interface:" + interfaceclass.getName()); }} string proxypkg = null; //プロキシクラスを定義するパッケージ /*非公開プロキシインターフェイスのパッケージを記録して、プロキシクラスが同じパッケージで定義されるようにします。 *すべての非公開プロキシインターフェイスが同じパッケージにあることを確認します。 *///この段落は、渡されたインターフェイスに公開されていないインターフェイスがあるかどうかによって異なります。もしそうなら、これらのインターフェイスはすべて1つのパッケージで定義する必要があります。それ以外の場合は、(クラス<?> intf:interfaces)の例外をスローします{int flags = intf.getModifiers(); if(!modifier.ispublic(flags)){string name = intf.getname(); int n = name.lastindexof( '。');文字列pkg =((n == -1)? "":name.substring(0、n + 1)); if(proxypkg == null){proxypkg = pkg; } else if(!pkg.equals(proxypkg)){throw new IllegalargumentException( "異なるパッケージからの非パブリックインターフェイス"); }}}} if(proxypkg == null){//非パブリックプロキシインターフェイスがない場合は、com.sun.proxyパッケージproxypkg = rferxutil.proxy_package + "。"。 "; } / * *生成するプロキシクラスの名前を選択します。 */ long num = nextuniquenumber.getandincrement(); //ランダムプロキシクラスのクラス名を生成、$ proxy + num string proxyname = proxypkg + proxyclassnameprefix + num; /**プロキシクラスのクラスファイルを生成し、バイトストリームを返します*/ byte [] proxycygenerator.generepproxyclass(proxyname、interfaces); try {return defineclass0(loader、proxyname、proxyclassfile、0、proxyclassfile.length); } catch(classformaterror e){//新しいIllegalargumentException(e.tostring()); }}}前述のProxyFactoryClass#Applyは、実際には不正確なプロキシクラスを実際に生成する方法です。ここでソースコードを読んだ後、Proxygenerator#GenerateProxyclassがプロキシクラスを真に生成する方法であることがわかります。 Javaクラスのバイトコード構成に従って、対応するクラスファイルを生成します(他の記事Java Bytecode Learning Notesを参照)。 Proxygenerator#GenerateProxyclassの特定のソースコードは次のとおりです。
private byte [] generateclassfile(){ / * *ステップ1:すべてのメソッドのproxymethodオブジェクトを組み立てて、プロキシディスパッチコードを生成します。 */ // AddProxyMethodメソッドは、すべてのメソッドをリストに追加し、対応するクラスに対応することです//オブジェクトに対応する3つのメソッド、ToStringおよび等しいAddProxyTymethod(HashCodeMethod、Object.Class); addproxymethod(equalsmethod、object.class); AddProxyMethod(toStringMethod、object.class); //インターフェイスリスト内のインターフェイスを(int i = 0; i <interfaces.length; i ++){method [] methods = interfaces [i] .getmethods(); for(int j = 0; j <methods.length; j ++){addproxymethod(methods [j]、interfaces [i]); }} / * *同じ署名を持つプロキシメソッドセットの各セットについて、 *メソッドの戻りタイプが互換性があることを確認します。 */ for(list <proxymethod> signmethods:proxymethods.values()){checkreturntypes(sigmethods); } / * *ステップ2:生成しているクラス内のすべてのフィールドとメソッドのFieldInfoおよびMethodInfo構造体を組み立てます。 *///メソッドにコンストラクターメソッドを追加します。これは、InvocationHandlerインターフェイスを持つコンストラクターであるコンストラクターが1つだけです。ただし、まだ処理されていません。最初に追加され、ループを待ちます。クラスファイルのコンストラクターの名前の説明は<init> try {methods.add(generateconstructor()); for(list <proxymethod> signmethods:proxymethods.values()){for(proxymethod pm:signmethods){//各プロキシメソッドにメソッドタイプ属性を追加します。番号10はクラスファイルの識別子です。つまり、これらの属性はfields.add(pm.methodfieldname、 "ljava/lang/resprem/method;"、acc_private | acc_static))です。 //各プロキシメソッドをプロキシクラスメソッドメソッドに追加します。 }} //静的初期化ブロックを追加し、各属性を初期化します。ここでは、静的コードブロックはクラスコンストラクターとも呼ばれます。実際には<clinit>という名前のメソッドなので、メソッドリストmethods.add(generatestaticInitializer())に追加します。 } catch(ioException e){new internalError( "予期しないI/O例外"); } //メソッドと属性の数は、以前のインターフェイスの数を含む65535を超えることはできません。 //これは、クラスファイルでは、これらの数値が4ビットヘキサデシマルで表されるため、最大値は16 -1のパワーに対する2であるためです。 } if(fields.size()> 65535){新しいIllegalargumentException( "フィールド制限を超えた"); } //次のステップは、マジック番号、クラス名、定数プール、その他のバイトコードを含むクラスファイルを作成することです。詳細は説明しません。必要な場合は、JVM仮想マシンバイトコードの関連知識を参照できます。 cp.getclass(dottoslash(classname)); cp.getclass(superclassname); for(int i = 0; i <interfaces.length; i ++){cp.getClass(dottoslash(interfaces [i] .getname())); } cp.setreadonly(); bytearrayoutputStream bout = new bytearrayoutputStream(); dataoutputStream dout = new DataOutputStream(Bout); try {// u4 Magic; dout.writeint(0xcafebabe); // u2 minor_version; dout.writeshort(classfile_minor_version); // u2 major_version; dout.writeshort(classfile_major_version); cp.write(dout); //(定数プールを書き込み)// u2アクセス_flags; dout.writeshort(acc_public | acc_final | acc_super); // u2 this_class; dout.writeshort(cp.getclass(dottoslash(className))); // u2 super_class; dout.writeshort(cp.getclass(superclassname)); // u2 interfaces_count; dout.writeshort(interfaces.length); // u2インターフェイス[interfaces_count]; for(int i = 0; i <interfaces.length; i ++){dout.writeshort(cp.getclass(dottoslash(interfaces [i] .getname())); } // u2 fields_count; dout.writeshort(fields.size()); // field_info fields [fields_count]; for(fieldinfo f:fields){f.write(dout); } // u2 methods_count; dout.writeshort(methods.size()); // method_info method [method_count]; for(methodinfo m:method){m.write(dout); } // u2 attributes_count; dout.writeshort(0); //(プロキシクラスのクラスファイル属性はありません)} catch(ioException e){throw new internalError( "予期しないI/O例外"); } return bout.tobytearray(); }コールのレイヤーの後、プロキシクラスが最終的に生成されます。
2。Invokeを呼んだのは誰ですか?
JDKをシミュレートして、クラス名がtestProxygenでプロキシクラスを生成します。
public class testgeneratorProxy {public static void main(string [] args)throws ioexception {byte [] classfile = proxygenerator.generepproxyclass( "testproxygen"、subject.class.getInterfaces(); file file = new file( "/users/yadoao/desktop/testproxygen.class"); fileoutputStream fos = new fileoutputStream(file); fos.write(classfile); fos.flush(); fos.close(); }}JD-GUIでクラスファイルを逆コンパイルすると、結果は次のとおりです。
com.su.dynamicproxy.isubject; import java.lang.refllect.invocationhandler; import java.lang.reflect.method; import java.lang.reflect.proxy; import java.lang.lang.refllect.undeclaredlowableableableableexception; private final class extaindsedsedsectecymentsectectecymentecymentsecteproxedigen extectexecen M3;プライベート静的メソッドM1;プライベート静的メソッドM0;プライベート静的メソッドM4;プライベート静的メソッドM2; public testproxygen(invocationhandler paraminvocationhandler)スロー{super(paraminvocationhandler); } public final void sayshello()throws {try {this.h.invoke(this、m3、null);戻る; } catch(error | runtimeexception localerror){localerrorをスロー; } catch(Throwable localThrowable){新しい非宣言されていないthrowableException(localthrowable); }} public final boolean equals(object paramobject)throws {try {return((boolean)this.h.invoke(this、m1、new object [] {paramobject}))。booleanvalue(); } catch(error | runtimeexception localerror){localerrorをスロー; } catch(Throwable localThrowable){新しい非宣言されていないthrowableException(localthrowable); }} public final int hashcode()throws {try {return((integer)this.h.invoke(this、m0、null))。intvalue(); } catch(error | runtimeexception localerror){localerrorをスロー; } catch(Throwable localThrowable){新しい非宣言されていないthrowableException(localthrowable); }} public final void saysgoodbye()throws {try {this.h.invoke(this、m4、null);戻る; } catch(error | runtimeexception localerror){localerrorをスロー; } catch(Throwable localThrowable){新しい非宣言されていないthrowableException(localthrowable); }} public final String toString()throws {try {return(string)this.h.invoke(this、m2、null); } catch(error | runtimeexception localerror){localerrorをスロー; } catch(Throwable localThrowable){新しい非宣言されていないthrowableException(localthrowable); }} static {try {m3 = class.forname( "com.su.dynamicproxy.isubject")。getMethod( "sayhello"、new class [0]); m1 = class.forname( "java.lang.object")。getMethod( "equals"、new class [] {class.forname( "java.lang.object")}); m0 = class.forname( "java.lang.object")。getMethod( "hashcode"、new class [0]); m4 = class.forname( "com.su.dynamicproxy.isubject")。getMethod( "saygoodbye"、new class [0]); m2 = class.forname( "java.lang.object")。getMethod( "toString"、new class [0]);戻る; } catch(nosuchmethodexception localnosuchmethodexception){新しいnosuchmethoderror(localnosuchmethodexception.getmessage()); } catch(classNotFoundException localClassNotFoundException){新しいnoclassdeffounderror(localclassnotfoundexception.getMessage()); }}}最初に、生成されたプロキシクラスのコンストラクターが、InvokeHandlerインターフェイスをパラメーターとして実装するクラスで渡され、親クラスプロキシのコンストラクターと呼ばれることに気付きました。
いくつかの静的初期化ブロックに再び気づきました。ここでの静的な初期化ブロックは、プロキシインターフェイスリストとハッシュコード、トストリング、およびメソッドに等しいものを初期化することです。
最後に、これらのメソッドの呼び出しプロセスがあり、それらはすべてInvokeメソッドへのコールバックです。
これは、このプロキシパターンの分析で終わります。