連載
» 2005年07月16日 00時00分 公開

Seaser Projectの全貌を探る(2):DI+AOPを実現するSeasarV2 (3/3)

[沖林正紀,@IT]
前のページへ 1|2|3       

S2におけるAOP(S2AOP)

 続いて、S2によるAOPをどのようにして行えばよいかについて見ていきましょう。

 AOPを行うツールには、AspectJSpringなどがありますが、アスペクトをどのようにしてウィービングするかによって、大きく2つのタイプに分けることができるでしょう。1つは、AspectJのようにアスペクトを別のクラスとして作成しておいて、ウィービングを一括して行うタイプです。そしてもう1つは、アスペクトをインターセプタ(Interceptor)として作成しておき、DIコンテナでインスタンスを生成する際にこれをウィービングするタイプです。S2やSpringはこちらのタイプに分けることができます。

 S2では、よく利用されると思われるアスペクトがあらかじめ既製のインターセプタとして用意されています。その一覧を表1に掲げます。S2におけるインターセプタを用いたアスペクトは、基本的に本質的関心事のメソッドを実行する前後に割り込ませて実行させるAroundアドバイスに相当します。これらのインターセプタの使用法はSeasarプロジェクトのドキュメント「S2AOPで用意されているInterceptor」(http://www.seasar.org/aop.html#AOPInterceptor)を参照してください。

 独自にアスペクトを実装する場合も、org.aopalliance.intercept.MethodInterceptorインターフェイスを実装するか、すでにこのインターフェイスを実装済みの抽象クラスorg.seasar.framework.aop.interceptors.AbstractInterceptorを継承するかのどちらかで行います。この場合は、実装の方法によってBeforeアドバイスにしたり、Afterアドバイスにしたりすることは可能です。

表1 S2で用意されているインターセプタ
(すべてorg.seasar.framework.aop.interceptorsパッケージ)
クラス名 働き
TraceInterceptor メソッドの実行をログに出力するトレース処理を行う
ThrowsInterceptor これを継承して例外発生時の処理を記述する
TraceThrowsInterceptor 例外の発生をトレースする
MockInterceptor オブジェクトをモックとして振る舞わせる(テスト用)
DelegateInterceptor ほかのクラスのメソッドに処理を委譲する
PrototypeDelegateInterceptor S2コンテナからインスタンスを取得して、そのインスタンスのメソッドに処理を委譲する
SyncInterceptor メソッド呼び出しを同期させる
InterceptorChain 複数のインターセプタをネストさせて実行する

既製のアスペクトの利用

 表1に掲げた既製のアスペクトの中から、ThrowsInterceptorを継承して例外処理を行うアスペクトを実装する方法を紹介します。サンプルとして用いるのは、平均値を求めるアプリケーションです。そのソースコードをリスト6、diconファイルをリスト7に示します。このとき、もし平均値を求める際に、計算の基になる引数がまったく設定されていなかったらメッセージを表示するアスペクトを実装してみましょう。

 まず、きちんと引数を設定してこのアプリケーションを実行した結果を以下に示します。

実行結果

>java -cp .;%CLASSPATH% myfirst.aop.AverageWithAOP myfirst\aop\average.dicon
引数の値 11.110000 22.220000 33.330000
平均値は 22.220000 です。


 続いて引数を設定しないでこのアプリケーションを実行した結果を以下に示します。

実行結果

>java -cp .;%CLASSPATH% myfirst.aop.AverageWithAOP myfirst\aop\average.dicon
java.lang.NullPointerException <- アスペクトにより出力された部分
getAverageメソッドで平均値を計算できませんでした <- アスペクトにより出力された部分
平均値は 0.000000 です。


 実行結果の最初の2行が、これから作成するアスペクトにより出力されたメッセージです。平均値を求めようにも引数が設定されていませんので、NullPointerExceptionが発生します。それをインターセプタで受け取って「getAverageメソッドで平均値を計算できませんでした」というメッセージを表示しているわけです。

 この処理を行うインターセプタをリスト8のように作成しました。ThrowsInterceptorを継承して例外処理を行うアスペクトを作成する場合は、必ずhandleThrowableというメソッドを実装することになっています。この部分に例外が発生した場合の処理を記述します。

 リスト8では、このメソッドの第1引数をThrowableにしていますので、すべての例外をこのメソッドで受け付けて処理することになりますが、この引数を別のExceptionクラスに変更すれば、その例外(とそのサブクラス)のみを受け付けるメソッドにすることができます。複数の種類の例外を受け付けるようにしたい場合は、それぞれの例外に応じたhandleThrowableメソッドを複数実装することで、それに対応できるようになっています。

 diconファイルでアスペクトの指定をするには、<component>〜</component>間に<aspect>タグを記述します。pointcut属性にポイントとカットとなるメソッド名を記述します。そして<aspect>〜</aspect>間には、事前にインターセプタをコンポーネントとして記述したときの名称(name属性の値)か、<component>タグ自体を記述します。これによりコンポーネントにアスペクトが設定されます。なお、1つのコンポーネントに複数のアスペクトを設定する場合は<aspect>タグが複数記述されることになります。

リスト6 平均値を求めるアプリケーション [AverageImpl.java]
package myfirst.aop;

public class AverageImpl implements Average  {

  Double[] values;

  public void     setValues( Double[] v )  {  values = v;     }
  public Double[] getValues()              {  return values;  }

  public double getAverage()  {
    double sum = 0;
    for( double v : values )  {  sum += v;  }
    return sum / values.length;
  }

}

リスト7 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="noArgsInterceptor" class=
"myfirst.aop.NoArgsInterceptor" />
  <!-- 引数が指定されなかったときの処理を行うインターセプタ -->
  <component name="average" class="myfirst.aop.AverageImpl">
    <initMethod name="setValues">
      <arg>new Double[] { 11.11, 22.22, 33.33 }</arg><!--
 引数を配列で指定(OGNL式) -->
    </initMethod>
    <!-- アスペクトとしてウィービングするインターセプタを指定 -->
    <!-- コンポーネントの名称を指定するときは引用符を付けない -->
    <aspect pointcut="getAverage">noArgsInterceptor</aspect>
  </component>
</components>

リスト8 ThrowsInterceptorを継承して作成したインターセプタ [noArgsInterceptor.java]
package myfirst.aop;

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

public class NoArgsInterceptor extends ThrowsInterceptor {
  // ThrowsInterceptorクラスを継承する場合は必ずこのメソッドを実装する
  // このメソッドは例外の種類に応じて複数実装することができる
  public void handleThrowable( Throwable t, MethodInvocation mi )
         throws Throwable {
    System.out.println( t.toString() );
    System.out.println( mi.getMethod().getName() + "メソッドで平均値を計算できませんでした" );
  }
}

独自のアスペクトを作成する

 今度は、既製のものではない独自のアスペクトを作成してみましょう。作成するのは、引数の最大値を求めるアスペクトです。ここではAbstractInterceptorを継承して作成する例(リスト9)を紹介します。

 リスト9の場合でも、MethodInterceptorインターフェイスを実装する場合でも、アスペクトとしての処理は必ずinvokeメソッドに実装します。そしてその内部では、ポイントカットとして指定されたメソッドをproceedメソッドで実行し、その結果を返す、というのが基本的な流れです。

 proceedメソッドの前に何らかの処理を実行するものはBeforeアドバイスとなり、このメソッドの後で何らかの処理を実行するものはAfterアドバイスになります。proceedメソッドの前後どちらとも何らかの処理が行われるものはAroundアドバイスとなります。リスト9の場合は先にproceedメソッドが実行されていますので、これはAfterアドバイスということになります。

 これをアスペクトとしてリスト7に追加したものがリスト10です。アスペクト自身の<component>タグと、それをアスペクトに指定する<aspect>タグを確認してみてください。

リスト9 AbstractInterceptorを継承して作成したアスペクト(引数の最大値を求める) [MaxValueIntercepter.java]
package myfirst.aop;

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

// 独自にアスペクトを作成 - AbstractInterceptorを継承した例
public class MaxValueInterceptor extends AbstractInterceptor  {
  // アスペクトとしての処理をこのメソッドに記述する
  public Object invoke( MethodInvocation mi ) throws Throwable  {
    Object result = mi.proceed();   // ポイントカットのメソッドを実行する
    double max = Double.MIN_VALUE;
    Double[] values = ((Average)mi.getThis()).getValues();  // 引数の値を取得
    if ( values != null )  {
      System.out.print( "引数の値" );
      for( double v : values )  {       
        System.out.printf( "  %f", v );   // 引数の値を表示
        if ( max < v )  max = v;          // 最大値を求める
      }
    }
    System.out.printf( "\n引数のうち最大値は %f です。\n", max );
    return result;  // proceedメソッドの実行結果を返す
  }
}

リスト10 diconファイルにリスト9のアスペクトを追加したもの [average.dicon]
<components>
  <!-- 最大値を求めるインターセプタ -->
  <component name="maxValueInterceptor" class=
"myfirst.aop.MaxValueInterceptor" />
  <!-- 引数が指定されなかったときの処理を行うインターセプタ -->
  <component name="noArgsInterceptor" class=
"myfirst.aop.NoArgsInterceptor" />
  <component name="average" class="myfirst.aop.AverageImpl">
    <initMethod name="setValues">
      <arg>new Double[] { 11.11, 22.22, 33.33 }</arg><!-- 
引数を配列で指定(OGNL式) -->
    </initMethod>
    <!-- アスペクトとしてウィービングするインターセプタを指定 -->
    <aspect pointcut="getAverage">noArgsInterceptor</aspect>
    <aspect pointcut="setValues">maxValueInterceptor</aspect>
  </component>
</components>

 今回はS2におけるDIとAOPについて見てきました。次回は、S2におけるデータベース関連の機能を見ていきます。


前のページへ 1|2|3       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。