2009년 10월 2일 금요일

seasar interceptor (MockInterceptor etc)

AOP(Aspect Oriented Programming)を簡単にいうと元のソースコードに手を加えることなく、透明なサービスを提供する技術といえるのではないかと思います。透明というのは、使っている側からその存在が見えないという意味です。キーとなる概念には次のようなものがあります。

Advice(MethodInterceptor)

プログラム中に挿入されるコードを表します。Interceptorと呼ばれることもあります。

Joinpoint(MethodInvocation)

対象となるクラスとAdviceを結合するポイントを表します。AdviceはJoinpointから引数やメソッドの情報を取得することができます。

Pointcut

どこにJoinpointを設定するのかを定義します。

Aspect

AdviceとPointcutを関連付けます。

S2AOPは、AOP Allianceに準拠しています。それでは、Seasarに最初からついているトレースのためのInterceptorを見てみましょう。

TraceInterceptor

package org.seasar.framework.aop.interceptors;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class TraceInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        StringBuffer buf = new StringBuffer(100);
        buf.append(invocation.getThis().getClass().getName());
        buf.append("#");
        buf.append(invocation.getMethod().getName());
        buf.append("(");
        Object[] args = invocation.getArguments();
        if (args != null && args.length > 0) {
            for (int i = 0; i < args.length; ++i) {
                buf.append(args[i]);
                buf.append(", ");
            }
            buf.setLength(buf.length() - 2);
        }
        buf.append(")");
        System.out.println("BEGIN " + buf);
        try {
            Object ret = invocation.proceed();
            buf.append(" : ");
            buf.append(ret);
            return ret;
        } catch (Throwable t) {
            buf.append(" Throwable:");
            buf.append(t);
            throw t;
        } finally {
            System.out.println("END " + buf);
        }
    }
}

Interceptorを実装するには、MethodInterceptorをimplementsしてinvokeメソッドを実装します。MethodInvocationのgetThis()、getMethod()、getArguments()で対象となるオブジェクト、メソッド、引数を取得できます。proceed()を呼び出すと実際のメソッドが呼び出され実行結果を取得することができます。

それでは、DateクラスにTraceInterceptorを適用します。対象となるメソッドはgetTime()とします。

Pointcut pointcut = new PointcutImpl(new String[]{"getTime"});
Aspect aspect = new AspectImpl(new TraceInterceptor(), pointcut);
AopProxy aopProxy = new AopProxy(Date.class, new Aspect[]{aspect});
Date proxy = (Date) aopProxy.create();
proxy.getTime();

PointcutImplのコンストラクタの引数で対象となるメソッド名を指定(複数可)します。AutoNumberImplのようにインターフェースを実装しているなら、new PointcutImpl(AutoNumberImpl.class)のようにクラスを指定することで、そのクラスが実装しているインターフェースのメソッドをすべて自動的に適用させることもできます。メソッド名には正規表現(JDK1.4のreqex)も使えます。

AopProxyのコンストラクタで、対象となるクラスとAspectの配列を指定します。その後、create()でAspectが適用されたProxyオブジェクトを取得できます。

ThrowsInterceptor

例外処理をCrosscutting concernとして扱えるように、S2ではorg.seasar.framework.aop.interceptors.ThrowsInterceptorが用意されています。使い方は簡単で、ThrowsInterceptorを継承し、Object handleThrowable(Throwable, MethodInvocation)を実装するだけです。ThrowableにはThrowableのサブクラスを指定することができます。例えば、handleThrowable(IOException, MethodInvocation)のようにメソッド定義すると、発生した例外がIOExceptionもしくはIOExceptionのサブクラスの場合に、 呼び出されることになります。handleThrowable()はいくつでも定義することができます。

MockInterceptor

Mockを使ったテストを簡単に行えるように、S2ではorg.seasar.framework.aop.interceptors.MockInterceptorが用意されています。例えば、次のようなインターフェースがあったとします。

public interface Hello {
    public String greeting();
    public String echo(String str);
}

このインターフェースのモックを作成します。モックの仕様として、greetingメソッドが呼び出されたときは、"Hello"を返し、echoメソッドが呼び出されたときは"Hoge"を返すことにします。そのようなモックは次のようにして作成します。

MockInterceptor mi = new MockInterceptor();
mi.setReturnValue("greeting", "Hello");
mi.setReturnValue("echo", "Hoge");
Hello hello = mi.createMock(Hello.class);

MockInterceptor#setReturnValue()の最初の引数にメソッド名、2番目の引数に1番目の引数で指定したメソッドが呼び出されたときに返す引数を指定します。setReturnValue("Hello")のようにメソッド名を省略することもできます。その場合、常に"Hello"がかえってきます。1つのメソッドしか呼び出さないことが分かっている場合に使います。上記のコードをコンポーネント定義で書くと次のようになります。

<component class="Hello">
    <aspect>
	    <component class="org.seasar.framework.aop.interceptors.MockInterceptor">
		    <initMethod name="setReturnValue">
			    <arg>"greeting"</arg>
				<arg>"Hello"</arg>
			</initMethod>
			<initMethod name="setReturnValue">
			    <arg>"echo"</arg>
				<arg>"Hoge"</arg>
			</initMethod>
		</component>
	</aspect>
</component>

また、常に"Hello"を返す場合は次のように定義します。

<component class="Hello">
    <aspect>
	    <component class="org.seasar.framework.aop.interceptors.MockInterceptor">
		    <property name="returnValue">"Hello"</property>
		</component>
	</aspect>
</component>

MockInterceptor#isInvoked(String methodName) : booleanでメソッドが呼び出されたかどうかをチェックできます。 MockInterceptor#getArgs(String methodName) : Object[]で呼び出されたメソッドの引数もチェックできます。これらのチェックを行うには次のようにします。

<component name="helloMockInterceptor" class="org.seasar.framework.aop.interceptors.MockInterceptor">
    <property name="returnValue">"Hello"</property>
</component>
<component class="Hello">
    <aspect>helloMockInterceptor</aspect>
</component>
Hello hello = container.getComponent(Hello.class);
MethodInterceptor mi = container.getComponent("helloMockInterceptor");
hello.echo("Hello");
assertEquals(true, mi.isInvoked("echo"));
assertEquals("Hello", mi.getArgs("echo")[0]);

SyncInterceptor

メソッド呼び出しをAspectを使って同期化できるように、S2ではorg.seasar.framework.aop.interceptors.SyncInterceptorが用意されています。ソースを変更することなく、メソッド呼び出しを同期化できます

댓글 없음:

댓글 쓰기