アメリカではほとんどデフェクトスタンダードとなっている「」の覚書きです。Spring は簡単に言うと、IoC (制御の反転)、またの名を DI (依存性注入) という仕組みを取り入れた軽量コンテナです。

AOP(アスペクト指向プログラミング) はクラスの直接的な責務ではない、各モジュールから共通で使われる処理を、独立して切り出す手法です。「クラスの直接的な責務でない」とは、例えば「ログ」や「トランザクション」、「認証」などです。多くのクラスに重複コードが生まれてしまうような処理は、アスペクト(横断的関心事) として別のモジュールにしてしまうという手法をとることが出来ます。Spring AOP は、このアスペクトを扱うものです。 参考:『AspectJによるアスペクト指向プログラミング入門』 ソフトバンクパブリッシング   Springの詳細については、ほかにもっとよいサイト(Springフレームワークの紹介)があるので、そちらを参考にしてください。ここでは、Springを使っていて、ハマった点や気になった点などをメモしていこうと思います。随時更新していくつもりです。間違っている可能性が高いので、気になる点があればコメントをお願いします。

参考

Spring Pad - Wiki

JavaWorld 7月号 2004年

Spring AOP フレームワーク

Spring AOP 用語

Spring AOP で出てくる主要な用語まとめです。

アスペクト (Aspect)
共通の処理としてモジュールから呼び出されるもの。
インターセプト (Intercept)
メソッドの呼び出しタイミングで、振る舞いを挿入すること。
ジョインポイント (Joinpoint)
アスペクトコードが挿入できる位置を表したもの。例えば、メソッド呼び出しの前後などは、ジョインポイントになる。
アドバイス (Advice)
ジョインポイントに埋め込まれるアスペクトコードのこと。4つの種類がある。
Around Advice
ジョインポイントの前後で実行される。
Before Advice
ジョインポイントの前に実行される。
After Advice
ジョインポイントの後に実行される。2つの種類がある。
After returning Advice
ジョインポイントでの処理が正常に終了した後に実行される。
Throws Advice
ジョインポイントでの処理で例外が発生した場合に実行される。
ポイントカット (Pointcut)
ジョインポイントの集合のこと。条件にあうジョインポイントだけを抜き出すことも可能。
イントロダクション (Introduction)
アドバイスとしてメソッドやフィールドを挿入すること。ソースコード上で実装していないインターフェースなどを実行時に付け加えることが出来るので、デバッグは難しくなる。
ウィーブ (Weave)

アスペクトをジョインポイントやポイントカットに織り込むこと。

[静的ウィーブと動的ウィーブ]

静的ウィーブとは、コンパイル時にアスペクトを織り込むこと。静的ウィーブを提供している実行環境には、「AspectJ」などがある。逆に、動的時にウィーブするには、プロキシを使って実行コードを隠蔽してやる必要があります。

アスペクト用語を図にするとこんな感じでしょうか?

アスペクト指向イメージ図

Spring AOP とは

Spring AOP はイントロダクションは使えます。

Spring AOP は、アスペクトを織り込む方法として、インターセプトを使います。処理の流れはこんな感じです。

  1. Spring は実行時に、アスペクト対象クラスのプロキシオブジェクトを生成します。
  2. そして、プロキシオブジェクトの内部で、アスペクト対象クラスの処理を実行します。
  3. ジョインポイントとして、アスペクト対象クラスが指定されると、プロキシの中でインターセプトクラスのアドバイスメソッドが呼ばれます。

Spring AOP では、アドバイスを一緒に扱う「アドバイザー (Advisor)」というものを使います。任意のポイントカットとアドバイスを組み合わせて使います。

Spring AOP で使われる主なクラス / インターフェース

Spring AOP を利用する場合に使われる主なクラスやインターフェースです。

org.springframework.aop.framework.ProxyFactoryBean

AOP Proxy 戦略を行うための FactoryBean です。簡単に言うと、Spring で AOP を行うときに使う、FactoryBean です。

【設定する項目】

interceptorNames
インターセプトするクラスを設定します。org.aopalliance.intercept.MethodInterceptor を実装したクラス参照の ID を指定します。リスト値を取ることが出来るので、クラス名の並び順を考える必要があります。(指定した順に適用される)
target
アスペクト対象の Bean 参照(ref bean)を指定します。
proxyInterfaces
プロキシに使用するクラス名の配列を指定します。特に指定のない場合は、書かなくても問題ありません。

org.springframework.aop.Pointcut

ポイントカットはアドバイスの挿入地点であるジョインポイントの部分集合です。クラス名やメソッド名のマッチング条件を指定することにより、ジョインポイントの集合を表すことが出来ます。

org.springframework.aop.MethodMatcher

ポイントカットの一部です。呼び出されたメソッドがポイントカットの集合に含まれるかどうかを判定します。静的な呼び出しである場合には、引数が2つの matches メソッドが呼び出されます。動的な呼び出しである場合には、引数が3つの matches メソッドが呼び出されます。

Spring AOP では、ポイントカットは Union (和集合) と Intersection (交差) で表せます。 org.springframework.aop.support.Pointcuts クラスを使います。

【主な MethodMatcher】

TrueMethodMatcher
すべてのメソッドにマッチするマッチャーです。
NameMatchMethodPointcut
メソッド名を正規表現を使って表せるマッチャーです。
RegexpMethodPointcutAdvisor
正規表現を使ってポイントカットを表せるアドバイザーです。

org.aopalliance.aop.Advice

アドバイスはポイントカットに挿入する処理を定義するインターフェースです。Spring では「Around Advice」、「Before Advice」、「Throws Advice」、「After returning Advice」の 4 種類をサポートしています。

org.aopalliance.intercept.MethodInterceptor

Around Advice を実装するためのインターフェースです。Around Advice は アドバイスの処理が、アスペクト対象の処理の前後に及ぶため、自分で Advice のタイミングを実装しなければなりません。Advice の処理の中で、 invocation.proceed() を呼び出す必要があります。このインターフェースの実装の仕方によっては、4種類全部の Advice を表現できます。

org.springframework.aop.MethodBeforeAdvice
Before Advice を実装するためのインターフェースです。before() メソッド中にアドバイスコードを実装します。
org.springframework.aop.ThrowsAdvice

Throws Advice を実装するためのインターフェースです。インターフェースで定義されたメソッドはありません。

public void afterThrowing(java.lang.refrect.Method m, Object[] args, Object target, Throwable subclass)

の形式でメソッドを定義しておくと、SpringFramework によって呼ばれます。

第4引数の Throwable は、キャッチしたい例外クラスを指定することで、例外にマッチするときだけ呼ばれるようになります。

org.springframework.aop.AfterReturningAdvice
After returning Advice を実装するためのインターフェースです。処理が正常に終了した場合に呼び出されます。

org.springframework.aop.Advisor

アドバイスを一緒に扱うインターフェースです。Spring の世界では、おそらく「インターセプト = アドバイス」と言えると思います。

ProxyFactoryBean を使ったサンプルコード

ProxyFactoryBean の使い方サンプルです。Bean 定義書に ProxyFactoryBean を使う指定をして、その ID に プロキシでラップした Bean を取り出すときの名前を付ける だけです。

bean-context.xml の例

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="calcTarget" 
      class="jp.dip.xlegend.spring.aop.Calculator"/>   
 
  <bean id="debugInterceptor" 
      class="org.springframework.aop.interceptor.DebugInterceptor"/>
 
  <bean id="calc"
       class="org.springframework.aop.framework.ProxyFactoryBean">
      <property name="target">
        <ref bean="calcTarget"/>
      </property>
      <property name="interceptorNames">
      <list>
        <value>debugInterceptor</value>
      </list>
    </property>
  </bean>
</beans>

「ProxyFactoryBean」の定義のところの ID を使って Bean を取り出しています。

メインクラス

public static void main(String[] args) throws Exception {
  BeanFactory factory = 
      new XmlBeanFactory(new FileInputStream("bean-context.xml"));
  Calculator cal = (Calculator)factory.getBean("calc");
  cal.add(10, 20);
} 

アスペクト対象の Calculator クラスです。アスペクト対象のクラスは、JavaBeanでなければなりません。デフォルトコンストラクタ(引数無しのコンストラクタ)が無いと、プロキシを作る時にエラーになります。

アスペクト対象のクラス

public class Calculator {    
  public Calculator() {
    super();
  }
 
  public int add(int a, int b) {      
    System.out.println("add(" + a + ", " + b + ")");
    return a + b;
  }
}

実行結果です。

Debug interceptor: count=1 invocation= ...
add(10, 20)
Debug interceptor: next returned

Advisor (アドバイザー)とは?

Advisor (アドバイザー)とは、アドバイスをセットにしたものと考えればよさそうです。アドバイスとはアスペクトとして付け加える処理のことです。ポイントカットはアドバイスを付け加える位置のことです。つまり、アドバイザーとは、自身がアドバイスであり、ポイントカットであるわけです。

アドバイザーの例

<?xml version="." encoding="UTF-"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
  "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
  <bean id="calcTarget"
      class="jp.dip.xlegend.spring.aop.Calculator"/>
 
   <bean id="debugInterceptor"
      class="org.springframework.aop.interceptor.DebugInterceptor"/>
 
   <bean id="advisor"
class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
    <property name="advice">
      <ref bean="debugInterceptor"/>
    </property>
    <property name="mappedNames">
      <list>
        <value>insert*</value>
        <value>update*</value>
      </list>
    </property>
  </bean>
 
  <bean id="calc"
       class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target">
      <ref bean="calcTarget"/>
    </property>
    <property name="interceptorNames">
      <list>
        <value>advisor</value>
      </list>
    </property>
  </bean>
</beans>

11行目でアドバイザーを定義しています。アドバイザーは「ポイントカット」と「アドバイス」の組み合わせでした。13〜15行目で「アドバイス」を設定しています。インターセプタはアドバイスだということですね。16〜21行目で「ポイントカット」を正規表現で指定しています。最後に ProxyFactoryBean の interseptorNames に 定義したアドバイザーを指定して、アスペクト対象を設定すれば、AOP の出来上がりです。

ハマった点

BeanCreationException が発生する

取得しようとしている Bean に 「デフォルトコンストラクタがない」と、このエラーが発生することがあります。基本的に Spring の Bean 定義ファイルに書くクラスには、「デフォルトコンストラクタ」を書いておきましょう。案外忘れがち・・・

Spring の ポイントカットは、クラス単位

Bean 定義書を見ると分かるのですが、ProxyFactoryBean の target には単一のクラスしか書けません。ポイントカットに関しても、ターゲットののメソッドしかサポートしません。

トランザクション管理などで、複数のクラスを指定したい場合には、「AutoProxy」 と呼ばれる仕組みを使う必要があるとのことです。そのうち使ってみようかと思います。

参考

  • Spring の ロッドジョンソンが贈る、J2EE技術者のためのバイブル
  • Spring のロッドジョンソンによる Spring ユーザのための本 (洋書)
  • SpringでWebアプリケーションを作りながら、Springの全体像がわかりやすく解説されています。