| S2AOPの概要 オブジェクト指向 オブジェクト指向の問題点 AOPを考える上でキーとなる概念 AOPのメリット S2AOPのメリット 注意点 S2AOPリファレンス 作成すべきファイル 設定ファイルの説明 S2AOPで用意されているInterceptor diconファイルを使用しないでアスペクトを組み込む方法 Example TraceInterceptor ThrowsInterceptor DelegateInterceptor SyncInterceptor 独自実装によるInterceptor ## 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インターフェースが１つです。 コンポーネントにどんなアスペクトが適用されるのかが明確です。 基本的な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()メソッドの１つだけです。 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つのコンポーネントに複数のアスペクトが定義されている場合は、以下のよう実行されます。 Aspectの登録順にMethodInterceptorのbefore部分が実行されます。 最後のMethodInterceptorのbefore部分を実行した後にコンポーネント自身のメソッドが呼び出されます。 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()メソッドの第１引数にコンポーネントに登録したクラス名（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()メソッドの第１引数に作成したdiconファイル(Checker.dicon)のパスを指定してコンテナを作成します。 org.seasar.framework.container.S2Container#getComponent()メソッドの第１引数にコンポーネントに登録したクラス名（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.java
    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()メソッドの第１引数に作成したdiconファイル(Random.dicon)のパスを指定してコンテナを作成します。 org.seasar.framework.container.S2Container#getComponent()メソッドの第１引数にコンポーネントに登録したクラス名（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()メソッドの第１引数にコンポーネントに登録したクラス名（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()メソッドの第１引数に作成したdiconファイル(SyncCalc.dicon)のパスを指定してコンテナを作成します。 org.seasar.framework.container.S2Container#getComponent()メソッドの第１引数にコンポーネントに登録したクラス名（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>
実行結果 １ずつ加算されていることから同期が取れていることが確認できます。
    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()メソッドの第１引数に作成したdiconファイル(Measurement.dicon)のパスを指定してコンテナを作成します。 org.seasar.framework.container.S2Container#getComponent()メソッドの第１引数にコンポーネントに登録したクラス名（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以下に用意されています。 |
| © Copyright The Seasar Project and the others 2004, all rights reserved.