4.5. S2AOPで用意されているInterceptor

4.5.1. TraceInterceptor

S2ApplicationContextを使用する. 

トレース処理を「Crosscutting Concern」として扱うためのInterceptorです。
例として、次のようなサービスクラスにアスペクトしてみます。

class Service {
    public function add($a, $b) {
        print __CLASS__ . ' called.' . PHP_EOL;
        return $a + $b;
    }
}

次のような実行スクリプトを作成します。

  • S2ApplicationContext::importメソッドでServiceクラスをインポートします。
  • S2ApplicationContext::registerAspectメソッドでサービスコンポーネントにTraceInterceptorをアスペクトする設定とします。
  • S2ApplicationContext::createメソッドでコンテナを生成します。
  • S2ContainerのgetComponentメソッドでServiceコンポーネントを取得します。
  • Serviceコンポーネントのaddメソッドを実行します。
<?php
require_once('S2Container/S2Container.php');
seasar\container\S2ApplicationContext::import(dirname(__FILE__) . '/classes');
seasar\container\S2ApplicationContext::registerAspect('new seasar\aop\interceptor\TraceInterceptor', '/^Service$/');
$container = seasar\container\S2ApplicationContext::create();
$service   = $container->getComponent('Service');

print get_class($service) . PHP_EOL;
$result = $service->add(2, 3);

上記スクリプトを実行すると、サービスクラスのaddメソッドの実行時TraceInterceptorがログを出力します。

% php context.php
Service_EnhancedByS2AOP
[INFO  ] seasar\aop\interceptor\TraceInterceptor::invoke - BEGIN Service->add(2,3)
Service called.
[INFO  ] seasar\aop\interceptor\TraceInterceptor::invoke - END   Service->add(2,3) : 5 : 0.0012331008911133
%

S2ContainerFactoryを使用する. 

S2ContainerFactoryを使用する場合は、次のようなダイコンファイルを作成します。aspectタグを用いてServiceコンポーネントにTraceInterceptorをアスペクトします。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR2.1//DTD S2Container//EN"
"http://www.seasar.org/dtd/components21.dtd">
<components>
    <component name="trace" class="seasar\aop\interceptor\TraceInterceptor"/>
    <component class="Service">
        <aspect>trace</aspect>
    </component>
</components>

次のような実行スクリプトを作成します。

  • ClassLoader::importメソッドでServiceクラスをインポートします。
  • S2ContainerFactory::createメソッドでdiconファイルを読み込みます。
  • S2ContainerのgetComponentメソッドでServiceコンポーネントを取得します。
  • Serviceコンポーネントのaddメソッドを実行します。
<?php
require_once('S2Container/S2Container.php');
seasar\util\ClassLoader::import(dirname(__FILE__) . '/classes');
$container = seasar\container\factory\S2ContainerFactory::create(dirname(__FILE__) . '/example.dicon');
$service = $container->getComponent('Service');

print get_class($service) . PHP_EOL;
$result = $service->add(2, 3);

上記スクリプトを実行すると、サービスクラスのaddメソッドの実行時TraceInterceptorがログを出力します。

% php factory.php
Service_EnhancedByS2AOP
[INFO  ] seasar\aop\interceptor\TraceInterceptor::invoke - BEGIN Service->add(2,3)
Service called.
[INFO  ] seasar\aop\interceptor\TraceInterceptor::invoke - END   Service->add(2,3) : 5 : 0.0012331008911133
%
[注意]NOTE

このExampleは examples/aop/interceptor/trace にあります。


4.5.2. MockInterceptor

S2ApplicationContextを使用する. 

MockInterceptorは、Mockを使ったテストを簡単に行うためのInterceptorです。S2ApplicationContextを使用する場合は、 コメントアノテーションを用いて、メソッドの戻り値やスローする例外を設定します。

  • アノテーションの表記 : @S2Mock
  • 引数
    • return : 戻り値を設定します。この値はExpressionとして扱われます。(eval関数で処理されます)
    • throw : スローする例外を設定します。この値はExpressionとして扱われます。
  • 注釈ポイント : メソッド

例として、次のようなサービスクラスにアスペクトしてみます。

class Service {
    /**
     * @S2Mock('return' => '10')
     */
    public function add($a, $b) {
        print __CLASS__ . ' called.' . PHP_EOL;
        return $a + $b;
    }
    /**
     * @S2Mock('throw' => 'new seasar\exception\NotYetImplementedException("mock exception")')
     */
    public function sub($a, $b) {
        print __CLASS__ . ' called.' . PHP_EOL;
    }
}

次のような実行スクリプトを作成します。

  • S2ApplicationContext::importメソッドでServiceクラスをインポートします。
  • S2ApplicationContext::registerAspectメソッドでサービスコンポーネントにMockInterceptorをアスペクトする設定とします。
  • S2ApplicationContext::createメソッドでコンテナを生成します。
  • S2ContainerのgetComponentメソッドでServiceコンポーネントを取得します。
  • Serviceコンポーネントのadd、subメソッドを実行します。
<?php
require_once('S2Container/S2Container.php');
seasar\container\S2ApplicationContext::import(dirname(__FILE__) . '/classes');
seasar\container\S2ApplicationContext::registerAspect('new seasar\aop\interceptor\MockInterceptor', '/^Service$/');
$container = seasar\container\S2ApplicationContext::create();
$service = $container->getComponent('Service');

print $service->add(2, 3) . PHP_EOL;
try {
    $result = $service->sub(3, 2);
} catch(Exception $e) {
    print get_class($e) . ' : ' . $e->getMessage() . PHP_EOL;
}

上記スクリプトを実行します。サービスクラスのaddメソッドを実行すると、実際の計算結果(5)ではなくMock値として10が返されます。 subメソッドを実行すると、NotYetImplementedExceptionがスローされます。

% php context.php
10
seasar\exception\NotYetImplementedException : mock exception
%

S2ContainerFactoryを使用する. 

S2ContainerFactoryを使用する場合は、次のようなダイコンファイルを作成します。aspectタグを用いてServiceコンポーネントにMockInterceptorをアスペクトします。 initMethodタグで、addメソッドに対してsetRetrunValueメソッドで戻り値を設定します。また、subメソッドに対してはsetThrowableメソッドでスローする例外を設定します。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR2.1//DTD S2Container//EN"
"http://www.seasar.org/dtd/components21.dtd">
<components>
    <component name="mock" class="seasar\aop\interceptor\MockInterceptor">
        <initMethod name="setReturnValue">
            <arg>"add"</arg>
            <arg>20</arg>
        </initMethod>
        <initMethod name="setThrowable">
            <arg>"sub"</arg>
            <arg>new seasar\exception\NotYetImplementedException('mock exception')</arg>
        </initMethod>
    </component>

    <component class="Service">
        <aspect>mock</aspect>
    </component>
</components>

Mock値の設定が省略された場合は、コメントアノテーションの設定が反映されます。

次のような実行スクリプトを作成します。

  • ClassLoader::importメソッドでServiceクラスをインポートします。
  • S2ContainerFactory::createメソッドでdiconファイルを読み込みます。
  • S2ContainerのgetComponentメソッドでServiceコンポーネントを取得します。
  • Serviceコンポーネントのadd、subメソッドを実行します。
<?php
require_once('S2Container/S2Container.php');
seasar\util\ClassLoader::import(dirname(__FILE__) . '/classes');
$container = seasar\container\factory\S2ContainerFactory::create(dirname(__FILE__) . '/example.dicon');
$service = $container->getComponent('Service');

print $service->add(2, 3) . PHP_EOL;
try {
    $result = $service->sub(3, 2);
} catch(Exception $e) {
    print get_class($e) . ' : ' . $e->getMessage() . PHP_EOL;
}

上記スクリプトを実行します。サービスクラスのaddメソッドを実行すると、実際の計算結果(5)ではなくMock値として20が返されます。 subメソッドを実行すると、NotYetImplementedExceptionがスローされます。

% php factory.php
20
seasar\exception\NotYetImplementedException : mock exception
%
[注意]NOTE

このExampleは examples/aop/interceptor/mock にあります。


4.5.3. InterceptorChain

複数のInterceptorをグルーピング化し再利用しやすくします。
例として、次のようなサービスクラスにTraceInterceptorとMockInterceptorをInterceptorChainにまとめてアスペクトしてみます。

class Service {
    public function add($a, $b) {
        print __CLASS__ . ' called.' . PHP_EOL;
        return $a + $b;
    }
}

次のようなダイコンファイルを作成します。aspectタグを用いてServiceコンポーネントにchainコンポーネントをアスペクトします。 chainコンポーネントでは、initMethodタグでtraceコンポーネントとmockコンポーネントをInterceptorChainに登録します。 また、traceコンポーネントとmockコンポーネントもそれぞれ登録します。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR2.1//DTD S2Container//EN"
"http://www.seasar.org/dtd/components21.dtd">
<components>
    <component class="Service">
        <aspect>chain</aspect>
    </component>

    <component name="chain" class="seasar\aop\interceptor\InterceptorChain">
        <initMethod name="add"><arg>trace</arg></initMethod>
        <initMethod name="add"><arg>mock</arg></initMethod>
    </component>

    <component name="trace" class="seasar\aop\interceptor\TraceInterceptor"/>
    <component name="mock" class="seasar\aop\interceptor\MockInterceptor">
        <initMethod name="setReturnValue">
            <arg>"add"</arg>
            <arg>20</arg>
        </initMethod>
    </component>
</components>

次のような実行スクリプトを作成します。

  • ClassLoader::importメソッドでServiceクラスをインポートします。
  • S2ContainerFactory::createメソッドでdiconファイルを読み込みます。
  • S2ContainerのgetComponentメソッドでServiceコンポーネントを取得します。
  • Serviceコンポーネントのaddメソッドを実行します。
<?php
require_once('S2Container/S2Container.php');
seasar\util\ClassLoader::import(dirname(__FILE__) . '/classes');
$container = seasar\container\factory\S2ContainerFactory::create(dirname(__FILE__) . '/example.dicon');
$service = $container->getComponent('Service');

print $service->add(2, 3) . PHP_EOL;

上記スクリプトを実行します。サービスクラスのaddメソッドを実行すると、TraceInterceptorのログが出力されます。 また、実際の計算結果(5)ではなくMock値として20が返されます。

% php factory.php
[INFO  ] seasar\aop\interceptor\TraceInterceptor::invoke - BEGIN Service->add(2,3)
[INFO  ] seasar\aop\interceptor\TraceInterceptor::invoke - END   Service->add(2,3) : 5 : 0.0012331008911133
20
%
[注意]NOTE

このExampleは examples/aop/interceptor/chain にあります。


4.5.4. Interceptorを実装する

Interceptorをカスタム実装する場合は、次のインターフェースを実装するクラスを作成します。

  • seasar\aop\MethodInterceptor
    namespace seasar\aop;
    interface MethodInterceptor {
        /**
         * @param MethodInvocation $invocation
         */
        function invoke(MethodInvocation $invocation);
    }
    

次のようなサンプルインターセプターを作成してみます。

class SampleInterceptor implements seasar\aop\MethodInterceptor {
    public function invoke(seasar\aop\MethodInvocation $invocation){

        print 'Before' . PHP_EOL;            // <-- 次のインターセプターや実際のメソッドを呼び出す前の処理

        $result = $invocation->proceed();

        print 'After' . PHP_EOL;             // <-- 次のインターセプターや実際のメソッドを呼び出した後の処理

        return $result;
    }
}

MethodInvocation::proceed()を実行すると、次のインターセプターや実際のメソッドを呼び出します。 1つのコンポーネントに複数のアスペクトが定義されている場合は、以下のよう実行されます。

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

MethodInterceptor::invokeメソッドの引数で渡されるMethodInvocationインスタンスを介して、アスペクト対象のインスタンスや、ReflectionMethod、メソッド引数などを取得できます。



© Copyright The Seasar Foundation and the others 2005-2010, all rights reserved.