Java

ダイナミックプロキシクラス

ドキュメントの目次

目次

はじめに
ダイナミックプロキシの API
直列化

はじめに

ダイナミックプロキシクラスとは、実行時に指定されたインタフェースのリストを実装するクラスのことです。 ダイナミックプロキシクラスを使用すると、メソッドが呼び出されるときに、そのインスタンスのいずれかのインタフェースを介して符号化され、統一インタフェースを介して別のオブジェクトにディスパッチされます。このため、インタフェースのリストに対して型保証されたプロキシオブジェクトを作成できます。 コンパイル時にツールを使用するなど、プロキシクラスを事前に生成する必要がなくなります。ダイナミックプロキシクラスのインスタンス上でメソッドを呼び出すと、インスタンスの呼び出しハンドラ内の 1 つのメソッドにディスパッチされ、呼び出されたメソッドを識別する java.lang.reflect.Method オブジェクト、および引数を含む Object 型の配列を使用して符号化されます。

ダイナミックプロキシクラスは、インタフェース API を提供するオブジェクト上で呼び出しを行うときに、アプリケーションまたはライブラリから型保証されたリフレクトディスパッチを行う必要がある場合に使用します。たとえば、ダイナミックプロキシクラスをアプリケーションで使用すると、複数の任意のイベントリスナーインタフェース (java.util.EventListener を継承するインタフェース) を実装するオブジェクトを作成し、すべてのイベントログをファイルに記録するなど、さまざまなタイプのイベントを統一された方式で処理できます。

ダイナミックプロキシクラスの API

ダイナミックプロキシクラス (以下「プロキシクラス」) は、クラスを作成するときに、実行時に指定されたインタフェースのリストを実装するクラスです。

プロキシインタフェースは、プロキシクラスによって実装されるインタフェースです。

プロキシインスタンスは、プロキシクラスのインスタンスです。

プロキシクラスの作成

プロキシクラスは、プロキシインスタンスと同様に、java.lang.reflect.Proxy クラスの static メソッドを使用して作成します。

Proxy.getProxyClass メソッドは、クラスローダおよびインタフェースの配列が渡されると、プロキシクラスの java.lang.Class オブジェクトを返します。プロキシクラスは、特定のクラスローダ内に定義されており、提供されたすべてのインタフェースを実装します。同じ組み合わせのインタフェースを持つプロキシクラスが、クラスローダ内にすでに定義されている場合は、既存のプロキシクラスを返します。 既存のプロキシクラスが存在しない場合は、クラスローダ内にプロキシクラスを動的に生成し、定義します。

Proxy.getProxyClass に渡すことのできるパラメータには、いくつかの制約があります。

これらの制約に対して違反が発生した場合は、Proxy.getProxyClass によって IllegalArgumentException がスローされます。interfaces 配列の引数または要素が null の場合は、NullPointerException がスローされます。

プロキシインタフェースは、順番が区別されます。プロキシクラスを 2 回要求したときに、インタフェースの組み合わせが同じで順番が異なる場合は、2 つの異なるプロキシクラスが作成されます。プロキシクラスは、プロキシインタフェースの順番が区別されます。 これは、複数のプロキシインタフェースで、名前およびパラメータシグニチャーを共有するメソッドが使用されている場合に、メソッド呼び出しのエンコーディングが正しく行われるようにするためです。 詳細は、「複数のプロキシインタフェースで重複するメソッド」を参照してください。

同一のクラスローダとインタフェースのリストを使用して Proxy.getProxyClass を呼び出したときに、新しいプロキシクラスが再生成されないようにするには、ダイナミックプロキシクラス API を実装したときに、対応するローダおよびインタフェースのリストをキーにして、生成されたプロキシクラスをキャッシュに格納する必要があります。その実装においては、クラスローダおよびそのクラスが適時ガベージコレクトされるのを妨げるような方法で、クラスローダ、インタフェース、およびプロキシクラスを参照しないように注意する必要があります。

プロキシクラスの特性

プロキシクラスには、次の特性があります。

プロキシインスタンスの作成

各プロキシクラスには、public コンストラクタが 1 つ組み込まれています。 引数には、InvocationHandler インタフェースの実装を指定します。

各プロキシインスタンスには、コンストラクタに渡された呼び出しハンドラオブジェクトが関連付けられます。 プロキシインスタンスは、リフレクション API を介して public コンストラクタにアクセスしなくても、Proxy.newProxyInstance メソッドを呼び出すことによっても作成できます。Proxy.newProxyInstance メソッドでは、Proxy.getProxyClass を呼び出すアクションと、呼び出しハンドラを使用してコンストラクタを呼び出すアクションが行われます。 Proxy.newProxyInstance は、Proxy.getProxyClass の場合と同じ理由で、IllegalArgumentException をスローします。

プロキシインスタンスの特性

プロキシインスタンスには、次の特性があります。

複数のプロキシインタフェースで重複するメソッド

複数のインタフェースに、同じ名前とパラメータシグニチャーを持つメソッドが含まれる場合は、プロキシクラスのインタフェースの順番が区別されます。プロキシインスタンス上で「重複するメソッド」が呼び出された場合、呼び出しハンドラに渡される Method オブジェクトで、プロキシメソッドの呼び出しに使用されたインタフェースの参照型から宣言クラスを割り当てることができないことがあります。このような制約が存在するのは、生成されたプロキシクラス内の対応するメソッドの実装から、その実装が呼び出されたときに使用されたインタフェースを特定できないためです。このため、プロキシインスタンス上で重複するメソッドが呼び出された場合は、メソッド呼び出しに使用された参照型にかかわりなく、プロキシクラスのインタフェースリストでそのメソッド (直接またはスーパーインタフェースから継承) を含むインタフェースのうち、最初のインタフェースのメソッドの Method オブジェクトが呼び出しハンドラの invoke メソッドに渡されます。

プロキシインタフェースに、java.lang.ObjecthashCodeequals、または toString メソッドと同じ名前およびパラメータシグニチャーを持つメソッドが含まれる場合は、プロキシインスタンス上でそのメソッドが呼び出されると、呼び出しハンドラに渡される Method オブジェクトの宣言クラスは java.lang.Object になります。つまり、public で非 final である java.lang.Object のメソッドは、呼び出しハンドラに渡す Method オブジェクトを決定するときに、論理的にほかのプロキシインタフェースより優先されます。

重複するメソッドが呼び出しハンドラにディスパッチされた場合は、invoke メソッドからスローできる例外の型は、チェックされる型のうち、呼び出されるすべてのプロキシインタフェースのメソッドに指定されている、throws 句の例外の型に割り当てることができるものに限定されます。invoke メソッドが、チェックされる例外のうち、呼び出されるプロキシインタフェースのメソッドで宣言されている例外の型に割り当てることができないものをスローした場合は、プロキシインスタンス上の呼び出しによって、チェックされない UndeclaredThrowableException がスローされます。つまり、invoke メソッドに渡された Method オブジェクト上で、getExceptionTypes を呼び出して例外の型を取得しても、invoke メソッドから正常にスローされないことがあります。

直列化

java.lang.reflect.Proxy は、java.io.Serializable を実装するため、この節で説明するように、プロキシインスタンスを直列化することができます。 ただし、プロキシインスタンスに java.io.Serializable に割り当てることができない呼び出しハンドラが含まれている場合は、インスタンスが java.io.ObjectOutputStream に書き込まれると java.io.NotSerializableException がスローされます。プロキシクラスの場合は、java.io.Externalizable を実装すると、java.io.Serializable を実装した場合と同様に直列化を行うことができます。 つまり、直列化の過程で、Externalizable インタフェースの writeExternal および readExternal メソッドが、プロキシインスタンス (または呼び出しハンドラ) 上に呼び出されることはありません。プロキシクラスの Class オブジェクトは、すべての Class オブジェクトと同様に常に直列化することができます。

プロキシクラスには、直列化可能なフィールドおよび 0LserialVersionUID がありません。つまり、プロキシクラスの Class オブジェクトが java.io.ObjectStreamClass の static lookup メソッドに渡されたときに、返される ObjectStreamClass インスタンスには次の特性があります。

オブジェクトの直列化に使用されるストリームプロトコルでは、TC_PROXYCLASSDESC という名前の型コードがサポートされています。 TC_PROXYCLASSDESC は、ストリーム形式の構文で使用する終端記号です。 この型と値は、java.io.ObjectStreamConstants インタフェースの次の定数フィールドに定義されています。

    final static byte TC_PROXYCLASSDESC = (byte)0x7D;

この構文には、次の 2 つの規則も適用されます。 最初の規則は、元の newClassDesc 規則の拡張として、代わりに適用されます。

newClassDesc:
        TC_PROXYCLASSDESC newHandle proxyClassDescInfo

proxyClassDescInfo:
        (int)<count> proxyInterfaceName[count] classAnnotation superClassDesc

proxyInterfaceName:
        (utf)

ObjectOutputStream を使用してプロキシクラスのクラス記述子を直列化する場合は、Class オブジェクトが Proxy.isProxyClass メソッドに渡されるため、上記の規則に従って型コード TC_CLASSDESC の代わりに TC_PROXYCLASSDESC が使用されます。proxyClassDescInfo が展開されたときに、proxyInterfaceName 項目のシーケンスは、プロキシクラスによって実装されたすべてのインタフェースの名前で構成されており、Class オブジェクト上で getInterfaces メソッドの呼び出しから返された順番に並んでいます。classAnnotationsuperClassDesc は、classDescInfo 規則の対応する項目と同じ意味を持ちます。プロキシクラスの場合は、superClassDescjava.lang.reflect.Proxy スーパークラスのクラス記述子です。 この記述子を指定すると、プロキシインスタンスに対して直列化された Proxy クラスを展開することができます。

プロキシクラス以外の場合は、ObjectOutputStream から protected annotateClass メソッドが呼び出され、サブクラスから特定のクラスのストリームにカスタムデータを書き込むことができます。プロキシクラスの場合は、annotateClass ではなく、そのプロキシクラスの Class オブジェクトによって java.io.ObjectOutputStream 内の次のメソッドが呼び出されます。

    protected void annotateProxyClass(Class cl) throws IOException;

ObjectOutputStream 内の annotateProxyClass のデフォルトの実装は、処理を行いません。

ObjectInputStream は、型コード TC_PROXYCLASSDESC を検出すると、プロキシクラスのクラス記述子をストリームから上記の形式で直列化復元します。 クラス記述子の Class オブジェクトを解決するために、resolveClass メソッドが呼び出される代わりに、次の java.io.ObjectInputStream のメソッドが呼び出されます。

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

プロキシクラス記述子に直列化復元されたインタフェース名のリストは、interfaces 引数として resolveProxyClass に渡されます。

ObjectInputStream 内の resolveProxyClass のデフォルトの実装は、interfaces パラメータに指定されたインタフェースの Class オブジェクトのリストを使用して、Proxy.getProxyClass を呼び出した結果を返します。各インタフェース名 i に対して使用される Class オブジェクトは、次の呼び出しによって返された値です。

	Class.forName(i, false, loader)
loader は、実行スタックに格納される null でない最初のクラスローダです。 ただし、null でないクラスローダがスタック上にない場合は、null です。このクラスローダは、resolveClass メソッドによってデフォルトで選択されるクラスローダと同じです。この loader の値は、Proxy.getProxyClass に渡されるクラスローダとなります。Proxy.getProxyClass から IllegalArgumentException がスローされた場合は、resolveClass から、IllegalArgumentException が含まれる ClassNotFoundException がスローされます。

プロキシクラスには独自の直列化フィールドが存在しないため、プロキシインスタンスのストリーム表現内の classdata[] は、すべて java.lang.reflect.Proxy スーパークラスのインスタンスデータで構成されます。Proxy には、h という直列化フィールドが 1 つあります。 このフィールドには、プロキシインスタンスの呼び出しハンドラが含まれています。

任意のインタフェースのリストを実装するオブジェクト上で、メソッドを呼び出す前後にメッセージを出力する簡単な例を示します。

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[0]);
	    } 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[i])) {
		    try {
			return m.invoke(delegates[i], 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 をオーバーライドします。 この結果、プロキシメソッド呼び出しの動作が実装され、ほかのオブジェクトに直接委譲されることがなくなります。 また、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() }));

上記の Delegator クラスの実装は、わかりやすくするために、最適化されていません。 たとえば、hashCodeequals、および toString メソッドの Method オブジェクトをキャッシュして比較する代わりに、文字列名で照合することもできます。 これらのメソッド名は、java.lang.Object 内でオーバーロードされないためです。



Copyright © 1999 Sun Microsystems, Inc.All Rights Reserved.

コメントの送付先:reflection-comments@sun.com
Sun

Java ソフトウェア