Seasar DI Container with AOP

S2AOPの概要

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

オブジェクト指向

現在のオブジェクト指向でプログラムを行う場合、下の図のように分けることが出来ます。

  

   

オブジェクト指向では顧客からの要求である機能(Core Concern)とロギング機能、宣言的トランザクション、DBコネクションの取得・解放、例外処理、セキュリティ機能や分散処理などの非機能要求(Crosscutting Concern)が同じクラスの同じメソッドの中に実装されることがあります。

オブジェクト指向の問題点

上で挙げているように非機能要求が散在します。この問題点を例に挙げて説明します。

例)プログラムを作成したところ、何らかの欠陥が見つかりプログラムの動作がおかしく、どこでおかしくなっているのかは分からないので、プログラム実行中の節目ごとにその途中経過を記録(ロギング)したい場合、ログを書き出すサンプルのコードを以下に示します。太字で書いた場所がログを書き出す場所です。

class Foo {
    private Bar bar = new Bar();
public void foo(){
logger.log("BEGIN Foo#foo");
bar.doSomething();
logger.log("END Foo#foo");
}
}
class Bar {
    private Baz baz = new Baz();
public void doSomething(){
logger.log("BEGIN Bar#doSomething");
baz.doSomething();
logger.log("END Bar#doSomething");
}
}

このサンプルのようにロギングするということは、プログラムの複数箇所にその命令を追加する必要があります。また、編集ツールの検索機能などを使って該当箇所を探すとしても、プログラムの書き換えは最終的に手作業となります。これは、次のような問題を引き起こします。

  • 処理が複数箇所に分散します。後になって集めたい情報が増えたり、処理そのものが不要になったりすると、それに合わせてすべての場所を変更する必要が出てきます。
  • プログラマが、処理を追加するべきでない場所に追加してしまう可能性があります。
  • プログラマが、処理を追加するべき場所に追加し忘れる可能性があります。
  • プログラマが、処理として誤ったコードを追加してしまう可能性があります。
  • プログラマが、作業中に誤って本来のプログラムを壊してしまう可能性があります。

このロギングの例のように、複数のクラスにまたがった非機能要求の処理を「Crosscutting Concern」と呼びます。

「Crosscuting Concern」を分散させないために、モジュール化して取り除き、実行時またはコンパイル時にバイトコードで組み込む仕組みが必要になります。

先程のロギングの例をS2AOPを使うと以下のようにXMLの設定をするだけです。ロギングを行う処理をプログラムの複数箇所に追加する必要がなくなります。

<?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="Foo">
        <aspect pointcut="foo">traceInterceptor</aspect>
    </component>
    <component class="Bar">
        <aspect pointcut="doSomething">traceInterceptor</aspect>
    </component>
</components>

設定ファイルの詳細は、S2AOPリファレンスの設定ファイルの説明を参照してください。

class Foo {
    privaye Bar bar = new Bar();
public void foo(){
bar.doSomething();
}
}
class Bar {
    private Baz baz = new Baz();
public void doSomething(){
baz.doSomething();
}
}

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

Advice(MethodInterceptor)

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

Joinpoint(MethodInvocation)

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

Pointcut

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

Aspect

AdviceとPointcutを関連付けます。

AOPのメリット

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

S2AOPのメリット

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

注意点

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

S2AOPリファレンス

作成すべきファイル

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

設定ファイルの説明

aspectタグ(AOPを使用する場合は必須)

アスペクトをコンポーネントに組み込みます。Interceptorの指定は、ボディでOGNL式を使うか、子タグでcomponentタグを使います。
※OGNLについては OGNLのマニュアルを参照してください。1つのコンポーネントに複数のアスペクトを組み込んだ場合はアスペクトの登録順に組み込まれ実行されます。詳しい説明は独自実装の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>

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プロパティ

        に委譲したい相手を指定します。委譲するときのメソッド名が異なる場合には、DeleateInterceptor#addMethodNameMap(String

        methodName, String targetMethodName)で指定します。

        例えば、bar()というメソッドをfoo.bar2()に委譲する場合は、以下のように指定します。

                DeleateInterceptor#setTarget(foo)

                DeleateInterceptor#addMethodNameMap("bar", "bar2")

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


(5) SyncInterceptor

クラス名

        org.seasar.framework.aop.interceptors.SyncInterceptor

説明

        メソッド呼び出しをAspectを使って同期化するためのInterceptorです。ソースを変更することなく、メソッド呼び出しを同期化でき

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


(6) 独自実装による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を作成したとします。

作成例

        MethodInvocation#proceed()を呼ぶ前と後で2分され、呼ぶ前は Beforeの個所を実行し、呼んだ後はAfterの個所を実行しま

        す。

        1つのコンポーネントに複数のアスペクトが定義されている場合は、以下のよう実行されます。

            1.Aspectの登録順にMethodInterceptorのbefore部分が実行されます。

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

            3.Aspectの登録の逆順にMethodInterceptorのafter部分が実行されます。

    

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


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引数にコンポーネントに登録したクラス名(ArrayList.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)を実装します。
    • handleThrowable()メソッドの戻り値をvoid(無し)にします。

    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(Throwable t, MethodInvocation invocation)
                throws Throwable {
            if (t instanceof NullPointerException) {
                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以下に用意されています。


    (3)ThrowsInterceptorを使って例外が発生した場合、もう一度処理を行うようにしましょう。

    数値をランダムで発生させて指定した数値では無い場合、例外を発生させて処理を続けるようにしましょう。作成するファイルは以下のとおりです。

    • ランダムで数値を発生させて指定した数値と同じか判定するクラス(RandomNumber.java)
    • ThrowsInterceptorを使って処理に失敗した場合、再実行(リトライ)するIntercptor(RetryExceptionInterceptor.java)
    • 設定を行うdiconファイル(RandomNumber.java)
    • 設定が正しく行われているか確認する実行ファイル(AopRandomClient.java)

  • ランダムで数字を発生させて指定した数値と同じか判定するクラスの作成
    • 引数に指定する数値を取るようにします。
    • ランダムで数値を発生させます。
    • 数値が同じ場合は処理成功、違う場合は失敗として例外を発生させる。

    RandomNumber.java

    package examples.aop.throwsinterceptor;
    
    public class RandomNumber {
        public void calc(int num) {
            int _num = (int)(Math.random() * 10 + 1);
    
            System.out.println(_num);
            if (_num == num) {
                System.out.println("success!");
            } else {
                throw new RuntimeException();                       
            }
        }
    }
  • 処理に失敗した場合、再実行(リトライ)するIntercptorを作成
    • ThrowsInterceptorを継承します。
    • handleThrowable(Throwable, MethodInvocation)を実装します。
    • もう一度コンテナからRandomNumberクラスを取得して実行します。

    RetryExceptionInterceptor

    package examples.aop.throwsinterceptor;
    
    import org.aopalliance.intercept.MethodInvocation;
    import org.seasar.framework.aop.interceptors.ThrowsInterceptor;
    import org.seasar.framework.container.S2Container;
    import org.seasar.framework.container.factory.S2ContainerFactory;
    
    public class RetryExceptionInterceptor extends ThrowsInterceptor {
        private static String PATH = "examples/aop/throwsinterceptor/Random.dicon"
    
        public void handleThrowable(Throwable t, MethodInvocation invocation)
                throws Throwable {
            S2Container container = S2ContainerFactory.create(PATH);
            RandomNumber randomNumber = (RandomNumber) container
                    .getComponent(RandomNumber.class);
            randomNumber.calc(5);
        }
    }
  • diconファイルの作成
    • 作成したRetryExceptionInterceptorをコンポーネント定義します。
    • 作成したRandomNumberクラスにRetryExceptionInterceptorをアスペクトします。

    Random.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="retryExceptionInterceptor" 
                   class="examples.aop.throwsinterceptor.RetryExceptionInterceptor"/>
        <component class="examples.aop.throwsinterceptor.RandomNumber">
            <aspect pointcut="calc">retryExceptionInterceptor</aspect>
        </component>
    </components>
  • 実行ファイルの作成
    • org.seasar.framework.container.S2Container#create()メソッドの第1引数に作成したdiconファイル(Random.dicon)のパスを指定してコンテナを作成します。
    • org.seasar.framework.container.S2Container#getComponent()メソッドの第1引数にコンポーネントに登録したクラス名(RandomNumber.class)を指定して取得します。
    • RandomNumber#calc()メソッドの引数に5を渡します。

    AopRandomClient.java

    package examples.aop.throwsinterceptor;
    
    import org.seasar.framework.container.S2Container;
    import org.seasar.framework.container.factory.S2ContainerFactory;
    
    public class AopRandomClient {
        private static String PATH = "examples/aop/throwsinterceptor/Random.dicon";
    	
        public static void main(String[] args) {
            S2Container container = S2ContainerFactory.create(PATH);
            RandomNumber randomNumber = (RandomNumber) container
                    .getComponent(RandomNumber.class);
            randomNumber.calc(5);
        }
    }
    
  • 実行結果
  • 指定した数値になるまで処理が繰り返されていることが確認できます。

    9
    6
    8
    2
    7
    2
    3
    5
    success!
    

    このサンプルは、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以下に用意されています。


    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以下に用意されています。