動的プロキシクラス

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.getProxyClassIllegalArgumentExceptionをスローします。 配列引数interfacesまたはその要素のいずれかがnullである場合、NullPointerExceptionはbethrownされます。

指定されたプロキシインターフェイスの順序は重要であることに注意してください:インターフェイスの同じ組み合わせを持つプロキシクラスの二つの要求は、異なる順序で二つの異なるプロキシクラスになります。 プロキシクラスは、複数のproxyinterfacesが同じ名前とparametersignatureを持つメソッドを共有する場合にdeterministicmethod呼び出しエンコーディングを提供するために、プロキシインターフェイスの理論によって区別されます。この推論については、以下の”複数のプロキシインターフェイスで重複するメソッド”の項で詳しく説明されています。

新しいプロキシクラスを生成する必要がないように、Proxy.getProxyClassが同じクラスローダーとインターフェイスのリストで呼び出されるたびに、dynamicプロキシクラスAPIの実実装では、クラスローダー、インターフェイス、およびプロキシクラスを、クラスローダーとそのすべてのクラスが適切な場合にgarbagecollectedされるのを防ぐように参照しないように注意する必要があります。

プロキシクラスプロパティ

プロキシクラスには次のプロパティがあります:

  • プロキシクラスはpublic、final、abstractではありません。
  • プロキシクラスの非修飾名は指定されていません。 ただし、文字列"$Proxy"で始まるspaceofクラス名は、プロキシクラス用に予約されます。
  • プロキシクラスはjava.lang.reflect.Proxyを拡張します。
  • プロキシクラスは、作成時に指定されたインターフェイスを同じ順序で実装します。
  • プロキシクラスが非パブリックインターフェイスを実装している場合、そのインターフェイスと同じパッケージで定義されます。 それ以外の場合は、プロキシクラスのパッケージも指定されていません。 Packagesealingは、実行時に特定のパッケージでプロキシクラスがsuccessfullydefinedされるのを防ぐことはできませんし、同じクラスローダーで既に定義されているwillclassesも、特定の署名者と同
  • プロキシクラスは作成時に指定されたすべてのインターフェイスを実装するため、getInterfacesClassオブジェクトで呼び出すと、同じインターフェイスのリストを含む配列が返されます(作成時に指定された順序で)、getMethodsClassオブジェクトで呼び出すと、それらのインターフェイス内のすべてのメソッドを含むMethodオブジェクトの配列が返され、getMethodを呼び出すと、プロキシインターフェイス内のメソッドが返されます。…..
  • Proxy.isProxyClassメソッドは、プロキシクラス(Proxy.getProxyClassによって返されるクラスまたはProxy.newProxyInstancereturnedbyオブジェクトのクラス)が渡された場合は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.newProxyInstanceProxy.getProxyClassと同じ理由でIllegalArgumentExceptionをスローします。

プロキシインスタンスプロパティ

プロキシインスタンスには次のプロパティがあります:

  • プロキシインスタンスproxyと、そのプロキシクラスFooによって実装されたインターフェイスのいずれかが指定された場合、次の式はtrue:
     proxy instanceof Foo

    を返し、次のキャスト操作は成功します(throwingaではなく)。ClassCastException):

     (Foo) proxy
  • staticProxy.getInvocationHandlerメソッドは、proxy instancepassedに関連付けられた呼び出しハンドラを引数として返します。 Proxy.getInvocationHandlerに渡されたオブジェクトがプロキシインスタンスでない場合、IllegalArgumentExceptionがスローされます。
  • プロキシインスタンス上のインターフェイスメソッド呼び出しは、以下で説明するようにコード化され、呼び出しハンドラのinvokeメソッドにディスパッ

    プロキシインスタンス自体は、Object型の最初のargumentofinvokeとして渡されます。

    invokeに渡される2番目の引数は、プロキシインスタンスで呼び出されるインターフェイスメソッドに対応するjava.lang.reflect.Methodインスタンスになります。 Methodオブジェクトの宣言クラスは、themethodが宣言されたインターフェイスになり、プロキシクラスがメソッドを継承するproxyinterfaceのスーパーインターフェイスになります。

    invokeに渡される3番目の引数は、プロキシインスタンスのメソッド呼び出しで渡される引数の値を含むオブジェクトの配列になります。 Primitivetypesの引数は、java.lang.Integerjava.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で宣言されたhashCodeequals、またはtoStringメソッドの呼び出しは、上記のようにインターフェイスメソッドの呼び出しがエンコードされ、ディスパッチされるのと同じ方法で、呼び出しハンドラのinvokeメソッドにディスパッチされます。 invokeに渡されたMethodオブジェクトの宣言クラスはjava.lang.Objectになります。 java.lang.Objectから継承されたproxyinstanceの他のパブリックメソッドは、プロキシクラスによってオーバーライドされないため、これらのメソッドの呼び出しはjava.lang.Objectのインスタン

MultipleProxyインターフェイスで複製されたメソッド

プロキシクラスの複数のインターフェイスに同じ名前とパラメータシグネチャを持つメソッドが含まれている場合、proxyclassのインターフェイスの順序が重要になります。 このようなduplicatemethodがプロキシインスタンスで呼び出されると、呼び出しハンドラに渡されるMethodオブジェクトは、プロキシのメソッドが呼び出されたイン この制限は、生成されたプロキシクラスの対応するメソッド実装が、呼び出されたインターフェイスを判断できないために存在します。 したがって、プロキシインスタンスで重複メソッドが呼び出されると、プロキシクラスのインターフェイスのリストにあるメソッドを含む最上位のイ

プロキシインターフェイスにjava.lang.ObjecthashCodeequals、またはtoStringメソッドと同じ名前とparameterシグネチャを持つメソッドが含まれている場合、そのようなメソッドがaproxyイ つまり、java.lang.Objectのpublicでfinalでないメソッドは、呼び出しハンドラに渡すオブジェクトのMethodを決定するために、すべてのproxyinterfacesの前に論理的に配置されます。

また、重複メソッドがaninvocationハンドラにディスパッチされると、invokeメソッドは、メソッドのthrows句のexceptiontypesのいずれかに割り当てることができるthrowchecked例外型のみを呼び出 invokeメソッドが、呼び出すことができるプロキシインターフェイスのいずれかのメソッドによって宣言された例外型に割り当てられないチェック例外をスローした場合、anuncheckedUndeclaredThrowableExceptionはプロキシインスタンスの呼び出しによってスローされます。 この制限は、invokeメソッドに渡されたMethodobjectpassedでgetExceptionTypesを呼び出すことによって返されるすべての例外型が、invokeメソッドによって必ずthrownsuccessfullyできるわけではないことを意

シリアル化

java.lang.reflect.Proxyjava.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です。

プロキシクラスには直列化可能なフィールドがなく、serialVersionUID0Lがあります。 つまり、プロキシクラスの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_PROXYCLASSDESCnewHandle 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;

ObjectOutputStreamannotateProxyClassのデフォルトの実装は何もしません。

ObjectInputStreamが型コードTC_PROXYCLASSDESCを検出すると、上記のようにフォーマットされたストリームからプロキシクラスのclassdescriptorを逆シリアル化します。 ClassdescriptorのClassオブジェクトを解決するためにresolveClassメソッドを呼び出す代わりに、java.io.ObjectInputStreamの次のメソッドが呼び出されます:

 protected Class resolveProxyClass(String interfaces) throws IOException, ClassNotFoundException;

proxyclass記述子で逆シリアル化されたインターフェイス名のリストは、interfacesargumenttoresolveProxyClassとして渡されます。

ObjectInputStreamresolveProxyClassの既定の実装は、interfacesパラメーターで指定されたインターフェイスのClassオブジェクトのリストを使用してProxy.getProxyClassを呼び出した結果を返します。 各インターフェイス名iClassobjectusedは、

 Class.forName(i, false, loader)

を呼び出すことによって再調整された値です。loaderは、実行スタックの最初の非nullクラスローダーです。 これは、resolveClassメソッドのデフォルト動作によって行われたのと同じクラスローダーの選択です。 これと同じloaderの値は、Proxy.getProxyClassに渡されるクラスローダーでもあります。Proxy.getProxyClassIllegalArgumentExceptionを投げた場合、resolveClassIllegalArgumentExceptionを含む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呼び出しの動作を実装し、proxyHashCodeproxyEquals、および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() }));

上記のDelegatorclassgivenの実装は、最適化されたものよりも例示的なものであることに注意してください; たとえば、hashCodeequals、およびtoStringメソッドのMethodオブジェクトをキャッシュして比較する代わりに、それらのメソッド名のどれもjava.lang.Objectでオーバーロードされていないため、文字列名でそれらを一致させることができます。

コメントを残す

メールアドレスが公開されることはありません。