Seasar DI Container with AOP

S2AOPの概要

S2AOPでは、AOPの機能を提供しています。AOPとは、Aspect Oriented Programming (アスペクト指向プログラミング) の略です。プログラム本来の目的とは異なる処理を内部に埋め込まず、外から織り込むように作ることです。

AOPを考える上でキーとなる概念

Advice(MethodInterceptor)

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

Joinpoint(MethodInvocation)

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

Pointcut

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

Aspect

AdviceとPointcutを関連付けます。

InterType

フィールドやメソッドの追加、実装するインタフェースの追加など,クラスの静的な構造を変更します。

AOPのメリット

  • 「Core Concern」と「Crosscutting Concern」を分離することでメンテナンス性が向上します。
  • 業務ロジックからシステム的機能を「Crosscutting Concern」に排出した「Core Concern」は、シンプルなソースになります。本来のやりたかったことだけが記述されます。
  • トランザクションの自動化やリモートメソッド呼び出しなど、従来EJBを使用しなければ実現できなかった処理がPOJO(Plain Old Java Object:特定の環境に依存しない普通のjavaのオブジェクト)で可能になります。

S2AOPのメリット

  • 設定をシンプルに行えます。
  • 実装しなければならないJavaインターフェースが1つです。
  • コンポーネントにどんなアスペクトが適用されるのかが明確です。
  • 基本的なAspect実装オブジェクトパターンが用意されているため、すぐに使用することが可能です。(独自にインターフェースや抽象クラスを実装することも可能)

注意点

  • finalなクラスにはアスペクトを適用できません。
  • finalまたはstaticまたは非publicなメソッドにはアスペクトを適用できません。
  • pointcut属性を指定しない場合、すべてのメソッドが対象になるわけではありません。実装しているインターフェースのすべてのメソッドが対象になります。すべてのメソッドを対象にするには、pointcut属性に".*"と指定します。

S2AOPリファレンス

作成すべきファイル

S2AOPを使用するにはS2Container の設定ファイル(diconファイル)で行います。設定ファイルの配置場所は、とくに指定がありませんが、通常「Crosscutting Concern」と同じ場所に配置するか、設定を行うコンポーネントと同じ場所に配置します。

設定ファイルの説明

aspectタグ

Advice(以下Interceptor)をコンポーネントに組み込みます。Interceptorの指定は、ボディでOGNL式を使うか、子タグでcomponentタグを使います。
※OGNLについては OGNLガイドを参照してください。1つのコンポーネントに複数のアスペクトを組み込んだ場合はアスペクトの登録順に組み込まれ実行されます。詳しい説明は独自実装のInterceptorを参照してください。

注意点

aspectタグで指定されたコンポーネントは、コンテナの初期化時にコンテナから取得されます。そのため、aspectタグで指定されたコンポーネントのinstance属性がprototypeだったとしても、Interceptor のメソッドが呼び出される度に新しいインスタンスが作成されるわけではありません。

pointcut属性(任意)

カンマ区切りで対象となるメソッド名を指定することができます。pointcutを指定しない場合は、コンポーネントが実装しているインターフェースのすべてのメソッドが対象になります。メソッド名には正規表現(JDK1.4のregex)も使えます。

設定例

pointcut属性を指定してjava.util.DateのgetTime()メソッドとhashCode()メソッドを対象とする場合以下のようになります。pointcut属性を指定しない場合はjava.util.Dateが実装しているインターフェースのメソッドが対象になります。

<component class="java.util.Date">
    <aspect pointcut="getTime,hashCode">
        <component class="org.seasar.framework.aop.interceptors.TraceInterceptor"/>
    </aspect>
</component>
正規表現を使ってjava.util.Dateのpublicなメソッドすべてを対象としたい場合は、以下のように設定します。
<component class="java.util.Date">
    <aspect pointcut=".*">
        <component class="org.seasar.framework.aop.interceptors.TraceInterceptor"/>
    </aspect>
</component>

interTypeタグ

InterTypeをコンポーネントに組み込みます。InterTypeの指定は、ボディでOGNL式を使うか、子タグでcomponentタグを使います。
※OGNLについては OGNLガイドを参照してください。1つのコンポーネントに複数のInterTypeを組み込んだ場合はInterTypeの登録順に組み込まれます。詳しい説明は独自実装のInterTypeを参照してください。

設定例
<component class="java.util.Date">
    <interType;>
        <component class="org.seasar.framework.aop.intertype.PropertyInterType"/>
    </interType>
</component>

S2AOPで用意されているInterceptor

S2AOPでは、以下のInterceptorを用意しています。また独自のInterceptorを簡単に作成できるようになっています。

(1) TraceInterceptor

クラス名

org.seasar.framework.aop.interceptors.TraceInterceptor

説明

トレース処理を「Crosscutting Concern」として扱うためのInterceptorです。DateクラスにTraceInterceptorを適用したdiconファイルは、以下のようになります。対象とするメソッドはgetTime()とします。

<component class="java.util.Date">
    <aspect pointcut="getTime">
        <component class="org.seasar.framework.aop.interceptors.TraceInterceptor"/>
    </aspect>
</component>

詳しい使用方法はTraceInterceptorを参照してください。

(2) ThrowsInterceptor

クラス名

org.seasar.framework.aop.interceptors.ThrowsInterceptor

説明

例外処理を「Crosscutting concern」として扱うためのInterceptorです。使用するにはThrowsInterceptorを継承し、Object handleThrowable(Throwable, MethodInvocation)を実装するだけです。ThrowableにはThrowableのサブクラスを指定することができます。例えばhandleThrowable(IOException, MethodInvocation)のようにメソッド定義すると、ThrowsInterceptorを適用したコンポーネント内で発生した例外がIOExceptionもしくはIOExceptionのサブクラスの場合に、呼び出されることになります。handleThrowable()はいくつでも定義することができます。詳しい使用方法はThrowsInterceptorを参照してください。

(3) MockInterceptor

クラス名

org.seasar.framework.aop.interceptors.MockInterceptor

説明

Mockを使ったテストを簡単に行うためのInterceptorです。詳しい説明はテスト技法のモックを作成するための設定を参照してください。

(4) DelegateInterceptor

クラス名

org.seasar.framework.aop.interceptors.DelegateInterceptor

説明

メソッド呼び出しを別のコンポーネントに委譲するためのInterceptorです。使用方法はDelegateInterceptorのtargetプロパティに委譲したい相手を指定します。委譲するときのメソッド名が異なる場合には、DelegateInterceptor#addMethodNameMap(String methodName, String targetMethodName)で指定します。例えば、bar()というメソッドをfoo.bar2()に委譲する場合、DelegateInterceptor#setTarget(foo),DelegateInterceptor#addMethodNameMap("bar", "bar2")のように指定します。詳しい使用方法はDelegateInterceptorを参照してください。

注意

targetプロパティに指定されたコンポーネントは、コンテナの初期化時にコンテナから取得されます。このため、targetプロパティに指定されたコンポーネントのinstance属性がprototypeであっても、常に同じインスタンスが使われます。メソッド呼び出しの度に新しいインスタンスをコンテナから取得したい場合は次のPrototypeDelegateInterceptorを使用してください。

(5) PrototypeDelegateInterceptor

クラス名

org.seasar.framework.aop.interceptors.PrototypeDelegateInterceptor

説明

メソッド呼び出しを別のコンポーネントに委譲するためのInterceptorです。メソッド呼び出しの度にコンポーネントをコンテナから取得します。使用方法はPrototypeDelegateInterceptorのtargetNameプロパティに委譲したい相手の名前を指定します。委譲するときのメソッド名が異なる場合には、PrototypeDelegateInterceptor#addMethodNameMap(String methodName, String targetMethodName)で指定します。例えば、bar()というメソッドをfoo.bar2()に委譲する場合、PrototypeDelegateInterceptor#setTarget(foo),PrototypeDelegateInterceptor#addMethodNameMap("bar", "bar2")のように指定します。詳しい使用方法はPrototypeDelegateInterceptorを参照してください。

(6) SyncInterceptor

クラス名

org.seasar.framework.aop.interceptors.SyncInterceptor

説明

メソッド呼び出しをAspectを使って同期化するためのInterceptorです。ソースを変更することなく、メソッド呼び出しを同期化できます。詳しい使用方法はSyncInterceptorを参照してください。

(7) InterceptorChain

クラス名

org.seasar.framework.aop.interceptors.InterceptorChain

説明

複数のInterceptorをグルーピング化し、再利用しやすくします。複数のInterceptorの組み合わせを複数コンポーネントに適用する場合は、InterceptorChainで複数のInterceptor1つにまとめて、各コンポーネントにはInterceptorChainを指定するようにするといいでしょう。

<component name="interceptor1" .../>
<component name="interceptor2" .../>
<component name="interceptor3" .../>
<component name="chain" class="org.seasar.framework.aop.interceptors.InterceptorChain">
<initMethod name="add"><arg>interceptor1</arg></initMethod>
<initMethod name="add"><arg>interceptor2</arg></initMethod>
<initMethod name="add"><arg>interceptor3</arg></initMethod>
</component> <component ...> <aspect>chain</aspect> </component> <component ...> <aspect>chain</aspect> </component>

(8) InterceptorLifecycleAdapter

クラス名

org.seasar.framework.aop.interceptors.InterceptorLifecycleAdapter

説明

Interceptorはアスペクトをクラスに組み込む際にインスタンス化されるため、singleton以外の場合は意図したとおりに動作しません。
このような場合は、InterceptorLifecycleAdapterをMethodInterceptorのinvoke()メソッドに適用します.

<component name="myInterceptor" instance="prototype" .../>
    <aspect pointcut="invoke">
        <component class="org.seasar.framework.aop.interceptors.InterceptorLifecycleAdapter">
    </aspect>
</component>
<component ...>
    <aspect>myInterceptor</aspect>
</component>

独自実装によるInterceptor

説明

独自にInterceptorを作成する場合は、次のインターフェースまたは、抽象クラスを実装します。

org.aopalliance.intercept.MethodInterceptor
org.seasar.framework.aop.interceptors.AbstractInterceptor

どちらの場合も実装するメソッドは、以下のinvoke()メソッドの1つだけです。

public Object invoke(MethodInvocation invocation) throws Throwable

AbstractInterceptorは、MethodInterceptorをimplementsした抽象クラスです。AbstractInterceptorには、Proxyオブジェクトを取得するcreateProxy()メソッドとアスペクトを適用するクラスを取得するgetTargetClass()メソッドがあります。アスペクトを適用したクラス名を必要とするInterceptor(例えば、ログ出力を行うInterceptor)を作成する場合は、AbstractInterceptorを使用することで簡単にクラス名を取得することができます。

public Object createProxy(Class proxyClass)
protected Class getTargetClass(MethodInvocation invocation)

MethodInvocationのgetThis()、getMethod()、getArguments()で対象となるオブジェクト、メソッド、引数を取得できます。getThis()でクラス名を取得するとバイトコードで組み込まれたクラス名が取得されます。proceed()を呼び出すと実際のメソッドが呼び出され実行結果を取得することができます。以下のような独自のInterceptorを作成したとします。

注意点

Interceptorのインスタンスは、コンテナの初期化時に作成されてクラスに組み込まれます。 そのため、Interceptorを定義した<component>要素のinstance属性がprototype等だったとしても、 Interceptorのメソッドが呼び出される度に新しいインスタンスが作成されるわけではありません。
instance属性がsingleton以外でないと正しく動作しないInterceptorにはInterceptorLifecycleAdapterを適用してください。

作成例

MethodInvocation#proceed()を呼ぶ前と後で2分され、呼ぶ前は Beforeの個所を実行し、呼んだ後はAfterの個所を実行します。1つのコンポーネントに複数のアスペクトが定義されている場合は、以下のよう実行されます。

  1. Aspectの登録順にMethodInterceptorのbefore部分が実行されます。
  2. 最後のMethodInterceptorのbefore部分を実行した後にコンポーネント自身のメソッドが呼び出されます。
  3. Aspectの登録の逆順にMethodInterceptorのafter部分が実行されます。

詳しい使用方法は独自実装によるInterceptorを参照してください。

独自実装によるInterType

説明

独自にInterTypeを作成する場合は、次のインターフェースまたは、抽象クラスを実装します。

org.seasar.framework.aop.InterType
org.seasar.framework.aop.intertype.AbstractInterType

InterTypeを実装するクラスは、以下のメソッドを実装します。

public void introduce(Class targetClass, CtClass enhancedClass)

targetClassはInterTypeが適用されるクラスです。enhancedClassはInterTypeを組み込むクラスで、先に登録されているInterceptorやInterTypeが適用済みの場合もあります。
CtClassの詳細はJavassistチュートリアルJavaDocを参照してください。

AbstractInterTypeは、InterTypeをimplementsした抽象クラスです。AbstractInterTyeのサブクラスは次のメソッドを実装します。

public void introduce() trhows CannotCompileException, NotFoundException

AbstractInterTypeのサブクラスは、次のメソッドで必要なオブジェクトを取得することができます。

protected Class getTargetClass()
protected CtClass getEnhancedClass()
protected ClassPool getClassPool()

AbstractInterTypeは、フィールドやメソッド,実装するインタフェースを追加するためのユーティリティメソッドを提供します。以下はその一部です。

protected void addField(Class type, String name)
protected void addField(Class type, String name, String init)
protected void addStaticField(Class type, String name)
protected void addStaticField(Class type, String name, String init)
protected void addMethod(String name, String src)
protected void addMethod(Class returnType, String name, String src)
protected void addMethod(String name, Class[] paramTypes, String src)
protected void addMethod(Class returnType, String name, Class[] paramTypes, String src)
protected void addStaticMethod(String name, String src)
protected void addStaticMethod(Class returnType, String name, String src)
protected void addStaticMethod(String name, Class[] paramTypes, String src)
protected void addStaticMethod(Class returnType, String name, Class[] paramTypes, String src)

diconファイルを使用しないでアスペクトを組み込む方法

diconファイルの設定を行わずプログラム上でアスペクトを組み込むこともできます。作成方法は次のようになります。

  • org.seasar.framework.aop.impl.PointcutImplのコンストラクタの引数で対象となるメソッド名を指定(複数可)します。java.util.HashMapのようにインターフェースを実装しているなら、new PointcutImpl(HashMap.class)のようにクラスを指定することで、そのクラスが実装しているインターフェースのメソッドをすべて自動的に適用させることもできます。
  • org.seasar.framework.aop.impl.AspectImplのコンストラクタの第1引数にInterceptorを指定して、第2引数にPointcutImplで作成したPointcutを指定します。
  • org.seasar.framework.aop.proxy.AopProxyのコンストラクタで、対象となるクラスとAspectImplで作成したAspectの配列を指定します。
  • org.seasar.framework.aop.proxy.AopProxy#create()でAspectが適用されたオブジェクトを取得できます。

java.util.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();

Example

以下のサンプルを実行する場合は、セットアップを行う必要があります。

TraceInterceptor

TraceInterceptorを使用してjava.util.ArrayListクラスとjava.util.DateクラスのgetTime()メソッドとhashCode()メソッドが呼ばれた場合にトレースを出力させましょう。作成するファイルは以下のとおりです。

  • コンポーネントを定義するdiconファイル(Trace.dicon)
  • 設定が正しく行われているか確認する実行javaファイル(AopTraceClient.java)

diconファイルの作成

  • TraceInterceptorをコンポーネント定義します。name属性をtraceInterceptorとします。
  • java.util.ArrayListクラスのコンポーネントの定義します。aspectタグにInterceptorを指定します。
  • java.util.Dateクラスのコンポーネントの定義します。pointcut属性にgetTime()メソッドとhashCode()メソッドを指定します。argタグを使用してjava.util.Dateクラスのコンストラクタに0を設定します。aspectタグにInterceptorを指定します。

Trace.dicon

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component name="traceInterceptor"
               class="org.seasar.framework.aop.interceptors.TraceInterceptor"/>
    <component class="java.util.ArrayList>
        <aspect>traceInterceptor</aspect>
    </component>
    <component class="java.util.Date">
        <arg>0</arg>
        <aspect pointcut="getTime, hashCode">
            traceInterceptor
        </aspect>
    </component>
</components>

実行ファイルの作成

  • org.seasar.framework.container.S2Container#create()メソッドの最初の引数に作成したdiconファイル(Trace.dicon)のパスを指定してコンテナを作成します。
  • org.seasar.framework.container.S2Container#getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(List.class、Date.class)を指定してコンポーネントを取得します。
  • トレースがAspectされるか確認するために取得したコンポーネントのArrayListのsize()メソッドを実行します。
  • 同様に取得したコンポーネントのDateのgetTime()メソッド、hashCode()メソッドを実行します。

AopTraceClient.java

package examples.aop.traceinterceptor;

import java.util.Date;
import java.util.List;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;

public class AopTraceClient {
    private static String PATH = "examples/aop/traceinterceptor/Trace.dicon";

    public static void main(String[] args) {
        S2Container container = S2ContainerFactory.create(PATH);
        List list = (List) container.getComponent(List.class);
        list.size();
        Date date = (Date) container.getComponent(Date.class);
        date.getTime();
        date.hashCode();
    }
}

実行結果

メソッドが呼ばれる前と後でトレースが出力されているのが確認できます。また、java.util.DateのhashCode()メソッドはメソッド内でgetTime()メソッドを呼んでいるのでhashCode()メソッドとgetTime()メソッドがトレースされていることが確認できます。

BEGIN java.util.ArrayList#size()
END java.util.ArrayList#size() : 0
BEGIN java.util.Date#getTime()
END java.util.Date#getTime() : 0
BEGIN java.util.Date#hashCode()
BEGIN java.util.Date#getTime()
END java.util.Date#getTime() : 0
END java.util.Date#hashCode() : 0

このサンプルは、seasar2/src/examples/aop/traceinterceptor以下に用意されています。


ThrowsInterceptor

(1) ThrowsInterceptorを使って、例外が発生した場合でも処理を続けられるようにしましょう。作成するファイルは以下のようになります。

  • RuntimeExceptionを発生させるクラス(Checker.java)
  • ThrowsInterceptorを継承して作成するInterceptor(HandleThrowableInterceptor.java)
  • コンポーネントの定義をするdiconファイル(Checker.dicon)
  • 設定が正しく行われているか確認する実行ファイル(AopCheckerClient.java)

例外を発生させるクラスの作成

  • run()メソッドの引数がnullでは無い場合は、引数の文字をコンソールに出力します。
  • 引数がnullの場合は、NullPointerExceptionを発生させます。

Checker.java

package examples.aop.throwsinterceptor;

public class Checker {
    public void check(String str) {
        if (str != null) {
            System.out.println(str);
        } else {
            throw new NullPointerException("null");
        }
    }
}

ThrowsInterceptorを継承するInterceptorの作成

  • ThrowsInterceptorを継承します。
  • handleThrowable(Throwable, MethodInvocation)を実装します。

HandleThrowableInterceptor.java

package examples.aop.throwsinterceptor;

import org.aopalliance.intercept.MethodInvocation;
import org.seasar.framework.aop.interceptors.ThrowsInterceptor;

public class HandleThrowableInterceptor extends ThrowsInterceptor {
    public void handleThrowable(Throwable t, MethodInvocation invocation)
        throws Throwable {
    }
}

diconファイルの作成

  • 作成したInterceptorをコンポーネント定義します。name属性をhandleThrowableInterceptorとします。
  • 例外を発生させるCheckerクラスのcheck()メソッドに作成したHandleThrowableInterceptorをアスペクトします。

Checker.dicon

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component name="handleThrowableInterceptor"
               class="examples.aop.throwsinterceptor.HandleThrowableInterceptor"/>
    <component class="examples.aop.throwsinterceptor.Checker">
        <aspect pointcut="check">
            handleThrowableInterceptor
        </aspect>
    </component>
</components>

実行ファイルの作成

  • org.seasar.framework.container.S2Container#create()メソッドの第1引数に作成したdiconファイル(Checker.dicon)のパスを指定してコンテナを作成します。
  • org.seasar.framework.container.S2Container#getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(Foo.class)を指定して取得します。
  • Checker#check()メソッドの引数に"foo"の文字列を渡します。
  • Checker#check()メソッドの引数にnullを渡して例外を発生させます。
  • Checker#check()メソッドの引数に"hoge"の文字列を渡します。

AopCheckerClient.java

package examples.aop.throwsinterceptor;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;

public class AopCheckerClient {
    private static String PATH = "examples/aop/throwsinterceptor/Checker.dicon";

    public static void main(String[] args) {
        S2Container container = S2ContainerFactory.create(PATH);
        Checker checker = (Checker) container.getComponent(Checker.class);
        checker.check("foo");
        checker.check(null);
        checker.check("hoge");
    }
}

実行結果

"hoge"が表示されていることから例外で処理が止まっていないことが確認できます。

foo
hoge

このサンプルは、seasar2/src/examples/aop/throwsinterceptorr以下に用意されています。


(2) 例外を別の例外に変換するInterceptorを作成して変換したメッセージを表示させましょう。作成するInterceptorは、先ほどThrowsInterceptorを継承して作成したクラス(HandleThrowableInterceptor.java)を応用して作成しましょう

  例外を別の例外に変換するInterceptorの作成

handleThrowable(Throwable, MethodInvocation)メソッドの第1引数がNullPointerExceptionの場合、org.seasar.framework.exception.SRuntimeExceptionを発生させてメッセージを変更します。

package examples.aop.throwsinterceptor;

import org.aopalliance.intercept.MethodInvocation;
import org.seasar.framework.aop.interceptors.ThrowsInterceptor;
import org.seasar.framework.exception.SRuntimeException;

public class HandleThrowableInterceptor extends ThrowsInterceptor {
    public void handleThrowable(NullPointerException t, MethodInvocation invocation)
            throws Throwable {
        throw new SRuntimeException("ESSR0007", new Object[] { "引数" });
    }
}

先ほど作成した実行ファイルを使って実行します。

実行結果

エラーメッセージが変わっているのが確認できます。

foo
org.seasar.framework.exception.SRuntimeException: [ESSR0007]引数はnullあるいは空であってはいけません
	at examples.aop.throwsinterceptor.HandleThrowableInterceptor.
handleThrowable(HandleThrowableInterceptor.java:11)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:324)
	at org.seasar.framework.aop.interceptors.ThrowsInterceptor.invoke(ThrowsInterceptor.java:55)
	at org.seasar.framework.aop.impl.MethodInvocationImpl.proceed(MethodInvocationImpl.java:60)
	at org.seasar.framework.aop.proxy.AopProxy.intercept(AopProxy.java:123)
	at examples.aop.throwsinterceptor.Checker$$EnhancerByCGLIB$$8cdef299.check(<generated>)
	at examples.aop.throwsinterceptor.AopCheckerClient.main(AopCheckerClient.java:13)
Exception in thread "main"

このサンプルは、seasar2/src/examples/aop/throwsinterceptorr以下に用意されています。

DelegateInterceptor

S2AOPで用意されているDelegateInterceptorを使って、他のクラスのメソッドに委譲させましょう。
ここでは、インターフェースで抽象メソッドを作成して、抽象クラスと普通のクラスの両方にそのインターフェースを実装させます。抽象クラスで実装したメソッドを普通のクラスで実装したメソッドに委譲させましょう。作成するファイルは以下のとおりです。

  • インターフェース(IBase.java)
  • インターフェースを実装した抽象クラス(Dummy.java)
  • インターフェースを実装したクラス(Substance.java)
  • 委譲を設定するdiconファイル(Delegate.dicon)
  • 設定が正しく行われているか確認する実行ファイル(AopDelegateClient.java)

インターフェースの作成

  • 抽象メソッドを作成します。

IBase.java

package examples.aop.delegateinterceptor;

public interface IBase {
    public abstract void run();
}

インターフェースを実装した抽象クラスの作成

  • 作成したインターフェースを実装します。

Dummy.java

package examples.aop.delegateinterceptor;

public abstract class Dummy implements IBase {
}

インターフェースを実装したクラスの作成

  • 作成したインターフェースを実装します。
  • 実装したインタフェースの抽象メソッドの実装します。

Substance.java

package examples.aop.delegateinterceptor;

public class Substance implements IBase {
    public void run() {
        System.out.println("substance");
    }
}

diconファイルの作成

  • 抽象クラス(Dummy.java)をコンポーネント定義します。
  • DelegateInterceptorのコンポーネントをaspectタグに定義します。DelegateInterceptor#setTarget()を使って委譲したい相手を指定します。委譲したいクラスはexamples.aop.delegateinterceptor.Substanceクラスなのでメソッド・インジェクションを使ってセットします。

Delegate.dicon

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component class="sample.aop.delegateinterceptor.Dummy">
        <aspect>
            <component class="org.seasar.framework.aop.interceptors.DelegateInterceptor">
                <initMethod name="setTarget">
                    <arg>new sample.aop.delegateinterceptor.Substance()</arg>
                </initMethod>
            </component>
        </aspect>
    </component>
</components>

実行ファイルの作成

  • org.seasar.framework.container.S2Container#create()メソッドの最初の引数に作成したdiconファイル(Delegate.dicon)のパスを指定してコンテナを作成します。
  • org.seasar.framework.container.S2Container#getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(IBase.class)を指定して取得します。
  • コンテナから取得したIBase#run()メソッドを実行します。
AopDelegateCilent.dicon
package examples.aop.delegateinterceptor;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;

public class AopDelegateClient {
    private static String PATH = "examples/aop/delegateinterceptor/Delegate.dicon";
    public static void main( String[] args ){
        S2Container container = S2ContainerFactory.create(PATH);
        IBase base = (IBase) container.getComponent(Dummy.class);
        base.run();
    }
}

実行結果

コンソールに"substance"と表示されているのでDummy#run()がSubstance#run()に委譲されているのが確認できます。

substance

このサンプルは、seasar2/src/examples/aop/delegateinterceptor以下に用意されています。


PrototypeDelegateInterceptor

S2AOPで用意されているPrototypeDelegateInterceptorを使って、singletonのコンポーネントからprototypeのコンポーネントのメソッドに委譲させましょう。
ここでは、インターフェースで抽象メソッドを作成して、抽象クラス(singleton)と普通のクラス(prototype)の両方にそのインターフェースを実装させます。抽象クラスで実装したメソッドを普通のクラスで実装したメソッドに委譲させましょう。作成するファイルは以下のとおりです。

  • インターフェース(IBase.java)
  • インターフェースを実装したsingletonの抽象クラス(Dummy.java)
  • インターフェースを実装したprototypeのクラス(Substance.java)
  • 委譲を設定するdiconファイル(Delegate.dicon)
  • 設定が正しく行われているか確認する実行ファイル(AopPrototypeDelegateClient.java)

インターフェースの作成

  • 抽象メソッドを作成します。

IBase.java

package examples.aop.delegateinterceptor;

public interface IBase {
    public abstract void run();
}

インターフェースを実装した抽象クラスの作成

  • 作成したインターフェースを実装します。

Dummy.java

package examples.aop.delegateinterceptor;

public abstract class Dummy implements IBase {
}

インターフェースを実装したクラスの作成

  • 作成したインターフェースを実装します。
  • 実装したインタフェースの抽象メソッドの実装します。

Substance.java

package examples.aop.delegateinterceptor;

public class Substance implements IBase {
    public void run() {
        System.out.println(this);
    }
}

diconファイルの作成

  • 抽象クラス(Dummy.java)をコンポーネント定義します。このコンポーネントはsingletonです。
  • PrototypeDelegateInterceptorのコンポーネントをaspectタグに定義します。PrototypeDelegateInterceptorのtargetNameプロパティに委譲したい相手の名前("target")を指定します。
  • 委譲したいクラス(Substance.java)をコンポーネント定義します。このコンポーネントのinstance属性はprototypeです。

PrototypeDelegate.dicon

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component class="sample.aop.delegateinterceptor.Dummy">
        <aspect>
            <component class="org.seasar.framework.aop.interceptors.PrototypeDelegateInterceptor">
                <property name="targetName">"target"</property>
            </component>
        </aspect>
    </component>

    <component name="target"
                  class="examples.aop.prototypedelegateinterceptor.Substance"
                  instance="prototype"/>
</components>

実行ファイルの作成

  • org.seasar.framework.container.S2Container#create()メソッドの最初の引数に作成したdiconファイル(PrototypeDelegate.dicon)のパスを指定してコンテナを作成します。
  • org.seasar.framework.container.S2Container#getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(IBase.class)を指定して取得します。
  • コンテナから取得したIBase#run()メソッドを繰り返し実行します。
AopPrototypeDelegateCilent.dicon
package examples.aop.prototypedelegateinterceptor;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;

public class AopDelegateClient {
    private static String PATH = "examples/aop/prototypedelegateinterceptor/PrototypeDelegate.dicon";
    public static void main( String[] args ){
        S2Container container = S2ContainerFactory.create(PATH);
        IBase base = (IBase) container.getComponent(Dummy.class);
        for (int i = 0; i < 5; ++i) {
            base.run();
        }
    }
}

実行結果

コンソールに"examples.aop.prototypedelegateinterceptor.Substance@〜"と表示されているのでDummy#run()がSubstance#run()に委譲されているのが確認できます。また、"@"の後に表示されているハッシュコード値が異なっていることから,run()メソッドを呼び出すたびに新しいSubstanceのインスタンスが作成されていることが分かります。

examples.aop.prototypedelegateinterceptor.Substance@ae533a
examples.aop.prototypedelegateinterceptor.Substance@da18ac
examples.aop.prototypedelegateinterceptor.Substance@8a0544
examples.aop.prototypedelegateinterceptor.Substance@401369
examples.aop.prototypedelegateinterceptor.Substance@e49dcd

このサンプルは、seasar2/src/examples/aop/delegateinterceptor以下に用意されています。


SyncInterceptor

SyncInterceptorを使って、同期を取りましょう。
まずは、SyncInterceptorを適用しない場合のサンプルを作成しましょう。複数のスレッドが共有オブジェクトにアクセスして、ある変数に1加算するクラスを作成しましょう。作成するファイルは以下のとおりです。

  • ある変数に1加算するインターフェース(Count.java)
  • インターフェースを実装するクラス(CountImpl.java)
  • コンポーネントの定義を行うdiconファイル(SyncCalc.dicon)
  • 設定が正しく行われているか確認する実行ファイル(AopSyncClient.java)
ある変数に1加算するインターフェース作成
  • 加算するメソッドを作成します。
  • カウント数を取得するメソッドを作成します。

Count.java

package examples.aop.syncinterceptor;

public interface Count {

    public void add();

    public int get();
}

インターフェースの実装クラス

  • インターフェースを実装します。
  • 加算していく変数を宣言します。初期値は0とします。
  • インターフェースのadd()メソッドを実装して、処理はスレッドを2秒間止めてから変数に1加算するようにする。また、加算する前の数値を表示します。
  • インターフェースのget()メソッドを実装して、処理は変数の値を返すようにします。

CountImpl.java

package examples.aop.syncinterceptor;

public class CountImpl implements Count {
    private int _count = 0;

    public void add() {
        int a = _count;

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        _count = a + 1;
        System.out.println(a);
    }

    public int get() {
        return _count;
    }
}

diconファイルの作成

  • 作成したCountImplをコンポーネント定義します。

Count.dicon

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component class="examples.aop.syncinterceptor.CountImpl">
    </component>
</components>

実行ファイルの作成

  • org.seasar.framework.container.S2Container#create()メソッドの第1引数に作成したdiconファイル(SyncCalc.dicon)のパスを指定してコンテナを作成します。
  • org.seasar.framework.container.S2Container#getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(Count.class)を指定して取得します。
  • Runnableを作成する。その際にThread が実行する処理のrun()メソッドにCountクラスのadd()メソッドの処理を行うようにします。
  • Threadを5つ作成します。
  • Thread#start()メソッドで作成した5つのスレッドを実行可能状態にします。
  • Thread#join()メソッドを使って当該スレッドが終了するまで、呼び出し元のスレッドを待機します。
  • すべてのスレッドが呼ばれたら、カウント数を表示します。

AopSyncClient.java

package examples.aop.syncinterceptor;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;

public class AopSyncClient {
    private String PATH = "examples/aop/syncinterceptor/SyncCalc.dicon";
    private Count _count = null;

    public void init() {
        S2Container container = S2ContainerFactory.create(PATH);
        _count = (Count) container.getComponent(Count.class);
    }

    public void start() {
        System.out.println("count: " + _count.get());

        Runnable r = new Runnable() {
            public void run() {
                _count.add();
            }
        };
        Thread[] thres = new Thread[5];
        for (int i=0; i<5; i++) {
            thres[i] = new Thread(r);
            thres[i].start();
        }
        for (int i=0; i<5; i++) {
            try {
                thres[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("count: " + _count.get());
    }

    public static void main(String[] args) {
        AopSyncClient asc = new AopSyncClient();
        asc.init();
        asc.start();
    }
}

実行結果

同期が取れていないことが確認できます。

count: 0
0
0
0
0
0
count: 1
SyncInterceptorを適用して同期が取れるようにしましょう。
  • diconファイルに定義したコンポーネントにSyncInterceptorをアスペクトします。

Count.dicon

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component class="examples.aop.syncinterceptor.CountImpl">
        <aspect>
            <component class="org.seasar.framework.aop.interceptors.SyncInterceptor"/>
        </aspect>
    </component>
</components>

実行結果

1ずつ加算されていることから同期が取れていることが確認できます。

count: 0
0
1
2
3
4
count: 5

このサンプルは、seasar2/src/examples/aop/syncinterceptor以下に用意されています。


独自実装によるInterceptor

クラス名、メソッド名、引数とメソッドの処理時間を計測してトレースするInterceptorを作成しましょう。また、そのInterceptorを使用して重い処理を行った時間をトレースさせましょう。作成するファイルは以下のとおりです。

  • クラス名、メソッド名、引数とメソッドの処理時間を計測して出力するInterceptor(MeasurementInterceptor.java)
  • 重い処理を行うクラス(HeavyProcess.java)
  • コンポーネントの定義を行うdiconファイル(Measurement.dicon)
  • 設定が正しく行われているか確認する実行ファイル(AopMeasurementClient.java)

独自実装のIntercepterの作成

  • org.seasar.framework.aop.interceptors.AbstractInterceptorクラスを実装します。
  • invoke(MethodInvocation invocation)メソッドを実装します。
  • getTargetClass(invocation).getName()でクラスの完全限定名を取得します。MethodInvocation#getThis()だとバイトコードで変換されて組み込まれたクラス名になります。
  • invocation.getMethod().getName()でメソッド名を取得します。
  • invocation.getArguments()で引数を取得します。
  • invocation.proceed()で実際のメソッドが呼ばれるので、その前の時間を取得します。
  • invocation.proceed()で実際のメソッドが呼ばれた後の時間を取得してfinallyで出力します。

MeasurementInterceptor.java

package examples.aop.originalinterceptor;

import org.aopalliance.intercept.MethodInvocation;
import org.seasar.framework.aop.interceptors.AbstractInterceptor;

public class MeasurementInterceptor extends AbstractInterceptor{
    public Object invoke(MethodInvocation invocation) throws Throwable {
        long start = 0;
        long end = 0;
        StringBuffer buf = new StringBuffer(100);

        buf.append(getTargetClass(invocation).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(")");
        try {
            start = System.currentTimeMillis();
            Object ret = invocation.proceed();
            end = System.currentTimeMillis();
            buf.append(" : ");
            return ret;
        } catch (Throwable t) {
            buf.append(" Throwable:");
            buf.append(t);
            throw t;
        } finally {
            System.out.println(buf.toString() + (end - start));
        }
    }
}

重い処理を行うクラスの作成

  • 重い処理を行ったということにするために5秒間sleepします。

HeavyProcess.java

package examples.aop.originalinterceptor;

public class HeavyProcess {
    public void heavy(){
        try{
            Thread.sleep(5000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}
diconファイルの作成
  • 作成したMeasurementInterceptorをコンポーネント定義します。name属性をmeasurementとします。
  • HeavyProcessクラスのheavy()メソッドにMeasurementInterceptorをaspectします。

Measurement.dicon

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components>
    <component name="measurement" class="examples.aop.originalinterceptor.MeasurementInterceptor"/>
    <component class="examples.aop.originalinterceptor.HeavyProcess">
        <aspect pointcut="heavy">
               measurement
        </aspect>
    </component>
</components>

実行ファイルの作成

  • org.seasar.framework.container.S2Container#create()メソッドの第1引数に作成したdiconファイル(Measurement.dicon)のパスを指定してコンテナを作成します。
  • org.seasar.framework.container.S2Container#getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(HeavyProcess.class)を指定して取得します。
  • コンテナから取得したHeavyProcess#heavy()メソッドを実行します。

AopMeasurementClient.java

package examples.aop.originalinterceptor;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;

public class AopMeasurementClient {
    private static String PATH = "examples/aop/originalinterceptor/Measurement.dicon";

    public static void main(String[] args) {
        S2Container container = S2ContainerFactory.create(PATH);
        HeavyProcess heavyProcess = (HeavyProcess) container
                .getComponent(HeavyProcess.class);
        heavyProcess.heavy();
    }
}

実行結果

クラス名、メソッド名、引数とメソッドの処理時間がトレースされているのが確認できます。

examples.aop.HeavyProcess#heavy() : 5004

このサンプルは、seasar2/src/examples/aop/originalinterceptor以下に用意されています。