2012年12月10日月曜日

動的プロキシによるアスペクトとしての実装

アスペクト志向の実装を動的プロキシ(または、ダイナミックプロキシ)と呼ばれる方法でJDK標準で
提供される機能だけで行うことが出来ます。

実現には
java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler
を使用します。

まずは、
java.lang.reflect.InvocationHandler
の実装クラスを用意します。
これは、「呼び出しハンドラ」と呼ばれProxyクラス経由で実行されるメソッドをフックする為に実装します。

public class MyInvocationHandler implements InvocationHandler{
private Object target;
public MyInvocationHandler(Object target){
// 本来のインスタンスを保持しておきます。
this.target = target;
}
@Override
public Object invoke( Object proxy , Method method , Object[] args ) throws Throwable {

System.out.println("invoke method:"+method.getName());

// 本来呼ばれるべきメソッドを本来のインスタンスで実行します。
// 必要に応じて実行すべきでない場合、実行する必要はありません。
Object ret = method.invoke( this.target , args );

System.out.println("invoke result:"+ret.toString());

// 通常の場合、戻り値は本来呼ばれるべきメソッドの結果を返します。
// 但し、必要に応じてそうでなくてもかまいません。
return ret;
}
}

次に、Proxy経由で実行するクラスを用意します。
このときに注意が必要です。
Proxyクラスの動作の特性上、必ずinterfaceとその実装クラスを用意しなければなりません。

interface MyInterface{
String sayHello(String name);
}

class MyClass implements MyInterface{
@Override
public String sayHello( String name ) {
// hello! 名前をコンソールに出力するだけ。
System.out.println("hello! "+name);
// 戻り値としてhello!を返す。
return "hello!";
}

}

最後にProxy経由でインスタンスを生成し、
後は通常の呼び出しと同じようにメソッドを実行します。

public static void main(String[] args) throws Exception{
// 通常のnewで得られるインスタンスを用意しておきます。
MyInterface myInterfaceReal = new MyClass();
// 呼び出しハンドラクラスを生成します。
// その際に実際のインスタンスを渡しておきます。
InvocationHandler handler = new MyInvocationHandler(myInterfaceReal);

// Proxy.newProxyInstanceメソッドを使ってインスタンスを生成します。
MyInterface myInterface = (MyInterface)Proxy.newProxyInstance(
// ClassLoader
MyClass.class.getClassLoader() ,
// メソッドフックに使われるinterface.
MyClass.class.getInterfaces() ,
// 呼び出しハンドラ
handler );
// 実行は通常と変わりません。myInterfaceの方であることに注意してください。
String ret = myInterface.sayHello( "JAVA" );

System.out.println(ret);
}

Proxyインスタンスの生成などは概ね定型的な処理になります。
最後にこの実行結果は次のようになります。

invoke method:sayHello
hello! JAVA
invoke result:hello!
hello!

0 件のコメント:

コメントを投稿