Google
 
Web andore.com
Powered by SmartDoc

6. Spring AOP:Springでのアスペクト指向プログラミング(Ver 1.2.7)

6.1 概念

アスペクト指向プログラミング(AOP)は、プログラム構造についての別の考え方を提供することによってOOPを補完する。OOがアプリケーションをオブジェクト階層へ分解するのに対し、AOPはプログラムをアスペクトあるいは関係に分解する。これは別の観点で複数オブジェクトに跨るトランザクション管理のような関係性をモジュール化することが可能となる(このような関心のことを横断的関心という用語で表すこともある)。

Springの重要なコンポネントの1つがAOPフレームワークだ。SpringのIoCコンテナ(BeanFactoryApplicationContext)はAOPには依存せず、もしAOPを使いたくなければ使わなくてもいいということだが、AOPはとても便利なミドルウェアソリューションを提供するためにSpringのIoCを補完する。

AOPはSpringで用いられているのはこれ:

したがって、SpringのAOPはSpringでEJBを使わずに宣言的トランザクション管理ができるようにするための技術か、あるいは独自のアスペクトを実装するためのSpringのAOPフレームワークのように見える。

もし、総称的な宣言的サービス、あるいはプーリングのようなその他のパッケージ化された宣言的ミドルウェアサービスだけに関心があるのであれば、SpringのAOPを直接使う必要はないので、本章は読み飛ばしてもらって差し支えないだろう。

6.1.1 AOPの概念

それでは、いくつかの中心てきなAOPの概念の定義から始めよう。この用語はSpringオリジナルなものではない。残念なことに、AOP用語は特に直感的でもない。でもSpringで独自の用語を使えばもっと混乱することになるだろう。

adviceの型には以下のようなものがある。

Around adviceは最も一般的な種類のadviceだ。インターセプションベースのAOPフレームワークのほとんど、例えばNanning Aspectsはaround adviceしかサポートしない。

Springでは、AspectJのように、全種類の型のadviceをサポートしており、望む動作を実装できるものの中で最も非力なadviceを使うようお勧めする。例えば、メソッドの返り値でキャッシュを更新する必要があるだけであれば、around adviceよりもafter returning adviceを実装した方がいい。around adviceでも同じことが実現できるけれども。最も限定的なadviceの型を使えば、プログラミングモデルを単純化し、潜在的なエラーを減らす。例えば、around adviceで用いられるMethodInvocationproceed()メソッドを起動する必要がないので、起動に失敗する、ということがないのだ。

このポイントカットの概念は、AOPの鍵であり、AOPを他の古いインターセプトを行うための技術と区別している。ポイントカットはadviceがOO階層と無関係に対象とされるadviceを実現可能にする。例えば、宣言的トランザクション管理を提供するaround adviceは、多数のオブジェクトに跨る一連のメソッドに適用することができる。したがってポイントカットはAOPの構造的な要素を提供するのだ。

6.1.2 SpringのAOP対応

SpringのAOPはPure Javaで実装されており、特別なコンパイルプロセスは必要としない。SpringのAOPはクラスローダ階層をコントロールする必要がなく、したがって、J2EE Webコンテナやアプリケーションサーバで使うのに向いているのだ。

Springでは現状、メソッド起動のインタセプションをサポートしている。コアのSpring AOP APIを壊すことなくフィールドインタセプションを追加することはできるが、フィールドインタセプションはまだ実装されていない。

フィールドインタセプションは恐らくOOのカプセル化を破ってしまう。我々はアプリケーション開発においてこれが賢いことだとは思わない。もし、フィールドインタセプションが必要なのであれば、AspectJを使うことを考えるべきだろう。

Springでは、ポイントカットや異なるadvice型を表すためのクラスが提供されている。Springではadvisorという用語をaspectを表すオブジェクトに用いており、これには、adviceと特定のジョインポイントを対象にしたポイントカットが含まれている。

異なるadvice型は(AOPアライアンス インタセプションAPIからの)MethodInterceptorと、org.springframework.aopパッケージに定義されたadviceインタフェースだ。全てのadviceはorg.aopalliance.aop.Advicetagインタフェースを実装していなければならない。adviceがサポートしているのは、MethodIntercetorThrowsAdviceBeforeAdviceAfterReturningAdviceだ。以下では、adviceの型について詳細に見ていくこととしよう。

SpringではAOPアライアンスのインタセプションインタフェース(http://www.sourceforge.net/projects/aopalliance)を実装している。around adviceはAOPアライアンスのorg.aopalliance.intercept.MethodInterceptorインタフェースを実装しなければならない。このインタフェースの実装はSpringやその他のAOPアライアンス準拠の実装で行うことができる。現状では、JACがAOPアライアンスのインタフェースを実装しており、NanningやDyaopは2004年の早い時期に実装するようだ。

AOPにおけるSpringのアプローチは他のほとんどのAOPフレームワークとは異なるものだ。目標は、ほとんど完全なAOPの実装を提供することではない(SpringのAOPはちゃんと動くが)。それよりもエンタープライズアプリケーションに共通の問題を解決するのを支援するためにAOPの実装とSpring IoCを密接に統合することにある。

したがって、例えば、SpringのAOP機能は普通Spring IoCコンテナと一緒に使われ、AOPのadviceは通常のビーン定義の文法を用いて指定する(これにより、強力な「autoproxy」が可能だが)adviceやポイントカットはそれ自身SpringのIoCに管理されるという点が、他のAOP実装との一番の違いだ。とても粒度の細かいオブジェクトへのadviceのように、SpringのAOPでは、簡単に、もしくは効果的に実現できないことがいくつかある。AspectJはおそらくこのようなケースの場合、最適な選択だと思う。しかしながら、我々の経験では、SpringAOPではAOPで扱いやすいJ2EEアプリケーションでの問題のほとんどに対して素晴らしいソリューションを提供する。

Spring AOPでは包括的なAOPソリューションを提供するためにAspectJやAspectWerkzと張り合うつもりはまったくない。我々は、Springのようなプロキシベースのフレームワークと、AspectJのような本格的なフレームワークのどちらにも価値があり、競争するのではなく、お互いに補完しあえると考えている。従って、Spring1.1での一番の優先事項は、Spring AOPとIoCをAspectJにシームレスに統合して、誰もが一貫したSpringベースのアプリケーションアーキテクチャでAOPを使えるようにすることだ。この統合は、Spring AOPのAPIやAOPアライアンスのPIにはなんら影響を及ぼさないと思う。Spring AOPは後方互換性を維持するつもりだ。

6.1.3 SpringのAOPプロキシ

SpringではデフォルトでJ2SEのダイナミックプロキシをAOPプロキシに使っている。これにより、任意のインタフェースやインタフェース群のプロキシができるようになるのだ。

SpringではCGLIBプロキシも使うことができる。これは、インタフェースよりもプロキシクラスに必要なものだ。CGLIBは、ビジネスオブジェクトがインタフェースを実装しない場合デフォルトで用いられる。これは、クラスではなくインタフェースでプログラムする方がいいやり方なので、ビジネスオブジェクトは通常1つあるいは複数のビジネスインタフェースを実装するだろう。

CGLIBを無理やり使うことは可能だ。以下ではこのことを議論し、なぜこれをやりたいのかを説明しよう。

Spring 1.0以降では、完全な生成クラスを含めて型を追加したAOPプロキシを扱うかもしれないが、これは、今のプログラミングモデルには影響ないはずだ。

6.2 Springのポイントカット

Springでどのように重大なポイントカットの概念を扱っているかを見ていこう。

6.2.1 概念

Springのポイントカットモデルは、advice型と無関係にポイントカットを再利用可能にする。同じポイントカットを使って異なるadviceをターゲットにすることが可能なのだ。

org.springframework.aop.Pointcutインタフェースが中心となるインタフェースであり、特定のクラスやメソッドをadviceのターゲットとするのに使われる。完全なインタフェースをを以下に示す。

public interface Pointcut {

    ClassFilter getClassFilter();

    MethodMatcher getMethodMatcher();

}

Pointcutインタフェースを2つに分割することにより、クラスやメソッドマッチングを再利用できるようになり、(メソッドマッチャで「union」を実行するような)粒度の細かい合成操作ができるようになる。

ClassFilterインタフェースは与えられた一連のターゲットクラスにポイントカットを限定するのに用いられる。matches()メソッドがいつもtrueを返す場合、ターゲットクラスは全て一致する。

public interface ClassFilter {

    boolean matches(Class clazz);
}

MethodMatcherインタフェースは通常、もっと重要だ。完全なインタフェースを以下に示す。

public interface MethodMatcher {

    boolean matches(Method m, Class targetClass);

    boolean isRuntime();

    boolean matches(Method m, Class targetClass, Object[] args);
}

このmatches(Method,Class)メソッドはこのポイントカットがターゲットクラスにある、与えられたメソッドやに一致するかどうかをテストするのに用いられる。全てのメソッド起動の度にテストする必要がないように、AOPプロキシが生成されるときにこの評価を実行する。もし、2つの引数をもつmatchesメソッドが与えられたメソッドに関しtrueを返す場合、さらにそのMethodMatcherisRuntime()メソッドがtrueを返す場合、引数が3つのmatchesメソッドがメソッドが起動されるたびに起動される。このため、ポイントカットが、ターゲットadviceが実行される前に、メソッドが起動される際に渡される引数を見ることができる。

ほとんどのMethodMatcherはスタティックで、これはつまりisRuntime()メソッドがfalseを返す、ということだ。この場合、3引数のmatchesメソッドは全く起動されない。

もし、可能であれば、ポイントカットをスタティックにして、AOPプロキシが生成された時にAOPフレームワークがポイントカット評価の結果をキャッシュできるようにしてみるといいだろう。

6.2.2 ポイントカットの操作

Springではポイントカットに対する操作、特にunionやintersectionをサポートしている。

unionは一方のポイントカットが一致するメソッドということだ。

intersectionは両方のポイントカットが一致するメソッドということだ。

通常はunionの方が役に立つ。

ポイントカットはorg.springframework.aop.support.Pointcutsクラスのスタティックメソッドを使うか、あるいは同じパッケージのComposablePointcutクラスを使って構成することができる。

6.2.3 便利なpointcutの実装

Springではいくつかの便利なpointcutの実装を用意している。役に立つものも中にはあり、他にはアプリケーションに特化してポイントカットのサブクラスになるように意図されている。

6.2.3.1 静的ポイントカット

スタティックポイントカットはメソッドとターゲットクラスに基づいていてメソッドの引数については考慮することができない。スタティックポイントカットはほとんどの用途にはこれで十分であり、かつこれがベストだ。Springではスタティックポイントカットを1回のみ、初めてそのメソッドが起動されたときに評価することができる。その後は、メソッドが起動される度にそのポイントカットを評価する必要はない。

Springに含まれている、いくつかのポイントカットの実装を見ていくこととしよう。

6.2.3.1.1 正規表現ポイントカット

静的なポイントカットを明示的に指定する方法の1つは正規表現だ。Springを始めとするAOPフレームワークはこれができるようになっているものもいくつかある。org.springframework.aop.support.Perl5RegexpMethodPointcut総称的な正規表現ポイントカットで、Perl5の正規表現シンタックスを採用している。Perl5RegexpMethodPointcutクラスは正規表現のマッチングをjakarta OROに依存している。SpringではJdkRegexpMethodPointcutクラスも提供しており、これは、JDK 1.4以降でサポートされている正規表現を使っている。

このPerl5RegexpMethodPointcutクラスを使えば、パターン文字列のリストを渡すことができる。このうちのどれかにマッチしたら、ポイントカットはtrueと評価される。(つまり、この結果は、ポイントカットのunionに対して有効なのだ)。

使い方については、下記を見て欲しい。

<bean id="settersAndAbsquatulatePointcut" 
    class="org.springframework.aop.support.Perl5RegexpMethodPointcut">
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

Springでは、RegexpMethodPointcutAdvisorという便利なクラスを提供していて、これによりAdviceへの参照が可能になる。(Adviceはインタセプタ、ビフォアアドバイス、スローアドバイスでもかまわない、ということを思い出してほしい)。 その背景にあるのは、SpringはJ2SE 1.4以降のJdkRegexpMethodPointcutを使い、それよりも古いVM上ではPerl5RegexpMethodPointcutに戻す。perl5プロパティにtrueを設定すれば強制的にPerl5RegexpMethodPointcutを使うことができるようになる。RegexpMethodPointcutAdvisorを使うことで、下記に示されるように、ある1つのビーンがポイントカットとアドバイザとして役に立つようにワイヤリングを単純化することができる。

<bean id="settersAndAbsquatulateAdvisor" 
    class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice">
        <ref local="beanNameOfAopAllianceInterceptor"/>
    </property>
    <property name="patterns">
        <list>
            <value>.*set.*</value>
            <value>.*absquatulate</value>
        </list>
    </property>
</bean>

RegexpMethodPointcutAdvisorはどののadvice型にも使うことができる。

6.2.3.1.2 属性駆動ポイントカット

静的ポイントカットの型の中で重要なのは、メタデータ駆動ポイントカットだ。これはメタデータ属性、よくあるのはソースレベルメタデータの値を使うものだ。

6.2.3.2 動的ポイントカット

動的ポイントカットを評価するのは、静的ポイントカットよりも処理が重い。動的ポイントカットでは、スタティックな情報と同様に、メソッドの引数も考慮する。これはつまり、メソッドが起動される度に評価を実施しないといけないということであり、引数は毎回変わるので、評価結果をキャッシュしておくこともできない。

メインの例はコントロールフローポイントカットだ。

6.2.3.2.1 コントロールフローポイントカット

Springのコントロールフローポイントカットは概念的にはAspectJのcflowポイントカットと似たものだ。でもそれほど強力ってわけじゃない。(現時点では、あるポイントカットを別のポイントカット下で実行するように指定する方法はない。)コントロールフローポイントカットは現状の呼び出しスタックと一致する。例えば、ジョインポイントがcom.mycompany.webパッケージのあるメソッド、あるいはSomeCallerクラスによって起動されたら発火する。コントロールフローポイントカットはorg.springframework.aop.support.ControlFlowPointcutクラスを使って指定される。

注意

コントロールフローポイントカットは実行時に評価するには他の動的ポイントカットに比べてもとても処理が重い。Java 1.4では、このコストは他の動的ポイントカットに比べて約5倍、Java 1.3では10倍もかかるのだ。

6.2.4 ポイントカットスーパクラス

Springでは独自のポイントカットを実装しやすいように、便利なポイントカットスーパクラスを提供している。

静的ポイントカットが一番便利なので、たぶん、下記の例のようにStaticMethodMatcherPointcutのサブクラスをつくるだろう。このときに必要なのは、1つだけ抽象メソッドを実装することだ(でも、動作をカスタマイズするために他のメソッドをオーバライドするのは可能だ)。

class TestStaticPointcut extends StaticMethodMatcherPointcut {

    public boolean matches(Method m, Class targetClass) {
        // return true if custom criteria match
    }
}

他にも動的ポイントカットのスーパクラスがある。

Spring 1.0RC2以降では任意のアドバイス型を兼ねた自前のポイントカットを使うことができるようになる。

6.2.5 カスタムポイントカット

SpringではポイントカットはJavaクラスなので、静的だろうと動的だろうと、(AspectJのような)言語機能よりもカスタムポイントカットを宣言するのが容易だ。でも、AspectJの文法だとコードに書ける厳密なポイントカット表現は全くサポートされないが、Springのカスタムポイントカットはどんな複雑なものでも大丈夫だ。

今後のバージョンのSpringでは、JACによる「セマンティックポイントカット」がサポートされるだろう。これは例えば、「ターゲットオブジェクトにあるインスタンス変数を変更するメソッド全部」といったものだ。

6.3 Springにおけるアドバイス型

Spring AOPがアドバイスをどのように扱うか見ていくこととしよう。

6.3.1 アドバイスライフサイクル

Springのアドバイスはアドバイスされるオブジェクト全部で共有されたり、各アドバイスされるオブジェクトでユニークであったりする。これはクラスごと(per-class)のアドバイス、インスタンスごと(per-instance)のアドバイスに対応するということだ。

クラス単位のアドバイスは最も頻繁に利用される。トランザクションアドバイザのような汎用的なアドバイスに向いているからだ。クラス単位のアドバイスはプロキシオブジェクトの状態には依存したり新しい状態を追加したりしないで、単にメソッドや引数に対して動くだけだ。

インスタンス単位のアドバイスはミックスインをサポートしているのでイントロダクションに向いている。この場合、アドバイスはプロキシオブジェクトに状態を追加する。

同じAOPプロキシで共有アドバイスやインスタンス単位のアドバイスを合成したものを利用することが可能だ。

6.3.2 Springのアドバイス型

Springではいくつかの優れたアドバイス型をサポートしていて、また任意のアドバイス型をサポートするために拡張することもできる。ここで基本的な概念と標準のアドバイス型を見ていこう。

6.3.2.1 インターセプションアラウンドアドバイス

Springで一番基本的なアドバイス型は、インターセプションアラウンドアドバイスだ。

Springはaroudアドバイスに関してAOPアライアンスのインタフェースに準拠していて、メソッドインターセプションを使っている。MethodInterceptorsを実装したアラウンドアドバイスでは以下のインタフェースを実装しなければならない。

public interface MethodInterceptor extends Interceptor {
  
    Object invoke(MethodInvocation invocation) throws Throwable;
}

このinvoke()メソッドのMethodInvocation引数は、起動されるメソッド、ターゲットのジョインポイント、AOPプロキシ、このメソッドの引数を明らかにする。このinvoke()メソッドは起動した結果得られる、ジョインポイントの返り値を返さなければならない。

簡単なMethodInterceptorの実装は下記のようになる。

public class DebugInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Before: invocation=[" + invocation + "]");
        Object rval = invocation.proceed();
        System.out.println("Invocation returned");
        return rval;
    }
}

MethodInvocationproceed()メソッドの呼び出しには注意して欲しい。これはジョインポイントへ向かってインタセプタの連鎖を進める。ほとんどのインタセプタはこのメソッドを起動し、その返り値を返す。しかしながら、任意のアラウンドアドバイスのようにMethodInterceptorは違った値を返したり、proceedメソッドを起動するのではなく例外を投げたりすることができる。でも、十分な理由がないままこういうことをやって欲しくはない。

MethodInterceptorは他のAOPアライアンス準拠なAOP実装を透過的にする。このセクションの以降で議論している他のアドバイス型は共通のAOP概念を実装しているが、Spring独自の実装法によるものなのだ。特定のアドバイス型のほとんどを使うのは利点があるけれど、他のAOPフレームワークでそのアスペクトを走らせたいのであれば、MethidInterceptorアラウンドアドバイスで切り離すことだ。ポイントカットは現状フレームワーク間で互換性はなく、AOPアライアンスでも現時点ではpointcutインタフェースを定義していないという点に注意してほしい。

6.3.2.2 ビフォアアドバイス

もっと単純なアドバイス型は、ビフォアアドバイスだ。これはMethodInvocationオブジェクトを必要としないのだ。それは、メソッドに入る前に呼ばれるだけだからだ。

ビフォアアドバイスの一番の利点は、proceed()メソッドを起動しなくてもよいということ、したがって、インターセプタチェインをつたって処理が進んでしまう恐れがない、ということだ。

MethodBeforeAdviceインタフェースを下記にお見せする。 (SpringのAPI設計では、フィールドビフォアアドバイスが考慮されているのだ。とはいっても、通常のオブジェクトはフィールドインタセプションに当てはまるし、Springがそれを実装するだろうということはありそうにはないけれど。)

public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method m, Object[] args, Object target) throws Throwable;
}

ここで返り値の型がvoid型だという点に注意してほしい。ビフォアアドバイスはジョインポイントが実行される前に独自の動作をはさむことができるが、返り値に手を入れることはできないのだ。ビフォアアドバイスが例外を投げたら、これはインタセプタチェインのそれから先の実行をアボートするだろう。この例外はインタセプタチェインに波及される。もしくは、AOPプロキシによって検査していない例外にラッピングされるだろう。

Springにおけるビフォアアドバイスの例だ。これは、メソッド起動を全てカウントするというものだ。

public class CountingBeforeAdvice implements MethodBeforeAdvice {
    private int count;
    public void before(Method m, Object[] args, Object target) throws Throwable {
        ++count;
    }

    public int getCount() { 
        return count; 
    }
}

ビフォアアドバイスは任意のポイントカットと一緒に使うことができる。

6.3.2.3 スローアドバイス

スローアドバイスはジョインポイントが例外を投げたら、そのジョインポイントがリターンした後で起動される。Springでは、スローアドバイスという型が用意されている。これはつまり、org.springframework.aop.ThrowsAdviceインタフェースにはメソッドが全然含まれていない、ということだ。これは与えられたオブジェクトが1つあるいは複数の型のスローアドバイスメソッドを実装することを指定するタグインタフェースで、この形式に従わないといけないのだ。

afterThrowing([Method], [args], [target], subclassOfThrowable) 

これは、最後の引数だけが必須だ。引数が1つから4つまであるのは、このアドバイスメソッドがそのメソッドや引数に着目しているかどうかに依存している。下記は、スローアドバイスの例だ。

このアドバイスは、RemoteException(サブクラスもふくむ)が投げられたら起動される。

public  class RemoteThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
}

下記のアドバイスは、ServletExceptionが投げられたら起動される。上述のアドバイスとは異なり、4つの引数が宣言されているので、起動されたメソッド、メソッドの引数、そしてターゲットオブジェクトへのアクセスが発生する。

public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something will all arguments
    }
}

最後の例は、1つのクラスの中でどのように2つのメソッドが使われるかを見てみよう。これは、RemoteExceptionServletExceptionの両方を処理する。スローアドバイスメソッドはいくつでも1つのクラスの中で組み合わせることができるのだ。

public static class CombinedThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
 
    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something will all arguments
    }
}

スローアドバイスはどんなポイントカットとでも一緒に使うことができる。

6.3.2.4 アフターリターンアドバイス

Springのアフターリターンアドバイスは、以下に示すようにorg.springframework.aop.AfterReturningAdviceインタフェースを実装しなければならない。

public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args, Object target) 
            throws Throwable;
}

アフターリターンアドバイスは返り値(が、手を加えることはできない)、起動されたメソッド、メソッド引数、ターゲットオブジェクトを参照する。

下記のアフターリターンアドバイスは、例外を投げずに成功したメソッド起動をすべてカウントするというものだ。

public class CountingAfterReturningAdvice implements AfterReturningAdvice {

    private int count;

    public void afterReturning(Object returnValue, Method m, Object[] args, Object target) throws Throwable {
        ++count;
    }

    public int getCount() {
        return count;
    }
}

このアドバイスは、実行パスを変更しない。もし例外が投げられたら、返り値のかわりにインターセプタチェインに沿って投げられるだろう。

アフターリターンアドバイスは任意のポイントカットと一緒に使うことができる。

6.3.2.5 イントロダクションアドバイス

Springではイントロダクションアドバイスを特別な種類のインタセプションアドバイスとして扱う。

イントロダクションはIntroductionAdvisorと、下記のインタフェースを実装するIntroductionInterceptorを必要とする。

public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}

AOPアライアンスのMethodInterceptorインタフェースから継承されたinvoke()メソッドはイントロダクションを実装しなければならない。これは、もし起動されたメソッドがあるイントロデュースされたインタフェースにあれば、そのイントロダクションインタセプタはそのメソッド呼び出しを処理する責任があり、proceed()を起動できない。

イントロダクションアドバイスはどのポイントカットからも利用することはできない。というのは、イントロダクションアドバイスは、メソッドレベルではなくクラスレベルでしか使うことができないからだ。イントロダクションアドバイスはIntroductionAdvisorと一緒のときしか使えない。これには以下のようなメソッドが用意されている。

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

    Class[] getInterfaces();
}

MethodMatcherはないし、それゆえイントロダクションアドバイスに関連づけられたポイントカットもない。論理的には、クラスフィルタリングだけだ。

ここにあるgetInterface()メソッドはこのアドバイザによってイントロデュースされたインタフェースを返すというものだ。

このValidateInterfaces()メソッドはイントロデュースされたインタフェースが設定されたIntroductionInterceptorにより実装されうるかどうかを見るために内部的に利用される。

Springのテストケースから簡単な例をみてみよう。あるオブジェクト(群)に以下のようなインタフェースをイントロデュースしたいとしよう。

public interface Lockable {
    void lock();
    void unlock();
    boolean locked();
}

これはmixinを示したものだ。アドバイスされたオブジェクトがどんな型であろうとLockableにキャストして、lockメソッド、unlockメソッドを叩きたいのだ。lock()メソッドが呼ばれると、すべてのsetterメソッドがLockedExceptionを投げられるようにしたい。つまり、オブジェクトを変更できないようにするという機能をオブジェクトにそういった情報を持たせずに提供するアスペクト追加することができるのだ。これはAOPの好例だ。

まずは、とても頼りになるIntroductionInterceptorを必要とするだろう。この場合だと、org.springframework.aop.support.DelegatingIntroductionInterceptorという便利なクラスを拡張する。IntroductionInterceptorを直接実装することができるだろうが、DelegatingIntyroductionInterceptorを使うのがこの場合だと一番いいだろう。

このDelegatingIntroductionInterceptorはインターセプションを使っていることを隠蔽してイントロダクションをイントロデュースされたインタフェースの実装へデリゲートするように設計されている。このデリゲートはコンストラクタ引数を使えばどんなオブジェクトに対しても設定できる。デフォルトのデリゲート(引数のないコンストラクタを使う場合)はこれだ。したがって、下記の例では、デリゲートはDelegatingIntroductionInterceptorのサブクラスのLockMixinだ。デリゲートが(デフォルトで)与えられると、DelegatingIntroductionInterceptorのインスタンスがそのデリゲートで実装されている(Introductioninterceptor以外の)全てのインタフェースを探す。そして、そのすべてに対し、イントロダクションをサポートする。LockMixinのようなサブクラスがsuppressInterface(Class intf)メソッドを叩いて、明示すべきでないインタフェースを無効にすることは可能だ。しかしながら、IntroductionInterceptorがサポートすることになっているインタフェースがどれだけあろうと、使われているIntroductinAdvisorはインタフェースが実際に公開されるよう制御するだろう。イントロデュースされたインタフェースはターゲットによって同じインタフェースの実装を隠そうとするのだ。

したがって、LockMixinDelegatingIntroductionInterceptorを継承し、Lockableそのものを実装する。スーパクラスは自動的にLockableがイントロダクション用にサポートされるものを選ぶので、特に指定する必要はない。この方法で、いくつでもインタフェースを導入することができるのだ。

ロックされたインスタンス変数を使う場合は注意してほしい。この効果によりターゲットオブジェクトに抱えられたものに状態が追加されるからだ。

public class LockMixin extends DelegatingIntroductionInterceptor 
    implements Lockable {

    private boolean locked;

    public void lock() {
        this.locked = true;
    }

    public void unlock() {
        this.locked = false;
    }

    public boolean locked() {
        return this.locked;
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (locked() && invocation.getMethod().getName().indexOf("set") == 0)
            throw new LockedException();
        return super.invoke(invocation);
    }

}

多くの場合、invoke()メソッドをオーバライドする必要はない。DelegatingIntroductionInterceptorの実装 -- これは、このメソッドがイントロデュースされると、デリゲートメソッドを叩く、そうでなければ、ジョインポイントに進む -- は通常はこれで十分だ。今のケースでは、ロックされた状態のときに起動されるsetterメソッドがないかどうかのチェックを追加しないといけない。

要求されているイントロダクションアドバイザは単純だ。やらないといけないことは別個のLockMixinインスタンスを抱え、イントロデュースされるインタフェース、この場合だとLockableだけを指定するだけだ。さらに複雑な例だと、イントロダクションインタセプタ(これはprototypeとして定義されると思う)への参照を取るかもしれない。このケースでは、LockMixinに必要な設定はなく、単に生成して、新しいのを使うだけだ。

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

    public LockMixinAdvisor() {
        super(new LockMixin(), Lockable.class);
    }
}

このアドバイザはとても簡単に適用することができる。設定は必要ない。(しかしながら、IntroductionAdvisorがなければIntroductionInterceptorを使うのが不可能なので、それは必要)イントロダクションでは、通例のように、ステートフルなので、アドバイザがインスタンスごとにないといけない。LockMixinAdvisorの別のインスタンス、したがってLockMixinが各アドバイスされるオブジェクトごとに必要とする。このアドバイザはアドバイスされるオブジェクトの状態の一部を含んでいる。

このアドバイザはAdvised.addAdvisor()メソッドを使ってプログラム的に適用するか、あるいは(お勧めの方法として)他のアドバイザのようにXML設定で適用することができる。下記で議論されているプロキシ生成選択は「自動プロキシクリエータ」を含んでいて、イントロダクションとステートフルmizinを正確に処理する。

6.4 Springにおけるアドバイザ

Springでは、アドバイザはアスペクトをモジュール化したものだ。アドバイザはだいたいは、アドバイスとポイントカットの両方を兼ね備えたものだ。

イントロダクションの特別なケースは別にして、任意のアドバイザは任意のアドバイスで使うことができる。org.springframework.aop.support.DefaultPointcutAdvisorは一番一般的なadvisorクラスだが、これだと例えば、MethodInterceptorBeforeAdviceThrowsAdviceと一緒に使うことができる。

Springでは同じAOPプロキシでアドバイザとアドバイス型を組み合わせることができる。例えば、1つのプロキシ設定の中でインターセプションアラウンドアドバイス、スローアドバイス、ビフォアアドバイスを使うことができる。Springでは自動的にインターセプタチェインを作るのに必要なものを生成する。

6.5 AOPプロキシ生成にProxyFactoryBeanを使う

Spring IoCコンテナ(ApplicationContextもしくはBeanFactoryをビジネスオブジェクトに使っているのであれば(そうすべきだ)、AOP FactoryBeansのどれかを使ってみたいとおもうことだろう(ファクトリビーンは間接的なレイヤを導入して、異なる型のオブジェクトを生成できるようにする、ということを思い出してほしい)。

6.5.1 基本事項

ProxyFactoryBeanはSpringのほかのFactoryBean実装のように、間接的なレイヤを導入する。fooという名前でProxyFactoryBeanを定義すると、fooを参照しているオブジェクトを見るのはProxyFactoryBeanのインスタンスではなくProxyFactoryBeanの実装のgetObject()メソッドによって生成されたオブジェクトだ。このメソッドはターゲットオブジェクトをラップするAOPプロキシを生成する。

ProxyFactoryBeanやその他AOPプロキシを生成するのにIoC用クラスを使う一番重要な利点は、これがアドバイスやポイントカットがIoCで管理できるようになる、ということを意味しているということだ。これはとても強力で、他のAOPフレームワークではなかなか実現しがたいアプローチを実現できるのだ。例えば、あるアドバイス自体が、(ターゲットオブジェクトに加えて、これはどんなAOPフレームワークでもできるはずだが)アプリケーションオブジェクトを参照し、Dependency Injectionによる着脱可能性から得られる利点を得るかもしれない。

6.5.2 JavaBeanのプロパティ

Springで提供されるFactoryBeanの実装のほとんどのように、ProxyFactoryBeanはそれ自体、JavaBeanであり、そのプロパティは以下のように利用される。

いくつかの重要なプロパティはorg.springframework.aop.famework.ProxyConfigという、全てのAOPプロキシファクトリのスーパクラスから継承したものだ。これには以下のようなものが含まれる。

ProxyFactoryBeanにある他のプロパティには以下のようなものがある。

6.5.3 インタフェースをプロキシする

実際のProxyFactorybeanの簡単な例をみてみよう。この例では以下のことを行う。

<bean id="personTarget" class="com.mycompany.PersonImpl">
    <property name="name"><value>Tony</value></property>
    <property name="age"><value>51</value></property>
</bean>

<bean id="myAdvisor" class="com.mycompany.MyAdvisor">
<property name="someProperty"><value>Custom string property \
    value</value></property>
</bean>

<bean id="debugInterceptor" \
    class="org.springframework.aop.interceptor.DebugInterceptor">
</bean>

<bean id="person" 
    class="org.springframework.aop.framework.ProxyFactoryBean">
<property \
    name="proxyInterfaces"><value>com.mycompany.Person</value></property>

    <property name="target"><ref local="personTarget"/></property>
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>
  • interceptorNamesプロパティは文字列のリスト、つまりカレントファクトリのインタセプタもしくはアドバイザのビーン名をとることに注意してほしい。アドバイザ、インタセプタ、ビフォアアドバイス、アフターリターンアドバイス、それにスローアドバイスが使用可能だ。このアドバイザの順序は重要だ。

    なぜ、このリストにビーンのリファレンスを持たないのか不思議に思っているかもしれない。そのわけは、もしProxyFactoryBeansingletonプロパティがfalseに設定されていたら、個別のプロキシインスタンスを返さなければならない。もし、どのアドバイザもがプロトタイプであれば、個々のインスタンスが返される必要があるので、ファクトリからプロトタイプのインスタンスを取得できる必要がある。リファレンスを保持してもそれで十分ではないのだ。

    上記の"person"ビーンの定義は、以下のようにPersonの実装に置き換えられる。

    Person person = (Person) factory.getBean("person");
    

    通常のJavaオブジェクトと同様に、同じIoCコンテナの他のビーンはそこの強く型付けられた依存関係を表現することができる。

    <bean id="personUser" class="com.mycompany.PersonUser">
        <property name="person"><ref local="person" /></property>
    </bean>
    

    この例にあるPersonUserクラスはPerson型のプロパティを露出している。これに関する限り、AOPプロキシは透過的に”実際の”personの実装で使うことができる。しかしながら、このクラスは動的プロキシクラスになるので、アドバイスされたインタフェースへのキャストは可能だ。

    下記のように、匿名の内部ビーンを使ってターゲットとプロキシの違いを隠蔽することができる。ここでは、ProxyFactoryBeanの定義だけが異なっており、アドバイスはその完全性のために含まれているだけである。

    <bean id="myAdvisor" class="com.mycompany.MyAdvisor">
    <property name="someProperty"><value>Custom string property \
        value</value></property>
    </bean>
    
    <bean id="debugInterceptor" \
        class="org.springframework.aop.interceptor.DebugInterceptor" />
    
    </bean>
    
    <bean id="person" \
        class="org.springframework.aop.framework.ProxyFactoryBean">
    <property \
        name="proxyInterfaces"><value>com.mycompany.Person</value></property>
        <!-- Use inner bean, not local reference to target -->
        <property name="target">
            <bean class="com.mycompany.PersonImpl">
                <property name="name"><value>Tony</value></property>
                <property name="age"><value>51</value></property>
            </bean>
       </property>
    
        <property name="interceptorNames">
            <list>
                <value>myAdvisor</value>
                <value>debugInterceptor</value>
            </list>
        </property>
    </bean>
    

    アプリケーションコンテキストのユーザがアドバイスされていないオブジェクトへの参照を取得のを避けたければ、もしくはSpring IoCのオートワイヤリングのあいまい性を回避する必要があれば、これには、Person型のオブジェクトだけという長所がある。他にも、ProxyFactoryBeanの定義が自己を含んでいることも利点であることは間違いない。しかしながら、アドバイアスされていないターゲットをファクトリから取得できるような、例えば、特定のテストシナリオでタイミングが存在することも実際には利点となるかもしれない。

    6.5.4 クラスをプロキシする

    もし、インタフェースではなく、クラスをプロキシする必要があるとすればどうだろう。

    上述した例の中で、Personインタフェースが無かった、つまりビジネスインタフェースを実装していないPersonクラスを叩くクラスをアドバイスする必要があった場合を考えてみてほしい。この場合、動的プロキシではなく、CGLIBプロキシを使うようにSpringを設定することができる。単に上記のProxyFactoryBeanにあるProxtTargetClasstrueに設定するだけでいい。クラスではなくてインタフェースにプログラムするのが一番ベストな方法ではあるけど、インタフェースを実装していないクラスをアドバイスできるというのはレガシーなコードを使う場合にとても便利だ。(一般に、Springは規定するようなことはあまりない。いいプラクティスを適用するのを簡単にするので、特別なやり方を強要するのを避けているのだ)。

    必要であれば、たとえインタフェースがあったとしても、どんな場合でもCGLIBを無理やり使うことは可能だ。

    実行時にターゲットクラスのサブクラスを生成することによりCGLIBプロキシは動く。Springはこの生成されたサブクラスをもとのターゲットメソッド呼び出しを委譲するように設定を行う。このサブクラスには概してデコレータパターンを実装されており、これはアドバイスに組み込まれる。

    CGLIBプロキシは一般にユーザに対し透過的でなければならない。でも、考慮しておくべき問題がいくつかある。

    CGLIBプロキシと動的プロキシではパフォーマンス上の違いはほとんどない。Spring1.0では動的プロキシの方が若干速かったが、将来には改善されるだろう。このケースではパフォーマンスに判断の重点を置くべきではないのだ。

    6.5.5 'global'アドバイザを使う

    インタセプタ名にアスタリスクを追加することにより、アスタリスクの前とマッチするビーン名のついたすべてのアドバイザがアドバイザチェーンに追加される。もし、標準の「グローバル」アドバイザを追加する必要があるのであれば、便利になるだろう。

    <bean id="proxy" \
        class="org.springframework.aop.framework.ProxyFactoryBean">
      <property name="target" ref="service"/>
      <property name="interceptorNames">
        <list>
          <value>global*</value>
        </list>
      </property>
    </bean>
    <bean id="global_debug" \
        class="org.springframework.aop.interceptor.DebugInterceptor"/>
    <bean id="global_performance" \
        class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor"/> \
    

    6.6 便利なプロキシ生成

    関心のあるアスペクトは1つだけ、例えばトランザクション管理というのがほとんどだろうからProxyFactoryBeanをフルに使い倒さないといけないようなことはほとんどないと思う。

    特定のあるアスペクトに注目した場合に、AOPプロキシを生成するのに使うことができる便利なファクトリはいくつもある。他の章でも述べてあるので、ここでは簡単なサーベイを提供しよう。

    6.6.1 TransactionProxyFactoryBean

    Springのリリースに同梱されているJPetStoreのサンプルアプリケーションではTransactionProxyFactoryBeanの使い方が示されている。

    このTransactionFactoryBeanProxyConfigのサブクラスなので、基本的な設定はProxyFactoryBeanと共有している(上述したProxyConfigプロパティのリストをみてほしい)。

    JPetStoreから持ってきた下記の例では、これをどう使うかを示したものだ。ProxyFactoryBeanをもつターゲットビーンが定義されている。依存関係はターゲットのPOJO(ここでいう、petStoreTarget)ではなくプロキシされたファクトリビーン定義(ここではpetStore)にある。

    このTransactionProxyFactoryBeanにはターゲットと、"transaction attributes"に指定されている、どのメソッドがトランザクションで囲むべきか、どこまで波及されるかといった情報やその他の設定が必要だ。

    <bean id="petStoreTarget" \
        class="org.springframework.samples.jpetstore.domain.logic.PetStoreImpl">
        <property name="accountDao"><ref bean="accountDao"/></property>
        <!-- Other dependencies omitted -->
    </bean>
    
    <bean id="petStore" 
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager" ref="transactionManager"/>
        <property name="target" ref="petStoreTarget"/>
        <property name="transactionAttributes">
            <props>
                <prop key="insert*">PROPAGATION_REQUIRED</prop>
                <prop key="update*">PROPAGATION_REQUIRED</prop>
                <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
            </props>
        </property>
    </bean>
    

    ProxyFactoryBeanでのように、ターゲットプロパティの値の設定を行うのに、top-levelターゲットビーンへの参照の代わりに内部ビーンを使うことにするかもしれない。

    TransactionProxyFactoryBeanはトランザクションアドバイザを自動的に生成する。これには、トランザクション属性をベースにしたポイントカットも含まれている。だから、トランザクションで囲まれるメソッドにだけアドバイスされるのだ。

    TransactionProxyFactoryBeanではpreInterceptorspostInterceptorsプロパティを使ってプレアドバイスやポストアドバイスを指定することができる。このプロパティはインタセプタ、その他のアドバイスやトランザクションインタセプタの前後のインタセプションチェインに配置されるアドバイザのオブジェクトの配列をとる。これは下記のように、XMLのビーン定義にある<list>要素を使って指定することができる。

    <property name="preInterceptors">
        <list>
            <ref bean="authorizationInterceptor"/>
            <ref bean="notificationBeforeAdvice"/>
        </list>
    </property>
    <property name="postInterceptors">
        <list>
            <ref bean="myAdvisor"/>
        </list>
    </property>
    

    これらのプロパティは上記のpetStoreビーンの定義に追記されることがあるかもしれない。一般的な使い方は、トランザクション性を宣言的なセキュリティと組み合わせるというEJBで提供されているのと似たやり方だ。

    ProxyFactoryBeanにあるようなビーン名ではなく実際のインスタンス参照を使うために、プレインタセプタやポストインタセプタは共有インスタンスアドバイスにしか使うことができない。そのため、ステートフルアドバイス、例えば、mixinには役に立たない。これはTransactionProxyFactoryBeanの目的と合致していて、一般的なトランザクション設定を行うための簡単な方法をもたらす。もっと複雑な独自のAOPが必要なのであれば、汎用的なProxyFactoryBeanかあるいは自動プロキシ生成(後述する)を使うことを考えてみることをお勧めする。

    特にSpringのAOPを、多くの場合そうだが、EJBの置換えとして見ると、ほとんどのアドバイスはかなり総称的であり、共有インスタンスモデルを使っているということに気づく。宣言的トランザクション管理やセキュリティチェックは古典的な例なのだ。

    TransactionProxyFactoryBeantransactionManagerというJavaBeanプロパティによりPlatformTransactionManagerの実装に依存している。これによりJTAやJDBCもしくはその他のストラテジをベースとする着脱可能なトランザクション実装が可能になる。これはAOPよりは、Springのトランザクション抽象化に関係がある。トランザクションインフラについては、次のチャプタで議論しようと思う。

    もし宣言的トランザクション管理にしか関心がないのであれば、TransactionProxyFactoryBeanがお望みのソリューションだ。ProxyFactoryBeanを使うよりも簡単だと思う。

    6.6.2 EJBプロキシ

    他の特定用途向けのプロキシはEJBのプロキシを生成し、EJBの「ビジネスメソッド」インタフェースがコードを呼ぶことで直接使えるようにすることができる。コードを呼ぶのにJNDIルックアップを実行したりEJB生成メソッドを使う必要はないので、コードの可読性とアーキテクチャの柔軟性という面で著しい改善が得られるのだ。

    より詳細の情報については、本マニュアルにあるSpringのEJBサービスのチャプタを参照してほしい。

    6.7 簡単なプロキシの定義

    特にトランザクションプロキシを定義する場合、多くの同じようなプロキシ定義になってしまってもよい。内部ビーンに加え、親と子供のビーン定義を使うことは、結局はよりきれいでより簡潔なプロキシ定義になる。

    最初は、親のテンプレートビーン定義がプロキシとして生成される。

    <bean id="txProxyTemplate" abstract="true"
    class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
    
        <property name="transactionManager" ref="transactionManager" />
        <property name="transactionAttributes">
          <props>
            <prop key="*">PROPAGATION_REQUIRED</prop>
          </props>
        </property>
    </bean>
    

    これはけっしてインスタンス化されることはないので、現実的には完全でないかもしれない。ここで、生成される必要のある各プロキシはまさに子供のビーン定義であり、これは内部ビーン定義としてターゲットのプロキシをラップするためであり、したがってこのターゲットは使用されることはない。

    <bean id="myService" parent="txProxyTemplate">
        <property name="target">
          <bean class="org.springframework.samples.MyServiceImpl">
          </bean>
        </property>
    </bean>
    

    もちろん、このトランザクションプロパゲーションの設定のように、親テンプレートからのプロパティをオーバライドは可能だ。

    <bean id="mySpecialService" parent="txProxyTemplate">
        <property name="target">
          <bean class="org.springframework.samples.MySpecialServiceImpl">
          </bean>
        </property>
        <property name="transactionAttributes">
          <props>
            <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
            <prop key="store*">PROPAGATION_REQUIRED</prop>
          </props>
        </property>
    </bean>
    

    上記の例では、前にも述べたように、明示的に親のビーン定義を、abstract属性を使ってabstractとしてマークしたので、実際にインスタンス化されることはない、という点には注意して欲しい。アプリケーションコンテキストは(単なるビーンファクトリではなく)デフォルトでは全てのシングルトンをあらかじめインスタンス化する。従って、(少なくともシングルトンビーンにとって)もし、テンプレートとしてしか使うつもりのない(親の)ビーン定義があり、その定義でクラスが指定されていれば、abstract属性をtrueを設定しておく必要がある、という点は重要だ。さもないと、アプリケーションコンテキストは本当にあらかじめインスタンス化しようとしてしまう。

    6.8 ProxyFactoryでAOPプロキシをプログラム的に生成する

    Springを使ってAOPプロキシをプログラム的に生成するのは簡単だ。Spring IoCの依存関係がなくてもSpringのAOPを使うことができるようになる。

    次のリストは、インタセプタ1つにアドバイザ1つと共にターゲットオブジェクトのプロキシを生成するのを示したものだ。このターゲットオブジェクトによって実装されたインタフェースは自動的にプロキシされる。

    ProxyFactory factory = new ProxyFactory(myBusinessInterfaceImpl);
    factory.addInterceptor(myMethodInterceptor);
    factory.addAdvisor(myAdvisor);
    MyBusinessInterface tb = (MyBusinessInterface) factory.getProxy();
    

    まず最初は、org.springframework.aop.framework.ProxyFactory型のオブジェクトを生成することだ。これは上記の例にあったようにターゲットオブジェクトで作るか、あるいは別のコンストラクタにプロキシされるインタフェースを指定することで作ることができる。

    インタセプタ、もしくはアドバイザを追加してProxyFactoryの生存のためにそれらを操作することができる。もしntroductionInterceptionAroundAdvisorを追加すれば、プロキシに追加のインタフェースを実装することができる。

    ProxyFactoryには他にも便利な(AdvisedSupportから継承された)メソッドがあり、ビフォアアドバイスやスローアドバイスのような他のアドバイス型を追加することができる。AdvisedSupportProxyFactoryProxyFactoryBeanの両方のスーパクラスなのだ。

    AOPプロキシ生成をIoCフレームワークと統合するのはほとんどのアプリケーションでベストなプラクティスだ。我々は一般によくあるように、コンフィグレーションをJavaコードからAOPで外在化させるのをお勧めする。

    6.9 アドバイスされたオブジェクトを操作する

    どうやってAOPプロキシを生成しようとも、どんなAOPプロキシでもこのインタフェースにキャストすることが可能で、他のどんなインタフェースが実装されていようとorg.springframework.aop.framework.Advisedインタフェースを使ってAOPプロキシを操作することができる。このインタフェースには以下のメソッドが用意されている。

    Advisor[] getAdvisors();
    
    void addAdvice(Advice advice) throws AopConfigException;
    
    void addAdvice(int pos, Advice advice) 
            throws AopConfigException;
    
    void addAdvisor(Advisor advisor) throws AopConfigException;
    
    void addAdvisor(int pos, Advisor advisor) throws AopConfigException;
    
    int indexOf(Advisor advisor);
    
    boolean removeAdvisor(Advisor advisor) throws AopConfigException;
    
    void removeAdvisor(int index) throws AopConfigException;
    
    boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;
    
    boolean isFrozen();
    

    このgetAdvisors()メソッドは、ファクトリに追加された全てのアドバイザ、インタセプタ、あるいはその他のアドバイス型のアドバイザを返す。もしあるアドバイザを追加していたら、このインデックスで返されるアドバイザは追加したオブジェクトだ。もしインタセプタ、あるいはその他のアドバイス型を追加していたら、Springはそれを常にtrueを返すポイントカットつきのアドバイザにそれをラップして返す。したがって、もしMethodInterceptorを追加したら、そのインデックスで返されるアドバイザは,そのmethodInterceptorを返すDefaultPointcutAdvisorと、クラスやメソッドと全て一致するポイントカットになるだろう。

    addAdvisor()メソッドは任意のアドバイザを追加するのに利用することができる。通常、このポイントカットを保持するアドバイザとアドバイスは汎用のDefaultPointcutAdvisorで、これはイントロダクションを除く任意のアドバイスやポイントカットと一緒に使うことができる。

    デフォルトでは、一旦プロキシが生成されてもアドバイザやインタセプタを追加したり削除することは可能だ。唯一の制限は、既存のファクトリからのプロキシがインタフェースの変更を見せないように、イントロダクションアドバイザを追加したり削除することができないということだ(この問題を回避するためにそのファクトリから新しいプロキシを取得することは可能だ)。

    以下は、AOPプロキシをアドバイスされたインタフェースへキャストしてそのアドバイス操作を実行する簡単な例だ。

    Advised advised = (Advised) myObject;
    Advisor[] advisors = advised.getAdvisors();
    int oldAdvisorCount = advisors.length;
    System.out.println(oldAdvisorCount + " advisors");
    
    // Add an advice like an interceptor without a pointcut
    // Will match all proxied methods
    // Can use for interceptors, before, after returning or throws advice
    advised.addAdvice(new DebugInterceptor());
    
    // Add selective advice using a pointcut
    advised.addAdvisor(new DefaultPointcutAdvisor(mySpecialPointcut, myAdvice));
    
    assertEquals("Added two advisors",
         oldAdvisorCount + 2, advised.getAdvisors().length);
    

    確かに正しいやり方の場合もあるが、作ったビジネスオブジェクトのアドバイスを修正するにあたってアドバイスできるかどうか(別に、駄洒落じゃないよ)は問題だ。でも、開発の中でも例えばテストなどではとても役に立つこともある。テストしたいメソッド起動の内部にインタセプタや別のアドバイスの形でテストコードを追加できるということがとても便利だと思うことがこれまでに時折あった。(ロールバックのためにトランザクションがマークされる前に、例えばデータベースが正しく更新されているかをチェックするためのSQLを走らせるために、例えばアドバイスはあるメソッドのために生成されたトランザクションを内部に取得することができる)。

    どうやってそのプロキシを生成したかによって、固定されたフラグを設定することができる。この場合は、アドバイスされたisFrozen()メソッドはtrueを返し、追加あるいは削除を使ってどのようにアドバイスを変更しようとしても結果はAopconfigExceptionになってしまう。アドバイスされたオブジェクトの状態が固定できるというのは便利なことがある。例えば、セキュリティインタセプタを削除しているコード呼び出しを避けるとか。もし実行時にアドバイスを変更するのが求められていないことが分かっているのであれば、積極的な最適化ができるようにするためにSpring1.1では使われるかもしれない。

    6.10 autoproxyファシリティを使う

    これまでのところでは、ProxyFactoryBeanあるいは同様のファクトリビーンを使った明示的なAOPプロキシの生成について考えてきた。

    Springでは「自動プロキシ」ビーン定義というものを使うことができる。これは自動的に選んだビーン定義をプロキシすることができるというものだ。これはSpringの「ビーンポストプロセッサ」インフラストラクチャで構築されていて、これによって任意のビーン定義をコンテナがロードするように修正することができるというものだ。

    このモデルでは、自動プロキシインフラストラクチャを設定するXMLビーン定義ファイルに特別なビーン定義をいくつかセットアップする。これにより、自動プロキシするべきターゲットを宣言するだけでよくなり、ProxyFactoryBeanを使う必要がない。

    これを行うには2つの方法がある。

    6.10.1 Autoproxyのビーン定義

    org.springframework.aop.framework.autoproxyパッケージでは以下のような標準の自動プロキシクリエータが用意されている。

    6.10.1.1 BeanNameAutoProxyCreator

    BeanNameAutoProxyCreatorはリテラル値やワイルドカードにマッチした名前のビーンに対するAOPプロキシを自動的に生成する。

    <bean \
        class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> \
      <property name="beanNames"><value>jdk*,onlyJdk</value></property>
      <property name="interceptorNames">
        <list>
          <value>myInterceptor</value>
        </list>
      </property>
    </bean>
    

    ProxyFactoryBeanのように、インタセプタのリストではなくinterceptorNamesプロパティがある。これはプロトタイプアドバイザが正しい振る舞いができるようにするためのものだ。「interceptors」というものはアドバイザかあるいは任意のアドバイス型をとることができる。

    一般的に自動プロキシでのようにBeanNameAutoProxyCreatorを使う一番のポイントは同じ設定を一貫性をもって複数のオブジェクトに適用し、なおかつ設定を最も少なくてすむようにするためだ。宣言的トランザクションを複数のオブジェクトに適用するのにこの方法が使われるのが一般的だ。

    上記の例の「jdkMyBean」や「onlyJdk」のように、名前がマッチするビーン定義はターゲットクラスのついた普通の(plain old)ビーン定義だ。AOPプロキシは自動的にBeanNameAutoProxyCreatorによって生成され、同じアドバイスがマッチしたビーン全てに適用される。もし(上記の例のようにインタセプタではなく)アドバイザが使われる場合は、ポイントカットが違うビーンに別の方法で適用されることもある、という点に注意してほしい。

    6.10.1.2 DefaultAdvisorAutoProxyCreator

    より一般的で非常に強力な自動プロキシクリエータは、DefaultAdvisorAutoProxyCreatorだ。これは自動的にカレントコンテキストにおいて適切なアドバイザが適用され、特定のビーン名を自動プロキシアドバイザのビーン定義に記述する必要がないというものだ。これは、BeanNameAutoProxyCreatorと同じように一貫した設定と、複製を避けるというメリットを享受できる。

    この機構は以下のようにして利用する

    DefaultAdvisorAutoProxyCreatorは各アドバイザに含まれているポイントカットを自動的に評価し、どのアドバイスを各ビジネスオブジェクト(ここの例では、「businessObject1」とか「businessObject2」)に適用すべきかを確認する。

    これはつまり、いくつでもアドバイザを各ビジネスオブジェクトに自動的に適用できるということだ。ビジネスオブジェクトのどのメソッドにもマッチするアドバイザがもしなければ、そのオブジェクトはプロキシされない。新しいビジネスオブジェクトのビーン定義が追加されると、必要であれば自動的にプロキシされる。

    自動プロキシは一般的に、呼び出し側や依存関係のあるオブジェクトにアドバイスされていないオブジェクトを取得できなくするという利点がある。getBean("businessObject1")をこのアプリケーションコンテキストで呼ぶと、ターゲットのビジネスオブジェクトではなくAOPプロキシが返る。(以前に紹介した「内部ビーン」イディオムにもこの利点がある。)

    <bean \
        class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> \
    
    <bean \
        class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> \
      <property name="transactionInterceptor" ref="transactionInterceptor"/>
    </bean>
    
    <bean id="customAdvisor" class="com.mycompany.MyAdvisor"/>
    
    <bean id="businessObject1" class="com.mycompany.BusinessObject1">
      <!-- Properties omitted -->
    </bean>
    
    <bean id="businessObject2" class="com.mycompany.BusinessObject2"/>
    

    もし、同じアドバイスを一貫して数多くのビジネスオブジェクトに適用したいのであれば、DefaultAdvisorAutoProxyCreatorはとても便利なものだ。一旦このインフラストラクチャの設定をしておけば特別なプロキシ設定をしないで新しいビジネスオブジェクトを単純に追加するだけでいい。さらに追加しているアスペクト-- 例えば、トレースアスペクトとかパフォーマンスモニタリングアスペクトとか--を最低限の設定変更でとても簡単に外すことが可能だ。

    DefaultAdvisorAutoProxyCreatorはフィルタリング(命名規約を使っているので特定のアドバイザのみ評価し、複数の別個に設定されたAdvisorAutoProxyCreatorが同じファクトリ内で利用できる)と順序づけをサポートする。アドバイザはorg.springframework.core.Orderedインタフェースを実装することで、正しく整列されていることを保証することができる。上述した例で使われていたThransactionAttributeSourceAdvisorには順序つきの値を設定することができるが、デフォルトは順序なしだ。

    6.10.1.3 AbstractAdvisorAutoProxyCreator

    これはDefaultAdvisorAutoProxyCreatorのスーパクラスだ。このクラスを継承してアドバイザ定義ではDefaultAdvisorAutoProxyCreatorフレームワークの振る舞いに与えるカスタマイズが十分ではないようなイベントの場合、自前の自動プロキシを作ることができる。

    6.10.2 メタデータ駆動自動プロキシを使う

    特に重要な自動プロキシの型はメタデータ駆動だ。これは、.NETServicedComponentsに似たプログラミングモデルを提供する。EJBでのようなXMLのデプロイメントデスクリプタを使うのではなく、トランザクション管理その他のエンタープライズサービスの設定はソースレベル属性に保持される。

    この場合は、メタデータ属性を理解するアドバイザと組み合わせDefaultAdvisorAutoProxyCreatorを使う。このメタデータの詳細は、自動プロキシ生成クラス自体にではなく、候補アドバイザのポイントカットのところに保持される。

    これは、DefaultAdvisorAutoProxyCreatorの本当に特別な場合だが、検討に値する。(メタデータを意図したコートはアドバイザのポイントカットに含まれており、AOPフレームワークにあるのではない。)

    JPetStoreのサンプルアプリケーションの/attributesディレクトリは、属性駆動の自動プロキシを使うということを表している。この場合、TransactionProxyFactoryBeanを使う必要はない。ビジネスオブジェクトにあるトランザクションが必要な属性を単に定義するだけで十分なのは、メタデータを処理するポイントカットを利用するからなのだ。このビーン定義には下記のコードが/WEB-INF/declarativeServices.xmlに含まれている。これは一般的なものであり、JPetStore以外にも用いることができる、という点に注意して欲しい。

    <bean \
        class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> \
    
    <bean \
        class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor"> \
      <property name="transactionInterceptor" ref="transactionInterceptor"/>
    </bean>
    
    <bean id="transactionInterceptor"
    class="org.springframework.transaction.interceptor.TransactionInterceptor">
      <property name="transactionManager" ref="transactionManager"/>
      <property name="transactionAttributeSource">
    <bean \
        class="org.springframework.transaction.interceptor.AttributesTransactionAttributeSource"> \
          <property name="attributes" ref="attributes"/>
        </bean>
      </property>
    </bean>
    
    <bean id="attributes" \
        class="org.springframework.metadata.commons.CommonsAttributes"/>
    

    ここで定義されているTransactionInterceptorPlatformTransactionManagerの定義に依存しており、これはこの一般的なファイルには(含まれるべきではあるが)含まれていない。というのは、アプリケーションのトランザクション要件(典型的な例としては、この例にあるようにJTA、Hibernate,JDOもしくはJDBC)を指定されるだろうから。

    <bean id="transactionManager" 
        class="org.springframework.transaction.jta.JtaTransactionManager"/>
    

    もしあなたが、宣言的トランザクション管理しか求めていないんどえあれば、これらの一般的なXML定義を使えばSpringがトランザクション属性をもったクラスやメソッドを全部自動的にプロキシしてくれる。直接AOPで指定する必要はなく、このプログラミングモデルは.NETServicedComponentsのプログラミングモデルに似たものだ。

    このメカニズムは拡張することができる。カスタム属性をベースとする自動プロキシが可能だ。これを行うには以下の手順が必要となる。

    このようなアドバイザをアドバイスされるクラス(例えば、mixins)ごとに一意にすることができる。その場合アドバイザは単に、シングルトンではなくプロトタイプビーン定義として定義すればよい。例えばSpringのテストスイートから前述した、SpringLockMixinイントロダクションインタセプタは示しているようにmixinをターゲットとする属性駆動ポイントカットと連携して使うことができる。我々はJavaBeanプロパティを使って設定された総称的なDefaultPointcutAdvisorを使っている。

    <bean id="lockMixin" class="org.springframework.aop.LockMixin" \
        singleton="false" />
    
    <bean id="lockableAdvisor" \
        class="org.springframework.aop.support.DefaultPointcutAdvisor" \
        singleton="false">
        <property name="pointcut" ref="myAttributeAwarePointcut" />
        <property name="advice" ref="lockMixin" />
    </bean>
    
    <bean id="anyBean" class="anyclass" ...
    

    anyBeanあるいは別のビーン定義に属性を意識したポイントカットがマッチするメソッドがあれば、このmixinが適用される。lockMixinlockableAdvisorの定義は両方ともプロトタイプであることに注意してほしい。このmyAttributeAwarePointocutポイントカットは、個々のアドバイスされるオブジェクトの状態を保持していないのでシングルトンで定義することも可能ではあるのだ。

    6.11 TargetSourceを使う

    Springではorg.springframework.aop.TargetSourceインタフェースで表されるTargetSourceの概念が用意されている。このインタフェースはジョインポイントを実装している「ターゲットオブジェクト」を返さなければならない。このTargetSourceの実装は、AOPプロキシがメソッドを起動するたびにターゲットとなるインスタンスを求められる。

    Spring AOPを用いる開発者は通常はTargetSorucesを直接使う必要はないが、プーリングやホットスワップ、その他技巧的な目的のための強力な手段を提供する。例えば、TargetSourceプーリングでは、インスタンスを管理するためのプールを用いて、起動する度に異なるターゲットインスタンスを返すことができる。

    もし、TargetSourceを指定しなkれば、ローカルオブジェクトをラップしたデフォルトの実装が使われる。(期待どおりに)メソッドを起動する度に同じターゲットが返される。

    Springで提供されている標準のターゲットソースを、そしてそれをどうやって使うことができるか見てみることにしよう。

    自前のターゲットソースを使う場合は、ターゲットは通常シングルトンではなく、プロトタイプのビーン定義を必要とする。これにより、メソッドが起動されたときにSpringが新しいターゲットインスタンスを生成することができるようになる。

    6.11.1 ホットスワップ可能なターゲットソース

    org.springframework.aop.target.HotSwappableTargetSourceが用意されているので、呼び出し側がAOPプロキシのターゲットへのリファレンスを保持しながらもそのターゲットを切り替えることができる。

    ターゲットソースのターゲットを変更するとそれは直ちに反映される。HotSwappableTargetSourceはスレッドセーフだ。

    HotSwappableTargetSourceにあるswap()メソッドを下記のように使えば、ターゲットを変更することができる。

    HotSwappableTargetSource swapper = 
        (HotSwappableTargetSource) beanFactory.getBean("swapper");
    Object oldTarget = swapper.swap(newTarget);
    

    これのXML定義は下記のようなものが必要となる。

    <bean id="initialTarget" class="mycompany.OldTarget" />
    
    <bean id="swapper" \
        class="org.springframework.aop.target.HotSwappableTargetSource">
        <constructor-arg ref="initialTarget" />
    </bean>
    
    <bean id="swappable" \
        class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="targetSource" ref="swapper" />
    </bean>
    

    上記にあるswap()呼び出しはスワップ可能ビーンのターゲットを差し替える。このビーンへのリファレンスを保持しているクライアントはその変更を意識せずに、すぐさま新しいターゲットにアクセスし始める。

    この例では、アドバイスを追加していないし、-それに、TargetSourceを使うのにアドバイスの追加は必要ない- もちろんどんなTargetSourceでも任意のアドバイスと一緒に使うことができるけど。

    6.11.2 ターゲットソースをプールする

    ターゲットソースプーリングを使うと、ステートレスセッションEJBと似たプログラミングモデルが使えるようになり、同一インスタンスのプールが維持され、メソッドが起動されるとプールにあるオブジェクトが解放される。

    Springのプーリングとステートレスセッションビーンのプーリングにおける一番の違いは、Springのプーリングは、どんなPOJOにも適用することができる、ということだ。通常Springでは、このサービスは侵略的ではない方法で適用することができる。

    Springでは、Jakarta Commons Pool 1.1がちゃんとサポートされている。これは、かなり効率のいいプーリングを実装したものだ。これを使うには、アプリケーションのクラスパスをcommons-poolのJarファイルに通しておく必要がある。他にもorg.springframework.aop.target.AbstractPoolingTargetSourceを継承して他のプーリングAPIを使うこともできる。

    サンプルの設定を下記にお見せする。

    <bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject" 
        singleton="false">
        ... properties omitted
    </bean>
    
    <bean id="poolTargetSource" 
        class="org.springframework.aop.target.CommonsPoolTargetSource">
        <property name="targetBeanName" value="businessObjectTarget" />
        <property name="maxSize" value="25" />
    </bean>
    
    <bean id="businessObject" \
        class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="targetSource" ref="poolTargetSource" />
        <property name="interceptorNames" value="myInterceptor" />
    </bean>
    

    ターゲットオブジェクト--この例でいえば"businessObjectTarget" -- はプロトタイプでないとダメだ。これはPoolingtargetSourceの実装が新しいターゲットのインスタンスを生成し、必要に応じてプールを増やすためだ。AbstractPoolingTargetSourceと実際に使いたいサブクラスに関するJavadocを参照して、プロパティを確認して欲しい。例えば、maxSizeは最も基本的なプロパティであり、通常はpresentであることが保証されている。

    この場合は、"myInterceptor"はインタセプタの名前で、同じIoCコンテキストで定義されている必要がある。しかしながら、プーリングを用いるのに、インタセプタを指定する必要はない。もしプーリングしか使うつもりがなくて、他にアドバイスを使わないのであれば、interceptorNamesプロパティは一切設定してはいけない。

    プール対象のオブジェクトはどれでもorg.springframework.aop.target.PoolingConfigインタフェースへのキャストできるようにSpringを設定することは可能だ。このインタフェースによってプールのサイズと設定値がイントロダクション経由で取得することができる。アドバイザは下記のように定義すること。

    <bean id="poolConfig" 
    class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="targetObject" ref="poolTargetSource" />
        <property name="targetMethod" value="getPoolingConfigMixin" />
    </bean>
    

    このアドバイザは、AbstractPoolingTargetSourceクラスにある便利なメソッドを叩けば、,つまりMethodInvokingFactoryBeanを使って取得することができる。このアドバイザ名(ここでは、"poolConfigAdvisor")は,プールされるオブジェクトを明らかにするProxyFactoryBeanにあるインタセプタ名リストに入っている必要がある。

    このキャストは、こんな感じ。

    PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
    System.out.println("Max pool size is " + conf.getMaxSize());
    

    ステートレスサービスオブジェクトのプーリングは通常必要になることはない。我々は、これをデフォルトにすべきだとは思わない。それは、ほとんどのステートレスオブジェクトはそのままでもスレッドセーフなので、リソースがキャッシュされていると、インスタンスプーリングは問題になってしまうのだ。

    自動プロキシを使えば、もっと単純なプーリングが実現できる。任意の自動プロキシクリエータを使ったTargetSourcesを設定ことが可能だ。

    6.11.3 'Prototype" ターゲットソース

    「プロトタイプ」ターゲットソースの設定はプーリングTargetSourceによく似たものだ。この場合、新しいターゲットのインスタンスが、メソッドが起動される度に生成される。新しいオブジェクトを生成するのに要するコストが最近のJVMではそれほど高くなくても、新しいオブジェクトを(IoC依存関係を満たすように)ワイヤリングするコストがやや高いかもしれない。よって、明確な理由が無い限り、このアプローチは使うべきではない。

    これをするには、上述したpoolTargetSourceの定義を下記のように変更すればいい。(明確にするために、名前も変えてある。)

    <bean id="prototypeTargetSource" 
        class="org.springframework.aop.target.PrototypeTargetSource">
        <property name="targetBeanName" ref="businessObjectTarget" />
    </bean>
    

    プロパティは、1つのみ、ターゲットビーン名しかない。命名の一貫性を保証するのにTargetSourceの実装に継承を用いている。プーリングターゲットソースでの場合のように、ターゲットビーンはプロトタイプのビーン定義である必要がある。

    6.11.4 ThreadLocalターゲットソース

    ThreadLocalターゲットソースは、もし受け付けるリクエストごとに(つまり、スレッドごとに)オブジェクトを生成する必要がある場合に便利だ。ThreadLocalというコンセプトは、スレッドのリソースを透過的に格納するためのJDK全体に渡るの機能を提供する。ThreadLocalTargetSourceのセットアップは、他のターゲットソース用に説明したのとほぼ同じものだ。

    <bean id="threadlocalTargetSource" \
        class="org.springframework.aop.target.ThreadLocalTargetSource">
      <property name="targetBeanName" value="businessObjectTarget"/>
    </bean>
    

    マルチスレッドで、複数のクラスローダがある環境で間違った使い方をすると、ThreadLocalは、重大な問題(結果的に潜在的なメモリリークになる)を引き起こす。常にthreadlocalは何か他のクラスでラッピングし、決して直接ThreadLocalそのものを触らない様にすることを考慮すべきである(もちろん、ラッパクラスは別だ)。他に、正確にローカルリソースをスレッドにアタッチ、デタッチすることを常に覚えておくべきだ。デタッチし忘れは問題を引き起こしてしまうので、いつでもデタッチできるようにするべきだ。SpringのThreadLocalでは、これをサポートしており、他に適切な処理コードなしでもThreadLocalを使えるように、常に考慮されるべきだ。

    6.12 新規のアドバイス型を定義する

    Spring AOPは拡張できるように設計されている。インタセプションの実装戦略は現在内部的に用いられていて、すでにサポートされているインタセプションアラウンドアドバイス、ビフォアアドバイス、スローアドバイス、アフタリターンアドバイスに加えて任意のアドバイス型をサポートできる。

    org.springframework.aop.framework.adapterパッケージは、既存のCoreパッケージに手を入れなくても新しい自前のアドバイス型をサポートできるようにするためのSPIパッケージだ。自前アドバイス型が満たさないといけないことは、org.aopalliance.aop.Advicetagインタフェースを実装しないといけない、という点のみである。

    さらに詳しい情報については、org.springframework.aop.framework.adapterパッケージのJavadocを参照して欲しい。

    6.13 さらに関心のある人のための文献、リソース

    AOPの入門書としては、Ramnivas LaddadのAspectJ in Action(Manning, 2003)([訳注:http://www.amazon.co.jp/exec/obidos/ASIN/1930110936/andorecom-22]が素晴らしいのでお勧め。

    SpringのAOPに関してもっと例が欲しい場合は、Springのサンプルアプリケーションを参照して欲しい。

    もし、Spring AOPのもっと高度な機能について興味があるのであれば、テスト集を見てほしい。テストカバレッジは90%以上あるので、本ドキュメントでは触れられていない高度な機能についても触れられている。