Contents
はじめに
動的プロキシAPI
シリアル化
例
はじめに
動的プロキシクラスは、クラスのインスタンス上のインターフェイスのいずれかを介してメソッド呼び出しがコード化され、uniforminterfaceを介して別のオブジェクトにディスパッチされるように、実行時に指定されたlistofインターフェイスを実装するクラスです。 したがって、動的プロキシクラスを使用して、コンパイル時ツールなどのプロキシクラスの事前生成を必要とせずに、インターフェイスのリストのatypeセーフ動的プロキシクラスのインスタンスでのメソッド呼び出しは、インスタンスのinvocationhandler内の単一のメソッドにディスパッチされ、呼び出されたmethodthatを識別するjava.lang.reflect.Method
動的プロキシクラスは、インターフェイスApiを提示するオブジェクトに対してinvocationのタイプセーフな反射ディスパッチを提供する必要があるアプリケーシ たとえば、動的プロキシクラスを使用して、任意のイベントリスナインターフェイスを複数実装するオブジェクトを作成することができます。interfaces thatextendjava.util.EventListener
は、そのようなすべてのイベントをファイルにバインドするなど、さまざまなタイプのさまざまなイベントを均一な方法で処理します。
動的プロキシクラスAPI
動的プロキシクラス(以下では単にproxyclassと呼びます)は、クラスの作成時に実行時に指定されたインターフェイスのリストを実装するクラスです。
プロキシインターフェイスは、プロキシクラスによって実装されたインターフェイスです。
プロキシインスタンスは、proxyclassのインスタンスです。
プロキシクラスの作成
プロキシクラスとそのインスタンスは、javaクラスの静的メソッドを使用して作成されます。ラング反省しろプロキシ。
Proxy.getProxyClass
メソッドは、classloaderとインターフェイスの配列を指定したプロキシクラスのjava.lang.Class
オブジェクトを返します。 プロキシクラスは、指定されたクラスローダーで定義され、サポートされているすべてのインターフェイスを実装します。 同じpermutation ofinterfacesのプロキシクラスがすでにクラスローダーで定義されている場合は、既存のプロキシクラスが返されます。
に渡すことができるパラメータにはいくつかの制限がありますProxy.getProxyClass
:
-
interfaces
配列内のすべてのClass
オブジェクトは、クラスまたはプリミティブ型ではなく、インターフェイスを表す必要があります。 -
interfaces
配列内の二つの要素は、同一のClass
オブジェクトを参照することはできません。 - すべてのインタフェース-タイプは、指定されたクラス-ローダーを介して名前で表示される必要があります。 つまり、クラスローダー
cl
およびすべてのインターフェイスi
の場合、followingexpressionはtrueでなければなりません:Class.forName(i.getName(), false, cl) == i
- そうしないと、どのパッケージで定義されているかにかかわらず、プロキシクラスがすべてのインターフェイスを実装することはできません。
- 同じシグネチャを持つ指定されたインターフェイスのメンバーメソッドのセットについて:
- いずれかのメソッドの戻り値の型がプリミティブ型orvoidの場合、すべてのメソッドは同じ戻り値の型を持たなければなりません。
- それ以外の場合は、メソッドのいずれかが、残りのメソッドのすべての戻り値の型に割り当て可能な戻り値の型を持っている必要があります。
- 結果のプロキシクラスは、仮想マシンによってクラスに課される制限を超えてはなりません。 その場合、
interfaces
配列のサイズは65535を超えてはなりません。
これらの制限のいずれかに違反した場合、Proxy.getProxyClass
はIllegalArgumentException
をスローします。 配列引数interfaces
またはその要素のいずれかがnull
である場合、NullPointerException
はbethrownされます。
指定されたプロキシインターフェイスの順序は重要であることに注意してください:インターフェイスの同じ組み合わせを持つプロキシクラスの二つの要求は、異なる順序で二つの異なるプロキシクラスになります。 プロキシクラスは、複数のproxyinterfacesが同じ名前とparametersignatureを持つメソッドを共有する場合にdeterministicmethod呼び出しエンコーディングを提供するために、プロキシインターフェイスの理論によって区別されます。この推論については、以下の”複数のプロキシインターフェイスで重複するメソッド”の項で詳しく説明されています。
新しいプロキシクラスを生成する必要がないように、Proxy.getProxyClass
が同じクラスローダーとインターフェイスのリストで呼び出されるたびに、dynamicプロキシクラスAPIの実実装では、クラスローダー、インターフェイス、およびプロキシクラスを、クラスローダーとそのすべてのクラスが適切な場合にgarbagecollectedされるのを防ぐように参照しないように注意する必要があります。
プロキシクラスプロパティ
プロキシクラスには次のプロパティがあります:
- プロキシクラスはpublic、final、abstractではありません。
- プロキシクラスの非修飾名は指定されていません。 ただし、文字列
"$Proxy"
で始まるspaceofクラス名は、プロキシクラス用に予約されます。 - プロキシクラスは
java.lang.reflect.Proxy
を拡張します。 - プロキシクラスは、作成時に指定されたインターフェイスを同じ順序で実装します。
- プロキシクラスが非パブリックインターフェイスを実装している場合、そのインターフェイスと同じパッケージで定義されます。 それ以外の場合は、プロキシクラスのパッケージも指定されていません。 Packagesealingは、実行時に特定のパッケージでプロキシクラスがsuccessfullydefinedされるのを防ぐことはできませんし、同じクラスローダーで既に定義されているwillclassesも、特定の署名者と同
- プロキシクラスは作成時に指定されたすべてのインターフェイスを実装するため、
getInterfaces
をClass
オブジェクトで呼び出すと、同じインターフェイスのリストを含む配列が返されます(作成時に指定された順序で)、getMethods
をClass
オブジェクトで呼び出すと、それらのインターフェイス内のすべてのメソッドを含むMethod
オブジェクトの配列が返され、getMethod
を呼び出すと、プロキシインターフェイス内のメソッドが返されます。….. -
Proxy.isProxyClass
メソッドは、プロキシクラス(Proxy.getProxyClass
によって返されるクラスまたはProxy.newProxyInstance
returnedbyオブジェクトのクラス)が渡された場合はtrueを返し、それ以外の場合はfalseを返します。 このメソッドの信頼性は、itを使用してセキュリティ上の決定を行う能力にとって重要であるため、その実装は問題のクラスがjava.lang.reflect.Proxy
を拡張しているかどうかをテストするだけではありません。 - proxyclassの
java.security.ProtectionDomain
は、プロキシクラスのコードが信頼されたシステムコードによって生成されるため、java.lang.Object
などのbootstrapclassローダーによってロードされるシステムクラスと同じです。 この保護ドメインは、通常、java.security.AllPermission
が付与されます。
プロキシインスタンスの作成
各プロキシクラスには、インターフェイスInvocationHandler
の実装であるoneargumentを取るパブリックコンストラクタがあります。
各プロキシインスタンスには、コンストラクタに渡された呼び出しハンドラオブジェクトが関連付けられています。 Reflection APIを使用してパブリックコンストラクターにアクセスするのではなく、Proxy.newProxyInstance
メソッドを呼び出すことによってproxyinstanceを作成することもできます。Proxy.newProxyInstance
はProxy.getProxyClass
と同じ理由でIllegalArgumentException
をスローします。
プロキシインスタンスプロパティ
プロキシインスタンスには次のプロパティがあります:
- プロキシインスタンス
proxy
と、そのプロキシクラスFoo
によって実装されたインターフェイスのいずれかが指定された場合、次の式はtrue:proxy instanceof Foo
を返し、次のキャスト操作は成功します(throwingaではなく)。
ClassCastException
):(Foo) proxy
- static
Proxy.getInvocationHandler
メソッドは、proxy instancepassedに関連付けられた呼び出しハンドラを引数として返します。Proxy.getInvocationHandler
に渡されたオブジェクトがプロキシインスタンスでない場合、IllegalArgumentException
がスローされます。 - プロキシインスタンス上のインターフェイスメソッド呼び出しは、以下で説明するようにコード化され、呼び出しハンドラの
invoke
メソッドにディスパップロキシインスタンス自体は、
Object
型の最初のargumentofinvoke
として渡されます。invoke
に渡される2番目の引数は、プロキシインスタンスで呼び出されるインターフェイスメソッドに対応するjava.lang.reflect.Method
インスタンスになります。Method
オブジェクトの宣言クラスは、themethodが宣言されたインターフェイスになり、プロキシクラスがメソッドを継承するproxyinterfaceのスーパーインターフェイスになります。invoke
に渡される3番目の引数は、プロキシインスタンスのメソッド呼び出しで渡される引数の値を含むオブジェクトの配列になります。 Primitivetypesの引数は、java.lang.Integer
やjava.lang.Boolean
などの適切なprimitivewrapperクラスのインスタンスでラップされます。invoke
メソッドの実装は、thisarrayの内容を自由に変更できます。invoke
メソッドによって返される値は、プロキシインスタンス上のメソッド呼び出しの戻り値になります。 インタフェース-メソッドの宣言された戻り値がprimitivetypeである場合、invoke
によって返される値は、対応するプリミティブ-ラッパークラスのインスタンスでなければinvoke
によって返される値がnull
で、インターフェイスメソッドの戻り値の型がプリミティブの場合、プロキシインスタンスのmethodinvocationによってNullPointerException
がスローされます。 それ以外の場合、invoke
によって返される値が上記のようにmethod’sdeclared戻り値の型と互換性がない場合、proxyinstanceによってClassCastException
がスローされます。invoke
メソッドによって例外がスローされた場合、プロキシインスタンスのメソッド呼び出しによってもスローされます。例外の型は、インターフェイスメソッドのシグネチャで宣言された例外型または非チェック例外型java.lang.RuntimeException
またはjava.lang.Error
に割り当てることができなければなりません。 Interfacemethodのthrows
句で宣言されているexceptiontypesのいずれにも代入できないinvoke
によってチェック例外がスローされた場合、UndeclaredThrowableException
はプロキシインスタンスのメソッド呼び出しによUndeclaredThrowableException
は、invoke
メソッドによってスローされた例外を使用して構築されます。 - プロキシインスタンス上で
java.lang.Object
で宣言されたhashCode
、equals
、またはtoString
メソッドの呼び出しは、上記のようにインターフェイスメソッドの呼び出しがエンコードされ、ディスパッチされるのと同じ方法で、呼び出しハンドラのinvoke
メソッドにディスパッチされます。invoke
に渡されたMethod
オブジェクトの宣言クラスはjava.lang.Object
になります。java.lang.Object
から継承されたproxyinstanceの他のパブリックメソッドは、プロキシクラスによってオーバーライドされないため、これらのメソッドの呼び出しはjava.lang.Object
のインスタン
MultipleProxyインターフェイスで複製されたメソッド
プロキシクラスの複数のインターフェイスに同じ名前とパラメータシグネチャを持つメソッドが含まれている場合、proxyclassのインターフェイスの順序が重要になります。 このようなduplicatemethodがプロキシインスタンスで呼び出されると、呼び出しハンドラに渡されるMethod
オブジェクトは、プロキシのメソッドが呼び出されたイン この制限は、生成されたプロキシクラスの対応するメソッド実装が、呼び出されたインターフェイスを判断できないために存在します。 したがって、プロキシインスタンスで重複メソッドが呼び出されると、プロキシクラスのインターフェイスのリストにあるメソッドを含む最上位のイ
プロキシインターフェイスにjava.lang.Object
のhashCode
、equals
、またはtoString
メソッドと同じ名前とparameterシグネチャを持つメソッドが含まれている場合、そのようなメソッドがaproxyイ つまり、java.lang.Object
のpublicでfinalでないメソッドは、呼び出しハンドラに渡すオブジェクトのMethod
を決定するために、すべてのproxyinterfacesの前に論理的に配置されます。
また、重複メソッドがaninvocationハンドラにディスパッチされると、invoke
メソッドは、メソッドのthrows
句のexceptiontypesのいずれかに割り当てることができるthrowchecked例外型のみを呼び出 invoke
メソッドが、呼び出すことができるプロキシインターフェイスのいずれかのメソッドによって宣言された例外型に割り当てられないチェック例外をスローした場合、anuncheckedUndeclaredThrowableException
はプロキシインスタンスの呼び出しによってスローされます。 この制限は、invoke
メソッドに渡されたMethod
objectpassedでgetExceptionTypes
を呼び出すことによって返されるすべての例外型が、invoke
メソッドによって必ずthrownsuccessfullyできるわけではないことを意
シリアル化
java.lang.reflect.Proxy
はjava.io.Serializable
を実装しているため、このセクションで説明するようにプロキシインスタンスをシリアル化できます。 ただし、proxy instancecontainsにjava.io.Serializable
に割り当てられない呼び出しハンドラがある場合、そのようなインスタンスがjava.io.ObjectOutputStream
に書き込まれるとjava.io.NotSerializableException
がスローされます。 Proxyclassesの場合、java.io.Externalizable
を実装することは、java.io.Serializable
を実装することと同じ効果があります。Externalizable
インターフェイスのwriteExternal
およびreadExternal
メソッドは、itsserializationプロセスの一部としてaproxyインスタン すべてのClass
オブジェクトと同様に、プロキシクラスのClass
オブジェクトはalwaysserializableです。
プロキシクラスには直列化可能なフィールドがなく、serialVersionUID
の0L
があります。 つまり、プロキシクラスのClass
オブジェクトがjava.io.ObjectStreamClass
のstaticlookup
メソッドに渡されると、返されるObjectStreamClass
インスタンスは次のプロパティを持ちます:
- その
getSerialVersionUID
メソッドを呼び出すと、0L
が返されます。 - その
getFields
メソッドを呼び出すと、arrayofの長さゼロが返されます。 - 任意の
String
引数でgetField
メソッドを呼び出すと、null
が返されます。
オブジェクトシリアル化のストリームプロトコルは、ストリーム形式の文法のterminalsymbolであるTC_PROXYCLASSDESC
という名前のtypecodeをサポートしています; その型と値は、java.io.ObjectStreamConstants
インターフェイスの次の定数フィールドによって定義されます:
final static byte TC_PROXYCLASSDESC = (byte)0x7D;
文法には、元のnewClassDescruleの代替展開である次の二つのルールも含まれています。
newClassDesc:TC_PROXYCLASSDESC
newHandle proxyClassDescInfo
proxyClassDescInfo:(int)<count>
p r o xyinterfacename c l a s s a n notationsuperclassdesc
Proxyinterfacename:(utf)
ObjectOutputStream
がプロキシクラスであるクラスのclassdescriptorをシリアル化するとき、Class
オブジェクトをProxy.isProxyClass
メソッドにバイパスすると判断され、上記の規則に従ってTC_CLASSDESC
ではなくTC_PROXYCLASSDESC
型コードを使用します。 ProxyClassDescInfoの拡張では、proxyinterfacename項目のシーケンスは、プロキシクラスによって実装されたすべてのinterfacesの名前であり、getInterfaces
オブジェクトのClass
メソッドを呼び出して返されます。 ClassAnnotation andsuperclassdesc項目は、classdescinfoルールと同じ意味を持ちます。 プロキシクラスの場合、superClassDescisはそのスーパークラスのクラス記述子java.lang.reflect.Proxy
;この記述を含むプロキシインスタンスのクラスProxy
のシリアル化された表現の進化のた
非プロキシクラスの場合、ObjectOutputStream
はitsprotectedannotateClass
メソッドを呼び出して、サブクラスが特定のクラスのストリームにカスタムデータを書き込むことを許可します。 Proxyclassesの場合、annotateClass
ではなく、java.io.ObjectOutputStream
のfollowingmethodがプロキシクラスのClass
オブジェクトで呼び出されます:
protected void annotateProxyClass(Class cl) throws IOException;
ObjectOutputStream
のannotateProxyClass
のデフォルトの実装は何もしません。
ObjectInputStream
が型コードTC_PROXYCLASSDESC
を検出すると、上記のようにフォーマットされたストリームからプロキシクラスのclassdescriptorを逆シリアル化します。 ClassdescriptorのClass
オブジェクトを解決するためにresolveClass
メソッドを呼び出す代わりに、java.io.ObjectInputStream
の次のメソッドが呼び出されます:
protected Class resolveProxyClass(String interfaces) throws IOException, ClassNotFoundException;
proxyclass記述子で逆シリアル化されたインターフェイス名のリストは、interfaces
argumenttoresolveProxyClass
として渡されます。
ObjectInputStream
のresolveProxyClass
の既定の実装は、interfaces
パラメーターで指定されたインターフェイスのClass
オブジェクトのリストを使用してProxy.getProxyClass
を呼び出した結果を返します。 各インターフェイス名i
のClass
objectusedは、
Class.forName(i, false, loader)
を呼び出すことによって再調整された値です。loader
は、実行スタックの最初の非nullクラスローダーです。 これは、resolveClass
メソッドのデフォルト動作によって行われたのと同じクラスローダーの選択です。 これと同じloader
の値は、Proxy.getProxyClass
に渡されるクラスローダーでもあります。Proxy.getProxyClass
がIllegalArgumentException
を投げた場合、resolveClass
はIllegalArgumentException
を含むClassNotFoundException
を投げます。
プロキシクラスには独自の直列化可能なフィールドがないため、プロキシインスタンスのストリーム表現のclassdataは、そのスーパークラスjava.lang.reflect.Proxy
のインスタンスデータ Proxy
には、プロキシインスタンスのinvocationhandlerを含むoneserializableフィールドh
があります。
例
インターフェイスのarbitrarylistを実装するオブジェクトのメソッド呼び出しの前後にメッセージを出力する簡単な例を次に示します:
public interface Foo { Object bar(Object obj) throws BazException;}public class FooImpl implements Foo { Object bar(Object obj) throws BazException { // ... }}public class DebugProxy implements java.lang.reflect.InvocationHandler { private Object obj; public static Object newInstance(Object obj) { return java.lang.reflect.Proxy.newProxyInstance( obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new DebugProxy(obj)); } private DebugProxy(Object obj) { this.obj = obj; } public Object invoke(Object proxy, Method m, Object args) throws Throwable { Object result; try { System.out.println("before method " + m.getName()); result = m.invoke(obj, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } catch (Exception e) { throw new RuntimeException("unexpected invocation exception: " + e.getMessage()); } finally { System.out.println("after method " + m.getName()); } return result; }}
は、Foo
インターフェイスの実装用にDebugProxy
を構築し、そのメソッドのいずれかを呼び出します:
Foo foo = (Foo) DebugProxy.newInstance(new FooImpl()); foo.bar(null);
ここでは、java.lang.Object
から継承されたメソッドの既定のプロキシ動作を提供し、呼び出されたメソッドのインターフェイスに応じて、特定のプロキシメソッド:
import java.lang.reflect.*;public class Delegator implements InvocationHandler { // preloaded Method objects for the methods in java.lang.Object private static Method hashCodeMethod; private static Method equalsMethod; private static Method toStringMethod; static { try { hashCodeMethod = Object.class.getMethod("hashCode", null); equalsMethod = Object.class.getMethod("equals", new Class { Object.class }); toStringMethod = Object.class.getMethod("toString", null); } catch (NoSuchMethodException e) { throw new NoSuchMethodError(e.getMessage()); } } private Class interfaces; private Object delegates; public Delegator(Class interfaces, Object delegates) { this.interfaces = (Class) interfaces.clone(); this.delegates = (Object) delegates.clone(); } public Object invoke(Object proxy, Method m, Object args) throws Throwable { Class declaringClass = m.getDeclaringClass(); if (declaringClass == Object.class) { if (m.equals(hashCodeMethod)) { return proxyHashCode(proxy); } else if (m.equals(equalsMethod)) { return proxyEquals(proxy, args); } else if (m.equals(toStringMethod)) { return proxyToString(proxy); } else { throw new InternalError( "unexpected Object method dispatched: " + m); } } else { for (int i = 0; i < interfaces.length; i++) { if (declaringClass.isAssignableFrom(interfaces)) { try { return m.invoke(delegates, args); } catch (InvocationTargetException e) { throw e.getTargetException(); } } } return invokeNotDelegated(proxy, m, args); } } protected Object invokeNotDelegated(Object proxy, Method m, Object args) throws Throwable { throw new InternalError("unexpected method dispatched: " + m); } protected Integer proxyHashCode(Object proxy) { return new Integer(System.identityHashCode(proxy)); } protected Boolean proxyEquals(Object proxy, Object other) { return (proxy == other ? Boolean.TRUE : Boolean.FALSE); } protected String proxyToString(Object proxy) { return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode()); }}
Delegator
のサブクラスは、invokeNotDelegated
をオーバーライドして、他のオブジェクトに直接委任されないproxymethod呼び出しの動作を実装し、proxyHashCode
、proxyEquals
、およびproxyToString
をオーバーライドして、プロキシがjava.lang.Object
から継承するメソッドのデフォルトの動作をオーバーライドすることができます。
Foo
インターフェイスの実装のためのDelegator
を構築するには:
Class proxyInterfaces = new Class { Foo.class }; Foo foo = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), proxyInterfaces, new Delegator(proxyInterfaces, new Object { new FooImpl() }));
上記のDelegator
classgivenの実装は、最適化されたものよりも例示的なものであることに注意してください; たとえば、hashCode
、equals
、およびtoString
メソッドのMethod
オブジェクトをキャッシュして比較する代わりに、それらのメソッド名のどれもjava.lang.Object
でオーバーロードされていないため、文字列名でそれらを一致させることができます。