kagamihogeの日記

kagamihogeの日記です。

Spring Framework Reference Documentation 4.1.xのIII. Core Technologies 9. Aspect Oriented Programming with Springの9.5までをテキトーに訳した

http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/ をテキトーに訳した。Spring Framework Reference Documentation 4.1.xのIII. Core Technologies 8. Spring Expression Language (SpEL)をテキトーに訳した - kagamihogeの日記の続き。

9. Aspect Oriented Programming with Spring

9.1 Introduction

アスペクト指向プログラミング(AOP)はオブジェクト指向プログラミング(OOP)とは異なるプログラム構造の設計手法を提供することでOOPを補完します。OOPのモジュールの中核要素はクラスですが、AOPではアスペクトになります。アスペクトは、複数の型やオブジェクトにまたがるトランザクション管理などの、関心事のモジュール化を行います。(こうした関心事はAOPの文脈ではクロスカット(crosscutting concerns)と言います。)

Springの中核コンポーネントの一つはAOP Frameworkです。Spring IoCコンテナAOPに依存しないため、つまり必要でなければAOPを使う必要は無いですが、AOPは優れたミドルウェアソリューションを提供することでSpring IoCを補完します。

Spring 2.0 AOP
Spring 2.0はschema-based approachまたは@AspectJ annotation styleのどちらかを使用して自前のアスペクトを記述するシンプルで強力な機能を備えています。両スタイルとも、ウィービングにSpring AOPを使用する場合、完全型付アドバイス(fully typed advice)とAspect Jポイントカット(AspectJ pointcut language)の機能を提供します。
Spring 2.0スキーマと@AspectJベースAOPサポートはこのチャプターで解説します。Spring 2.0 AOPはSpring 1.2 AOPとの完全な下位互換性を維持しており、Spring 1.2 APIが提供する低レベルAOPサポートについては次の章で解説します。

Spring FrameworkにおいてAOPは以下のように使われています。

もし、プーリングなど定義済み宣言的ミドルウェアサービスやジェネリックな宣言的サービスにだけ関心がある場合、Spring AOPを直接触る必要は無いため、このチャプターはスキップしても構いません。

9.1.1 AOP concepts

AOPの中核コンセプトと用語の定義から始めます。これらの用語はSpring固有ではなく、残念なことに、AOPの用語はあまり直観的ではありません。しかし、もしSpringが固有の用語を使うと更なる混乱を招くことになります。

  • アスペクト(Aspect): 複数のクラスにまたがる関心事をモジュール化したもの。トランザクション管理は、エンタープライズJavaアプリケーションのクロスカットの好例です。Spring AOPでは、アスペクトは通常クラス(スキーマベース)で実装するか、@Aspectを付与する通常クラス(@AspectJスタイル)になります。
  • ジョインポイント(Join point): プログラム実行中におけるある一点で、メソッド実行や例外ハンドリングなどです。Spring AOPでは、ジョインポイントは常に何らかのメソッド実行を表現します。
  • アドバイス(Advice): ある特定のジョインポイントでアスペクトが取るアクション。アドバイスには"around," "before", "after"の型があります。(アドバイスの型は以下で説明します。)Springを含む多くのAOPフレームワークは、インターセプター(interceptor)としてアドバイスをモデル化しており、ジョインポイントのaroundにインターセプターのチェーンを繋げます。
  • ポイントカット(Pointcut): ジョインポイントにマッチする述語。アドバイスにポイントカットの式が関連付けられ、ポイントカットにマッチする任意のジョインポイントで実行します(たとえば、特定の名前を持つメソッド実行など)。ポイントカット式でマッチするジョインポイントというコンセプトはAOPの中核を成すもので、SpringはデフォルトではAspectJのポイントカット式言語を使います。
  • イントロダクション(Introduction): ある型の代わりに追加のメソッドやフィールドを宣言するもの。Spring AOPは任意のアドバイスオブジェクトに追加のインタフェース(と対応する実装)を導入(introduce)できます。たとえば、キャッシュ簡易化のためのIsModifiedインタフェースを、あるビーンで実装するのにイントロダクションを使用可能です(イントロダクションはAspectJコミュニティではinter-type declarationと呼ばれています)。
  • ターゲットオブジェクト(Target object): 一つ以上のアスペクトでアドバイスが行われるオブジェクト。また、アドバイスオブジェクト(advised object)とも言います。Spring AOPではランタイムプロキシで実装しているので、このオブジェクトは常にプロキシオブジェクト(proxied object)になります。
  • AOPプロキシ(AOP proxy): アスペクトの契約(アドバイスのメソッド実行など)を実装するのにAOPフレームワークが作成するオブジェクト。Spring Frameworkでは、AOPプロキシはJDKの動的プロキシかCGLIBプロキシになります。
  • ウィービング(Weaving): アドバイスオブジェクトを生成する型やオブジェクトとアスペクトをリンクすること。このリンクは、コンパイル時(AspectJコンパイラを使用)・ロード時・実行時、に行えます。Spring AOPは、他のピュアJava AOPフレームワーク同様に、実行時にウィービングを行います。

アドバイスの型について。

  • Before advice: ジョインポイントの前にアドバイスを実行し、実行を中断する機能を持たないジョインポイントは次のジョインポイントへと移る(ただし例外をスローしない場合に限る)*1
  • After returning advice: ジョインポイントの正常終了後に実行するアドバイス。たとえば、例外をスローしないメソッドが返る時など。
  • After throwing advice: 例外スローでメソッドを抜ける場合に実行するアドバイス。
  • After (finally) advice: ジョインポイントの存在(正常もしくは例外リターン)にかかわらず実行するアドバイス。
  • Around advice: メソッド呼び出しなどのジョインポイントを囲むアドバイス。これは最も強力なアドバイスです。Aroundアドバイスはメソッド呼び出しの前後にカスタムの振る舞いを実行可能です。また、このアドバイスの責務として、自前の戻り値や例外スローによってアドバイスメソッドの実行をショートカットしたり次のジョインポイントに進むかどうかの決定を実装する必要があります。

Aroundアドバイスが最も汎用的なアドバイスです。AspectJなどSpring AOPはすべてのアドバイス型を備えているので、要求する振る舞いを実装可能な最も狭いアドバイスを使うことを推奨します*2。たとえば、メソッドの戻り値のキャッシュを更新したいだけの場合、aroundアドバイスよりもafter returingアドバイスを実装するほうが、aroundアドバイスでも同じことは可能だけれども、よりベターです。最適なアドバイス型を使用することは潜在的なエラーの可能性を減らすシンプルなプログラミングモデルとなります。たとえば、aroundアドバイスで使うJoinPointproceed()メソッドを呼ばなければ、proceed()が失敗することはありません。

Spring 2.0では、アドバイスの引数はすべて静的な型付けがされるため、Object配列よりも適切な型(メソッド実行の戻り値型など)のアドバイス引数を使えます。

ポイントカットでマッチするジョインポイントのコンセプトはAOPの中核であり、旧来のインターセプターのみのテクノロジとは異なる点です。ポイントカットはアドバイスをオブジェクト指向の階層とは独立した位置づけが可能です。たとえば、宣言的トランザクション管理を提供するaroundアドバイスは複数オブジェクトにまたがるメソッドに適用が可能です(たとえば、サービスレイヤーの全ビジネスメソッドなど)。

9.1.2 Spring AOP capabilities and goals

Spring AOPはピュアJavaな実装で、特殊なコンパイル処理は必要ありません。Spring AOPはクラスローダー階層を制御する必要は無いので、Servletコンテナやアプリケーションサーバでの使用に適しています。

Spring AOPは現在メソッド実行ジョインポイント(Springビーンのメソッド実行のアドバイス)のみサポートします。コアSpring AOP APIを壊すことなくフィールドインターセプターの追加は可能ではありますが、フィールドインターセプターは実装されていません。アドバイスフィールドアクセスとアップデータジョインポイントが必要な場合、AscpectJなどを検討してください。

Spring AOPのアプローチは他の多くのAOPフレームワークとは異なります。目的は完全なAOP実装を提供することではなく(それでもSpring AOPはソレナリに有用ですが)、エンタープライズアプリケーションのよくある問題を解くためのSpring IoCAOP実装との緊密な統合です。

よって、Spring Framework AOPの機能は通常はSpring IoCコンテナと組み合わせて使います。アスペクトは通常のビーン定義シンタックスで設定(元は"autoproxying"機能を提供するものですが)し、この点が他のAOP実装とは大きく異なる点となります。極めて細粒度のオブジェクト(例:ドメインオブジェクト)など、Spring AOPでは上手いこと簡単にいかない場合があり、その場合にはAspectJが最も適しています。しかし、我々の経験では、Spring AOPエンタープライズJavaアプリケーションの大半の問題に対するエクセレントなソリューションになります。

Spring AOPは包括的なAOPソリューションを提供するAspectJとは決して競争しません。Spring AOPなどのプロキシベースフレームワークAspectJなど本格的なフレームワークも共に価値があり、競合するというより相互補完関係にあると考えています。SpringはシームレスにAspectJとSring AOPIoCを統合し、一貫性のあるSpringベースアプリケーションアーキテクチャ内でのAOPの活用を可能にしています。この統合はSpring AOP APIもしくはAOP Alliance APIには影響を与えません。Spring AOP後方互換性を維持しています。Spring AOP APIについては次のチャプターを参照してください。

Spring Frameworkの中心的な教義の一つは非侵襲性(non-invasiveness)です。このアイデアは、フレームワーク固有のクラスやインタフェースをビジネス/ドメインモデルに導入することをユーザに強制するべきでない、というものです。しかし、Spring Frameworkは各所でフレームワーク固有の依存性をユーザのコードベースに導入するオプションを提供しています。そうする理論的根拠としては、ある特定の場合にはそうした方が機能の特定箇所のコードを書いたり読んだりするのが容易になるからです。Spring Frameworkは(ほとんど)常に選択肢を提供します。環境固有のユースケースやシナリオに最もマッチするオプションを、十分な情報に基づいて決定するのはユーザの仕事です。
選択肢の一つはこのチャプターに関連しており、AOPフレームワーク(とAOPスタイル)の選択がそれに当たります。AspectJかSpring AOPもしくは両方の選択肢があり、また、@AspectJアノテーションスタイルかSpring XMLスタイルのどちらかも選べます。実際のところこのチャプターでは@AspectJスタイルを選択していますが、このことによりSpringチームはSpring XMLスタイルより@AspectJスタイルを好んでいる、とは捉えないでください。。
各スタイルの理由などのより詳細な議論についてはSection 9.4, “Choosing which AOP declaration style to use”を参照してください。

9.1.3 AOP Proxies

Spring AOPはデフォルトではAOPプロキシには標準JDKdynamic proxiesを使います。これは任意のインタフェースにプロキシの適用を可能にするものです。

また、Spring AOPはCGLIBプロキシも使用可能です。これはインタフェースではなくプロキシクラスを必要とします。CGLIBはデフォルトではビジネスオブジェクトがインタフェースを実装しない場合に使われます。クラスではなくインタフェースでプログラムするのは良いプラクティスなので、ビジネスクラスは通常は一つ以上のビジネスインタフェースを実装します。CGLIBの使用を強制可能で、その場合(レアケースであると願いますが)は、メソッドがインタフェースで宣言されないよう周知するか、メソッドに具象型クラスとしてプロキシオブジェクトを渡す必要があります。

重要なことはSpring AOPはプロキシベース(proxy-based)だということです。実装の詳細についてはSection 9.6.1, “Understanding AOP proxies”を参照してください。

9.2 @AspectJ support

@AspectJスタイルはアノテーション付与した通常のJavaクラスでアスペクトを宣言します。@AspectJスタイルはAspectJ 5リリースの一部としてAspectJ projectが導入しました。SpringはAspectJ 5と同じアノテーションを解釈し、ポイントカットのパースとマッチにAspectJが提供するライブラリを使います。AOPランタイムは今のところピュアSpring AOPで、AspectJコンパイラないしウィーバには依存していません。

AspectJコンパイラとウィーバを使うことでAspectJ言語の全機能が使用可能になります。詳細についてはSection 9.8, “Using AspectJ with Spring applications”を参照してください。

9.2.1 Enabling @AspectJ Support

Springコンフィグレーションで@AspectJのアスペクトを使うには、@AspectJベースのSpring AOP設定サポートと、アスペクトでアドバイスするかどうかに関わらずにautoproxying beansを、Springで有効化する必要があります。オートプロキシにより、あるビーンが一つ以上のアスペクトでアドバイスされているとSpringが判定した場合、メソッド実行のインターセプトおよび必要に応じてアドバイスの実行を保証するプロキシをそのビーンに自動的に生成します。

@AspectJサポートはXMLないしJavaコンフィグレーションで有効化可能です。どちらの場合でもアプリケーションのクラスパスにAspectJaspectjweaver.jar(1.6.8以降)を置く必要があります。AspectJディストリビューション'lib'ディレクトリか、Maven Centralリポジトリから取得します。

Enabling @AspectJ Support with Java configuration

Java@Configurationで@AspectJサポートを有効化するには@EnableAspectJAutoProxyを追加します。

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

}
Enabling @AspectJ Support with XML configuration

XMLで@AspectJサポートを有効化するにはaop:aspectj-autoproxy要素を使います。

<aop:aspectj-autoproxy/>

上記はChapter 34, XML Schema-based configurationで解説するスキーマサポートの使用を想定しています。aop名前空間にタグをインポートする方法はSection 34.2.7, “the aop schema”を参照してください。

9.2.2 Declaring an aspect

@AspectJサポートを有効化すると、@AspectJのアスペクト(@Aspectアノテーションを持つ)クラスをアプリケーションコンテキストに定義したビーンはSpringが自動的に検出してSpring AOPの設定に使います。以下は、特に意味は持たないですが最少な定義の例です。

アプリケーションコンテキストの通常のビーン定義が@Aspctを持つビーンクラスを指しています。

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
    <!-- configure properties of aspect here as normal -->
</bean>

NotVeryUsefulAspectクラス定義にはorg.aspectj.lang.annotation.Aspectアノテーションを付与しています。

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}

アスペクト(@Aspectを付与したクラス)は他の普通のクラス同様にメソッドとフィールドを持てます。また、ポイントカット・アドバイス・イントロダクション(inter-type)宣言も持てます。

Spring XMLやクラスパススキャン経由の自動検出など、他のSpringマネージドビーン同様に、通常のビーンとしてアスペクトクラスを登録可能です。ただし、@Aspectアノテーションはクラスパス上の自動検出には不十分なことに注意してください。このためには別途@Componentアノテーションを追加する必要があります(もしくは、Springコンポーネントスキャンに則り、qualifyするステレオタイプを使います)。

Spring AOPでは、他のアスペクトからアドバイスされるアスペクトを持つことは出来ません@Aspectはクラスをアスペクトとしてマークし、auto-proxyingから除外されます。

9.2.3 Declaring a pointcut

ポイントカットは関心事のジョインポイントを決定し、これによりアドバイスをいつ実行するかを制御できます。Spring AOPはSpringビーンに対するメソッド実行ジョインポイントのみサポートしているため、ポイントカットをSpringビーンのメソッド実行にマッチするものとして考えることが出来ます。ポイントカットは二つの要素から成り、名前と引数のシグネチャと、関心事の対象となるメソッド実行を正確に決定するポイントカット式です。AOPの@AspectJアノテーションでは、ポイントカットシグネチャは通常のメソッド定義で行い、ポイントカット式は@Pointcutで指定します(ポイントカットシグネチャの戻り値型はvoidが必須です)。

ポイントカットシグネチャとポイントカット式とを明確に区別するための例を示します。以下の例は'anyOldTransfer'という名前のポイントカットを定義し、'transfer'という名前の任意のメソッド実行にマッチします。

@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature

ポイントカット式は@Pointcutの値で指定し、このアノテーションAspectJ 5の通常のポイントカット式です。AspectJのポイントカット言語の完全な解説については、AspectJ Programming Guide(拡張については、AspectJ 5 Developers Notebook)もしくは、Colyer et. al著の"Eclipse AspectJ"やRamnivas Laddad著の"AspectJ in Action"などのAspectJ関連本を参照してください。

Supported Pointcut Designators

Spring AOPはポイントカット式に以下のAspectJポイントカット指定子(AspectJ pointcut designators(PCD))をサポートします。

他のポイントカットの型
完全なAspectJポイントカット言語はSpringではサポートしないポイントカット指示子を追加で持ちます。call, get, set, preinitialization, staticinitialization, initialization, handler, adviceexecution, withincode, cflow, cflowbelow, if, @this, @withincodeがそれに当たります。Spring AOPが解釈するポイントカット式で前述の指示子を使うとIllegalArgumentExceptionがスローされます。
Spring AOPがサポートするポイントカット指示子は将来的にAspectJの指示子をより多くサポートするよう拡張されるかもしれません。

  • execution - メソッド実行ジョインポイントのマッチ用で、Spring AOPを使う場合に最も良く使うポイントカット指示子です。
  • within - 特定型にだけジョインポイントをマッチするよう制限する(つまり、マッチした型で宣言しているメソッドの実行のみに制限する)。
  • this - ジョインポイントのマッチをビーン参照(Spring AOPプロキシ)が指定した型のインスタンスに制限する。
  • target - ジョインポイントのマッチをターゲットオブジェクト(プロキシ化されたアプリケーションオブジェクト)が指定した型のインスタンスに制限する。
  • args - ジョインポイントのマッチを引数が指定した型のインスタンスに制限する。
  • @target - ジョインポイントのマッチを指定した型のアノテーションを持つ実行オブジェクトのクラスに制限する。
  • @args - ジョインポイントのマッチを実引数の実行時の型が指定した型のアノテーション場合に制限する。
  • @within - 指定のアノテーションを持つ型内部のみにジョインポイントのマッチを制限する。
  • @annotation - ジョインポイントのサブジェクトが指定のアノテーションを持つジョインポイントにマッチを制限する。

*3

Spring AOPメソッド実行ジョインポイントだけにマッチする制限があるので、上記のポイントカット指定子の説明はAspectJプログラミングガイドのものよりも狭い定義となります。加えて、AspectJ自体は型ベースのセマンティクスを持ち、thistargetの実行ジョインポイントは、同一オブジェクト・メソッドを実行するオブジェクト、を指します。Spring AOPはプロキシベースシステムであり、プロキシオブジェクト自体(this)とプロキシの後ろにあるターゲットオブジェクト(target)は区別されます。

Spring AOPフレームワークはプロキシベースなので、protectedメソッドは、JDKプロキシでもCGLIBプロキシでも、インターセプトされませんJDKプロキシでは不可能で、CGLIBは技術的には可能なもののAOP目的には非推奨です)。結果として、ポイントカットはpublicメソッドにのみマッチします。
インターセプトでprotected/privateメソッドコンストラクタを含めたい場合、SpringのプロキシベースAOPフレームワークの代わりにSpring-driven native AspectJ weavingを検討してください。こちらは異なる特徴を持つAOPで異なる使用法なので、決定を下す前にまずウィービングのやり方を学ぶ必要があります。

Spring AOPはPCDネームドbeanをサポートします。PCDはジョインポイントのマッチを特定のネームドSpringビーンかネームドSpringビーンの組(ワイルドカード使用時)に制限します。beanPCDは以下の形式です。

bean(idOrNameOfBean)

idOrNameOfBeanトークンはSpringビーン名を取ります。*文字による限定的なワイルドカードをサポートしているので、Springビーンに命名規約を適用する場合にbeanPCD式を簡単に書くことが出来ます。As is the case with other pointcut designators,*4 beanPCDには、&&, ||, !を使えます。

前述のbean PCDはSpring AOPだけサポートされ、ネイティブAspectJウィービングではサポートされません。これはAspectJが定義する標準PCDのSpring固有の拡張です。
beanPCDは、型レベルだけ(これはウィービングベースAOPに制限される)というより、(Springのビーン名コンセプトに基づく)インスタンスレベルの操作です。インスタンスベースポイントカット指定子はSpringのプロキシベースAOPフレームワークの特殊な機能なので、Springビーンファクトリーとの統合に親和性があり、名前で特定のビーンを指示するのは自然で簡単なことです。

Combining pointcut expressions

ポイントカット式では&&, ||, !を組み合わせて使用可能です。また、名前でポイントカット式を参照可能です。以下にポイントカット式の例を三つあげました。anyPublicOperationメソッド実行ジョインポイントがpublicメソッドの場合にマッチする)。inTradingメソッド実行がtrading moduleの場合にマッチする)。tradingOperationメソッド実行がtrading moduleのpublicメソッドの場合にマッチする)

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}

@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {}

@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}

上記に示すように複雑なポイントカット式をより小さなネームドコンポーネントの組み合わせで作るのは良いやり方です。名前でポイントカットを参照する場合、通常のJava可視性ルールが適用されます(you can see private pointcuts in the same type, protected pointcuts in the hierarchy, public pointcuts anywhere and so on)。可視性はポイントカットマッチングには影響しません。

Sharing common pointcut definitions

エンタープライズアプリケーションの場合、you often want to refer to modules of the application and particular sets of operations from within several aspects.この場合には共通のポイントカット式を持つ"SystemArchitecture"アスペクトを定義するのを推奨します。こうしたアスペクトは一般的に以下のようになります。

package com.xyz.someapp;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class SystemArchitecture {

    /**
     * そのメソッドがcom.xyz.someapp.web packageかそのサブパッケージ下の型で
     * 定義されている場合はwebレイヤーのジョインポイント
     */
    @Pointcut("within(com.xyz.someapp.web..*)")
    public void inWebLayer() {}

    /**
     * そのメソッドがcom.xyz.someapp.serviceかそのサブパッケージ下の型で
     * 定義されている場合はserviceレイヤーのジョインポイント
     */
    @Pointcut("within(com.xyz.someapp.service..*)")
    public void inServiceLayer() {}

    /**
     * そのメソッドがcom.xyz.someapp.daoかそのサブパッケージ下の型で
     * 定義されている場合はdata accessレイヤーのジョインポイント
     */
    @Pointcut("within(com.xyz.someapp.dao..*)")
    public void inDataAccessLayer() {}



    /**
     * ビジネスサービスはサービスインタフェースで定義されるメソッド実行の
     * ことで、インタフェースは"service"パッケージにあり、その実装型はサブ
     * パッケージにある、と仮定します。
     * 
     * もし機能的にサービスインタフェースをグループ化する場合(例えば、
     * com.xyz.someapp.abc.serviceとcom.xyz.someapp.def.service)、ポイント
     * カット式は"execution(* com.xyz.someapp..service.*.*(..))"を使います。
     *
     * もしくは、'bean' PCDを使用して"bean(*Service)"のような式を書くことも
     * 可能です。(この場合は一貫してSpringのサービスビーンを命名している、
     * との仮定です)
     */
    @Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
    public void businessService() {}

    /**
     * データアクセス操作はdaoインタフェースに定義されているメソッド実行の
     * ことです。インタフェースは"dao"パッケージにあり、実装型はサブパッケージ
     * にある、との仮定です。
     */
    @Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
    public void dataAccessOperation() {}

}

こうしたアスペクトで定義したポイントカットは、ポイントカット式が必要な場所であればどこででも参照可能です。たとえば、サービスレイヤをトランザクショナルにするには、以下のようにします。

<aop:config>
    <aop:advisor
        pointcut="com.xyz.someapp.SystemArchitecture.businessService()"
        advice-ref="tx-advice"/>
</aop:config>

<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<aop:config><aop:advisor>要素はSection 9.3, “Schema-based AOP support”で解説します。トランザクション要素はChapter 12, Transaction Managementで解説します。

Examples

Spring AOPユーザはおおむねexecutionポイントカット指定子を使います。execution式のフォーマットは以下の通りです。

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)
            throws-pattern?)

戻り値型のパターン(上記のret-type-pattern)・nameパターン・引数パターンを除く全パターンはオプションです。戻り値型のパターンはジョインポイントにマッチするメソッドの戻り値型を決定します。最も頻繁に使われる戻り値型のパターン*は任意の戻り値型にマッチします。完全修飾型名はメソッドがその型を返す場合にのみマッチします。nameパターンはメソッド名にマッチします。nameパターン全体かその一部にワイルドカードとして*を使えます。引数パターンはやや複雑で、()は引数なしのメソッドにマッチする一方、(..)は任意の数の引数(ゼロかそれ以上)にマッチします。パターン(*)は何らかの型の引数を一つとるメソッドにマッチし、(*,String)は二つの引数を取るメソッドにマッチし、一つ目は何らかの型、二つ目はStringです。より詳細な情報についてはAspectJ Programming GuideのLanguage Semanticsを参照してください。

以下に良く使われるポイントカット式の例を示します。

execution(public * *(..))
execution(* set*(..))
  • AccountServiceで定義されているメソッドの実行
execution(* com.xyz.service.AccountService.*(..))
  • serviceパッケージに定義されているメソッドの実行
execution(* com.xyz.service.*.*(..))
  • serviceもしくはそのサブパッケージ内に定義されているメソッドの実行
execution(* com.xyz.service..*.*(..))
  • serviceパッケージ内のジョインポイント(Spring AOP内のメソッド実行のみ)
within(com.xyz.service.*)
  • serviceもしくはそのサブパッケージ内のジョインポイント(Spring AOP内のメソッド実行のみ)
within(com.xyz.service..*)
  • AccountServiceを実装するプロキシのジョインポイント(Spring AOP内のメソッド実行のみ)
this(com.xyz.service.AccountService)

thisバインディング形式で良く使われます。以降のセクションの、アドバイスボディで利用可能なプロキシオブジェクトの作成方法についてを参照してください。 * AccountServiceを実装するターゲットオブジェクトのジョインポイント

target(com.xyz.service.AccountService)

targetバインディング形式で良く使われます。アドバイスボディで利用可能なターゲットオブジェクトを作成するアドバイスについては以降のセクションを参照してください。 * 単一のパラメータを取り、かつ、実行時に渡される引数がSerializableなジョインポイント(Spring AOP内のメソッド実行のみ)

args(java.io.Serializable)

argsバインディング形式で良く使われます。アドバイスボディで利用可能なメソッド引数を作成するアドバイスについては以降のセクションを参照してください。

上記のポイントカットのサンプルはexecution(* *(java.io.Serializable))と異なる事に注意してください。argsの方は実行時に渡された引数がSerializableの場合にマッチし、executionの方はメソッドシグネチャ宣言の単一引数の型がSerializableの場合にマッチします。

  • ターゲットオブジェクトが@Transactionalを持つジョインポイント(Spring AOP内のメソッド実行のみ)
@target(org.springframework.transaction.annotation.Transactional)

@targetバインディング形式でも使用可能です。アドバイスボディで利用可能なアノテーションオブジェクトを作成するアドバイスについては以降のセクションを参照してください。

  • ターゲットオブジェクトの宣言型が@Transactionalを持つジョインポイント(Spring AOP内のメソッド実行のみ)
@within(org.springframework.transaction.annotation.Transactional)

@withinバインディング形式でも使用可能です。アドバイスボディで利用可能なアノテーションオブジェクトを作成するアドバイスについては以降のセクションを参照してください。

@annotation(org.springframework.transaction.annotation.Transactional)

@annotationバインディング形式でも使用可能です。アドバイスボディで利用可能なアノテーションオブジェクトを作成するアドバイスについては以降のセクションを参照してください。

  • 単一引数を取り、かつ、渡された引数の実行時の型が@Classifiedを含むジョインポイント(Spring AOP内のメソッド実行のみ)
@args(com.xyz.security.Classified)

@argsバインディング形式でも使用可能です。アドバイスボディで利用可能なアノテーションオブジェクトを作成するアドバイスについては以降のセクションを参照してください。

  • tradeServiceという名前を持つSpringビーンのジョインポイント(Spring AOP内のメソッド実行のみ)
bean(tradeService)
bean(*Service)
Writing good pointcuts

コンパイル時にAspectJはマッチングのパフォーマンス最適化のためにポイントカットを処理します。コードを探索し、ポイントカットに(静的か動的に)マッチする各ジョインポイントがコストの高い処理かどうかを判定します。(動的なマッチは静的解析からは完全には検出できないマッチで、and a test will be placed in the code to determine if there is an actual match when the code is running)*5。ポイントカット宣言の初回発見時に、AspectJはマッチング処理中にその箇所を最適な形式(optimal form)に書き換えます。これは何を意味するのでしょうか? 基本的に、ポイントカットはDNF(Disjunctive Normal Form)に書き換えられ、低コストと評価されたコンポーネントが最初にチェックされるように、ポイントカットのコンポーネントはソートされます。つまり、ポイントカット指示子のパフォーマンスについて考慮する必要は無く、ポイントカット宣言を好きな順番で指定して構いません。

とはいえ、AspectJは書かれた通りにだけ動作するので*6、マッチングの最適パフォーマンスのために、定義のマッチでは可能な限り検索空間が狭まるように実装すべきです。存在する指定子は三つのグループkinded, scoping, contextのいずれかに振り分けられます。

  • Kinded指定子は、ジョインポイントの特定種類を選択するのものです。例:execution, get, set, call, handler
  • Scoping指定子は、(多様な種類が存在する)関心事のジョインポイントのグループを選択するものです。例:within, withincode
  • Contextual指定子は、コンテキストベースのマッチ(とオプションでバインド)を行うものです。例:this, target, @annotation

ポイントカットは少なくとも二つのタイプ(kinded, scoping)のどちらかにすべき一方で、ジョインポイントコンテキストベースのマッチかアドバイスで使うためにコンテキストをバインドをしたい場合にはcontextual指定子を含めても構いません。kinded指定子だけ、もしくはcontextual指定子だけ、というのは動作はしますが、相当に余分な処理と分析が必要となるのでウィービングのパフォーマンス(時間とメモリ使用量)に影響を及ぼす可能性があります。 Scoping指定子は最も高速なマッチを行い、これを使うとAspectJでは余計な処理をすべきではないジョインポイントグループの解除が可能になります。これはつまり、ポイントカットは可能な限り一つのジョインポイントを含むようにするのが良い理由です。

9.2.4 Declaring advice

アドバイスはポイントカット式に関連付けられ、ポイントカットにマッチするメソッド実行の前・後・前後(around)に動作します。ポイントカット式は、namedなポイントカットへの単純な参照か、インプレース宣言するポイントカット式のどちらかになります。

Before advice

Beforeアドバイスは@Beforeを使用してアスペクトに宣言します。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

}

インプレースのポイントカット式を使う場合、上記のサンプルは以下のように書き換えられます。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

    @Before("execution(* com.xyz.myapp.dao.*.*(..))")
    public void doAccessCheck() {
        // ...
    }

}
After returning advice

After returningアドバイスは、メソッド実行の正常終了マッチ時に実行されます。@AfterReturningで宣言します。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doAccessCheck() {
        // ...
    }

}

Note: 当然ながらアスペクトには複数のアドバイス宣言・その他のメンバを作れます。サンプルでは単一のアドバイスを宣言していますが、これは説明対象以外を省いているだけです。

場合によっては、戻り値として返された実際の値でアドバイスボディにアクセスしたいこともあります。これを行うには、戻り値をバインドする@AfterReturning形式を使います。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

    @AfterReturning(
        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
        returning="retVal")
    public void doAccessCheck(Object retVal) {
        // ...
    }

}

returning属性の名前はアドバイスメソッドの引数名との合致が必須です。メソッド実行が返されると、対応する引数値としてアドバイスメソッドに戻り値が渡されます。また、returning節は指定の型の値を返すメソッド実行のみにマッチングを制約します(上記のObjectは任意の戻り値にマッチします)。

after-returningアドバイス使用時には全く異なる参照を返すことは出来ない点には注意してください。

After throwing advice

After throwingアドバイスは例外スローによってメソッド実行を抜けるのにマッチした場合に実行されます。@AfterThrowingを使用して宣言します。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doRecoveryActions() {
        // ...
    }

}

指定の型の例外がスローされた場合にだけアドバイスを実行したい場合や、アドバイスボディでスローされた例外にアクセスする必要があると考えられます。両者の制限マッチを行うにはthrowing属性を使い(必要であれば例外型にThrowableを使い)、アドバイス引数にスローされる例外をバインドします。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

    @AfterThrowing(
        pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
        throwing="ex")
    public void doRecoveryActions(DataAccessException ex) {
        // ...
    }

}

throwing属性に使う名前はアドバイスメソッドの引数名との一致が必須です。メソッド実行が例外スローで抜ける時、一致する引数の値としてアドバイスメソッドに例外が渡されます。また、throwing節は指定の型の例外(上記ではDataAccessException)をスローするメソッド実行のみにマッチングを制約します。

After (finally) advice

After (finally)アドバイスはマッチするメソッド実行が抜ける場合に常に実行されます。@Afterを使用して宣言します。Afterアドバイスは正常終了と例外リターン両方を扱えることが必須となります。一般的にはリソース解放などに使われます。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

    @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
    public void doReleaseLock() {
        // ...
    }

}
Around advice

アドバイスの最後の種類はAroundアドバイスです。Aroundアドバイスはマッチするメソッド実行の前後("around")に実行されます。メソッド実行の前後に何等かの処理を行うことが可能で、 and to determine when, how, and even if, the method actually gets to execute at all. Aroundアドバイスはスレッドセーフ下でのメソッド実行前後の状態を共有したい場合に使われることがあります(例えればタイマーの開始と終了)。要求を満たす最小限の形式のアドバイスを使用してください(すなわち、単純なbeforeアドバイスで事足りるならaroundアドバイスは使わなくて良い)。

Aroundアドバイスは@Aroundで宣言します。アドバイスメソッドの最初の引数はProceedingJoinPoint型が必須です。アドバイスボディ内部でProceedingJoinPointproceed()を呼ぶと対象メソッドの実行が行われます。また、proceedメソッドObject[]を渡すことも可能です。配列の各値はproceed時に呼び出されるメソッドの引数に使われます。

Object[]を渡す場合のproceedの振る舞いは、AspectJコンパイラコンパイルするaroundアドバイスのproceedの振る舞いと若干異なります。伝統的なAspectJ言語で書かれたaroundアドバイスでは、proceedに渡される引数の数とaroundアドバイスに渡される引数の数はマッチしなければならず(元となるジョインポイントが取る引数の数では無い)、指定の引数ポジションでproceedに渡した値が、値をバインドしたエンティティのジョインポイントのオリジナルの値を置換します*7 (Don’t worry if this doesn’t make sense right now!)。Springでのアプローチは単純でプロキシベースに馴染むもので、execution only semantics. この差異について知っておく必要があるケースは、Spring用に書かれた@AspectJアスペクトをコンパイルする場合と、AspectJコンパイラとウィーバーでproceedに引数を使う場合です。このようなアスペクトはSpring AOPAspectJで100%の互換性があり、アドバイスの引数については以降のセクションで解説します。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

    @Around("com.xyz.myapp.SystemArchitecture.businessService()")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }

}

aroundアドバイスが返す値は、メソッドの呼び出し元が戻り値として参照します。たとえば、単純なキャッシュを行うアスペクトでは、もし値を保持していればキャッシュから返し、無ければproceed()を実行します。注意点としてproceedはaroundアドバイスボディにおいて、一度だけでも複数回でも全く呼び出さなくても構いません。

Advice parameters

Springは完全型付きアドバイスを提供しており、アドバイスのシグネチャ(上述のreturningとthrowingのサンプルを参照)で必要となる引数をObject[]配列ではない宣言も可能です。そうした引数とアドバイスボディで利用可能なコンテキスト値の使い方についてこれから解説します。まず、アドバイス実行中のメソッドを参照可能なジェネリックアドバイスについて見ていきます。

Access to the current JoinPoint

任意のアドバイスメソッドで最初の引数にorg.aspectj.lang.JoinPoint型(注意点としてaroundアドバイスは最初の引数にJoinPointのサブクラスのProceedingJoinPointを宣言することは必須ですJoinPointにはいくつかのメソッドがあり、たとえばgetArgs()メソッド引数を返す)・getThis()(プロキスオブジェクトを返す)・getTarget()(ターゲットオブジェクトを返す)・getSignature()(アドバイス実行中メソッドの情報を返す)・toString()(アドバイス実行中メソッドの情報を表示する))を宣言可能です。詳細についてはjavadocを参照してください。

Passing parameters to advice

戻り値や例外の値(after returningとafter throwing使用時)のバインド方法については既に解説しました。アドバイスボディで引数を利用可能にするには、args形式のバインディングを使います。args式の型名に引数名を指定した場合、対応する引数の値がアドバイス実行時に引数として渡されます。サンプルを見ればすぐに理解できると思います。いま、最初の引数としてAccountオブジェクトを取るdaoのメソッド実行をアドバイスしたいとすると、アドバイスボディでそのaccountにアクセスしたい筈です。それには以下のようにします。

@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
    // ...
}

ポイントカット式のargs(account,..)という部分は二つの意味があります。一つ目は、少なくとも一つの引数を取るメソッド実行にだけマッチし、渡される引数はAccountインスタンス、という制限をかけています。二つ目は、account引数によって実際のAccountオブジェクトをアドバイスで利用可能にしています。

別の方法としてはジョインポイントマッチ時にAccountオブジェクトを"渡す(provides)"ポイントカットを宣言し、アドバイスからそのネームドポイントカットを参照します。以下のような使い方です。

@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
    // ...
}

詳細について興味がある方はAspectJプログラミングガイドを参照してください。

プロキシオブジェクト(this)、ターゲットオブジェクト(target)、アノテーション@within, @target, @annotation, @args)も似たような方法でバインドします。以下の例は@Auditableを付与されたメソッド実行にマッチし、監査コードを展開するものです。

まず、以下は@Auditableの定義です。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
    AuditCode value();
}

次に、以下は@Auditableメソッド実行にマッチするアドバイスです。

@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
    AuditCode code = auditable.value();
    // ...
}
Advice parameters and generics

Spring AOPはクラス宣言とメソッド引数のジェネリクスを処理可能です。以下のようなジェネリク型があるとします。

public interface Sample<T> {
    void sampleGenericMethod(T param);
    void sampleGenericCollectionMethod(Collection>T> param);
}

アドバイスの引数にメソッドインターセプトしたい型を指定することで、メソッドインターセプトを特定の引数型に制限出来ます。

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
    // Advice implementation
}

今までの説明を踏まえてもこの動作は見た目通りに明らかです。ただし、ジェネリックコレクションでは上手く動作しないことを明記しておきます。なので、以下のようなポイントカットは定義出来ません。

@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
    // Advice implementation
}

上記を動作させるにはコレクション要素を個別に検証する必要があり、null値を一般的に扱う方法は判断出来ません。上記と似たようなことをするには、引数の型をCollection<?>にして手動で要素の型をチェックします。

Determining argument names

アドバイス実行のパラメータバインディングは、(アドバイスとポイントカットの)メソッドシグネチャでパラメータを宣言するポイントカット式内部で使う名前とのマッチに依存します。パラメータ名はJavaリフレクション経由では利用不可なので、Spring AOPはパラメータ名の決定は以下のルールに従います。

  • パラメータ名をユーザが明示的に指定する場合、その指定パラメータ名が使われます。アドバイスとポイントカットの両方とも"argNames"属性オプションを持ち、これはアノテーションを付与したメソッドの引数名を指定するのに使い、この属性名は実行時に利用可能です。例を挙げます。
@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code and bean
}

一つ目の引数がJoinPoint, ProceedingJoinPoint, JoinPoint.StaticPart型の場合、"argnames"属性からこのパラメータを省略可能です。たとえば、上記の例をジョインポイントオブジェクトを受け取れるように修正する場合、"argNames"属性にジョインポイントオブジェクトを含める必要はありません。

@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
        argNames="bean,auditable")
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
    AuditCode code = auditable.value();
    // ... use code, bean, and jp
}

他のジョインポイントコンテキストを伴わないアドバイスで一つ目の引数にJoinPoint, ProceedingJoinPoint, JoinPoint.StaticPart型を使う場合は簡易な書き方が可能です。その場合、"argNames"属性は省略できます。たとえば、以下のアドバイスでは"argNames"属性を宣言する必要はありません。

@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
    // ... use jp
}
  • 'argNames'属性の使用は若干ぶきっちょなので、では'argNames'属性を指定しない場合はというと、Spring AOPはクラスのデバッグ情報を参照してローカル変数テーブルからパラメータ名を決定しようとします。デバッグ情報('-g:vars'など)で付きでコンパイルしない限りクラスには付与されません。このフラグをオンでコンパイルすると以下が発生します。(1)コードの解析(リバースエンジニアリング)が若干容易になる。(2)クラスのファイルサイズが若干大きくなる(通常そこまでの差は発生しない)。(3)未使用ローカル変数削除による最適化をコンパイラが適用しなくなる。つまり、このフラグをオンにすることで何らかの支障をきたすことは無いでしょう。

AspectJコンパイラ(ajc)がコンパイルする@AspectJのアスペクトはデバッグ情報無しであってもargNames属性を追加する必要がありません。これはコンパイラが必要な情報を保持しているためです。

  • もしコードが必須のデバッグ情報無しでコンパイルされている場合、Spring AOPは変数とパラメータのバインディングの組み合わせを推定します(たとえば、ポイントカット式に一つだけ変数がバインドされていて、アドバイスメソッドが一つのパラメータのみ取る場合、組み合わせは自明です)。もし変数のバインディングが利用可能な情報と矛盾する場合、AmbiguousBindingExceptionをスローします。
  • 上記のルールすべてが失敗する場合、IllegalArgumentExceptionをスローします。
Proceeding with arguments

引数付きでターゲットオブジェクトのメソッドを呼び出して処理を続行する方法については前述したとおりで、Spring AOPAspectJの両方で同じように動作します。このやり方はただ単にアドバイスのシグネチャメソッドパラメータの順番にバインドするだけです。たとえば、

@Around("execution(List<Account> find*(..)) && " +
        "com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " +
        "args(accountHolderNamePattern)")
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
        String accountHolderNamePattern) throws Throwable {
    String newPattern = preProcess(accountHolderNamePattern);
    return pjp.proceed(new Object[] {newPattern});
}

(上述の例のように)こうしたケースではこのバインディングのやり方になると思われます。

Advice ordering

同一ジョインポイントに複数のアドバイスを動かそうとする場合はどうなるのでしょうか? Spring AOPはアドバイス実行順序の決定にはAspectJと同一の優先順位に従います。最も高い優先順位のアドバイスが"on the way in"で最初に実行されます(二つのbeforeアドバイスがあるとき高優先順位のものが先に実行される)。ジョインポイントから"On the way out"、高優先順位のアドバイスが最後に実行される。(二つのafterアドバイスがあるとき高優先順位のものが二番目に実行される)*8

異なるアスペクトに定義した二つのアドバイスを同じジョインポイントで動かしたい場合、明示しない限り実行順序は未定義です。優先順位を指定することで実行順序を制御できます。アスペクトクラスにorg.springframework.core.Orderedを実装するかOrderアノテーションのどちらかで行います。二つのアスペクトがあるとき、Ordered.getValue()(もしくはアノテーションの値)が低い値を返すアスペクトが高優先順位になります。

同一のアスペクトに定義した二つのアドバイスを同じジョインポイントで動かしたい場合、順序が未定義です(この理由はjavacコンパイルしたクラスのリフレクション経由で宣言順序を参照する方法が無いためです)。この場合、衝突しているそれらのアドバイスメソッドをアスペクトクラスへジョインポイント別に一つのアドバイスメソッドへと分割したり、別々のアスペクトクラスにアドバイスを分割することで、アスペクトレベルで順序付けが可能なようにすることを検討してください。

9.2.5 Introductions

イントロダクション(AspectJで言うところのinter-type declarations)とは、アスペクトでアドバイスオブジェクトが指定のインタフェースを実装するよう宣言可能にするもので、アドバイスオブジェクトの代わりに指定インタフェースの実装を与えます。

イントロダクションは@DeclareParentsを使用して作成します。このアノテーションは型マッチする親を宣言するためのものです。

たとえば、指定インタフェースUsageTrackedとその実装DefaultUsageTrackedがあるとき、以下のアスペクトは、UsageTrackedも実装するすべてのサービスインタフェースを宣言するものです(この例はJMX経由で統計を出力するものです)。

@Aspect
public class UsageTracking {

    @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
    public static UsageTracked mixin;

    @Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")
    public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();
    }

}

実装されるインタフェースの決定はアノテーションを付与したフィールドの型で決定します。@DeclareParentsvalue属性がAspectJのtype patternで、型マッチする任意のビーンがUsageTrackedインタフェースを実装する、という意味です。なお、上記例のbeforeアドバイスでは、サービスビーンはUsageTrackedインタフェースの実装としても直接使用可能です。プログラム的にビーンにアクセスする場合は以下のようにします。

UsageTracked usageTracked = (UsageTracked) context.getBean("myService");

9.2.6 Aspect instantiation models

このトピックは上級向けなので、AOPをただ単に使い始めたいだけであればスキップしても構いません。

デフォルトではアプリケーションコンテキストで各アスペクトごとに単一のインスタンスが作られます。AspectJはこれをシングルトンインスタンス化モデル(singleton instantiation model)と呼んでいます。別のライフサイクルでのアスペクト定義も可能です。SpringはAspectJperthispertargetインスタンス化モデルをサポートします(percflow, percflowbelow, pertypewithinは現在非サポート)。

"perthis"アスペクトは@Aspectアノテーションperthis節を指定することで宣言します。以下がサンプルで、動作についてはこれから説明します。

@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
public class MyAspect {

    private int someState;

    @Before(com.xyz.myapp.SystemArchitecture.businessService())
    public void recordServiceUsage() {
        // ...
    }

}

'perthis'節により、ビジネスサービスを実行する個々のサービスオブジェクトごとに一つのアスペクトインスタンスが作られます(ポイントカット式にマッチするジョインポイントのthisと個々のアスペクトをバインド)。サービスオブジェクトのメソッドが最初に呼ばれるときにアスペクトインスタンスが作られます。サービスオブジェクトがスコープを抜けるときにアスペクトはスコープを抜けます。アスペクトインスタンスが生成される前は、実行されるアドバイスはありません。アスペクトインスタンスが生成された直後、マッチするジョインポイントを実行するサービスオブジェクトにアドバイスが宣言され、ただし、そのアスペクトが関連付けられたサービスオブジェクトのみが対象となります。

'pertarget'インスタンス化モデルはperthisと同様な動作をしますが、ジョインポイントにマッチするターゲットオブジェクト個々の一つのアスペクトインスタンスを生成します(but creates one aspect instance for each unique target object at matched join points.)。

9.2.7 Example

それでは構成要素を組み合わせて動作させる方法を解説していきます。

ビジネスサービスの実行は同時実行起因で失敗することがあります(デッドロックによる切り捨てなど)。この場合、オペレーションをリトライすれば次回実行時は成功する可能性があります。何らかの状態でリトライするのが適切なビジネスサービス(べき等なオペレーションはコンフリクト解決にユーザへ操作を戻す必要が無い)では、PessimisticLockingFailureExceptionをクライアントに見せることなくオペレーションのリトライを透過的に行いたいケースがあります。この要求はサービスレイヤーの複数のサービスに交差(cut across)するのは明白であり、よって、アスペクトで実装するのが理想的です。

ここではオペレーションをリトライしたいので、複数回proceedが可能なaroundアドバイスを使う必要があります。ここでは基本的なアスペクトの実装を行います。

@Aspect
public class ConcurrentOperationExecutor implements Ordered {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Around("com.xyz.myapp.SystemArchitecture.businessService()")
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }

}

注意点として、上記のアスペクトはOrderedインタフェースを実装していますが、これはトランザクションアドバイスよりも前の優先順位にするためです(リトライごとに新規トランザクションにするため)。maxRetriesorderプロパティは共にSpringが設定します。メインのアクションはdoConcurrentOperationaroundアドバイスです。ひとまずはすべてのbusinessService()sにリトライを適用します。proceed時にPessimisticLockingFailureExceptionで失敗する場合、上限回数に達するまでリトライし続けます。

対応するSpringコンフィギュレーションは以下の通りです。

<aop:aspectj-autoproxy/>

<bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
    <property name="maxRetries" value="3"/>
    <property name="order" value="100"/>
</bean>

べき等なオペレーションのみリトライ可能にするアスペクト用に、Idempotentアノテーションを定義します。

@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}

サービスオペレーションの実装に上記のアノテーションを使います。べき等オペレーションのみリトライするようアスペクトを変更します。@Idempotentオペレーションにのみマッチするようポイントカット式を書き換えます。

@Around("com.xyz.myapp.SystemArchitecture.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
    ...
}

9.3 Schema-based AOP support

XMLベースのフォーマットを使いたい場合、Springは"aop"名前空間タグによるアスペクト定義サポートをしています。@AspectJスタイルと同等なポイントカット式とアドバイスの種類をサポートしているので、このセクションではシンタックスに焦点をあて、ポイントカット式の書き方とアドバイスパラメータのバインドについては前述のセクション(Section 9.2, “@AspectJ support”)を参照してください。

本セクションで説明するaop名前空間タグを使うには、Chapter 34, XML Schema-based configurationで解説されているspring-aopスキーマをインポートしてください。aop名前空間aopタグをインポートする方法についてはSection 34.2.7, “the aop schema”を参照してください。

Springコンフィギュレーションでは、すべてのaspectとadvisor要素は<aop:config>要素内への配置が必須です(アプリケーションコンテキスト設定内に一つ以上の<aop:config>を指定可能です)。<aop:config>要素には、pointcut, advisor, aspect要素を含めることが可能です(この順序での宣言が必須)。

<aop:config>スタイルの設定はSpringのauto-proxyingに強く依存しています(makes heavy use of Spring’s auto-proxying mechanism)。もしBeanNameAutoProxyCreatorなどでauto-proxyingを明示的に使用していると、このことが問題の原因になる(ウィービングするアドバイスが無い)ことがあります。推奨事項としては、<aop:config>スタイルか、AutoProxyCreatorスタイルか、どちらかのみを使用してください。

9.3.1 Declaring an aspect

スキーマサポートを使う場合、アスペクトはSpringアプリケーションコンテキストのビーンとして定義する通常のJavaオブジェクトになります。その状態と振る舞いはオブジェクトのフィールドとメソッドで記述し、ポイントカットとアドバイスはXMLに記述します。

アスペクトは<aop:aspect>要素で宣言し、バッキングビーンはref属性で参照します。

<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>

<bean id="aBean" class="...">
    ...
</bean>

アスペクトのバッキングビーン(上記例での"aBean")は他のSpringビーン同様にDIが可能です。

9.3.2 Declaring a pointcut

名前付きポイントカットを<aop:config>要素内に宣言可能で、これにより複数のアスペクトとアドバイスでポイントカット定義を共有できます。

サービスレイヤの任意のビジネスサービスの実行を表現するポイントカットは以下のようになります。

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

</aop:config>

ポイントカット式自体はSection 9.2, “@AspectJ support”で解説したAspectJポイントカット式言語と同等なものが使えます。スキーマベースの宣言スタイルを使いたい場合、ポイントカット式内の型に定義した名前付きポイントカットを参照します。上記のポイントカットの別の定義方法は以下となります。

<aop:config>

    <aop:pointcut id="businessService"
        expression="com.xyz.myapp.SystemArchitecture.businessService()"/>

</aop:config>

SystemArchitecturethe section called “Sharing common pointcut definitions”で解説したものと同様のアスペクトです。

アスペクト内にポイントカットを宣言するのはトップレベルのポイントカットの宣言とほぼ同じです。

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..))"/>

        ...

    </aop:aspect>

</aop:config>

@AspectJアスペクトと同様に、スキーマベース定義スタイルのポイントカット宣言はジョインポイントコンテキストを使用可能です。例えば、以下のポイントカットはジョインポイントコンテキストとしてthisオブジェクトをアドバイスに渡します。

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) &amp;&amp; this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...

    </aop:aspect>

</aop:config>

アドバイスの宣言はマッチするパラメータ名を含めてジョインポイントコンテキストを受け取れるようにしておく必要があります。

public void monitor(Object service) {
    ...
}

ポイントカットサブ式(pointcut sub-expressions)を組み合わせる場合、&&はXMLでは不格好になるので、キーワードand, or, notでそれぞれ&&, ||, !に置き換え可能です。たとえば、前述のポイントカットは以下のように書き換えられます。

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) **and** this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...
    </aop:aspect>
</aop:config>

注意点として、この方法で定義したポイントカットはXML IDで参照されており、複合ポイントカットを作るための名前付きポイントカットとしての使用は出来ません。このように、スキーマベース定義スタイルの名前付きポイントカットは、@AspectJスタイルのものよりも制限されています。

9.3.3 Declaring advice

@AspectJスタイルと同様な5種類のアドバイスがあり、セマンティクスも同様な構造を持ちます。

Before advice

Beforeアドバイスはマッチしたメソッド実行前に動作します。<aop:before>は<aop:aspect>内に宣言します。

<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>

    ...

</aop:aspect>

ここでのdataAccessOperationはトップレベル(<aop:config>)に定義したポイントカットのidです。そうではなくインラインでポイントカットを定義するには、pointcut属性をpointcut-ref属性で置き換えます。

<aop:aspect id="beforeExample" ref="aBean">

    <aop:before
        pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
        method="doAccessCheck"/>

    ...

</aop:aspect>

@AspectJスタイルの項で説明したように、名前付きポイントカットを使用することで可読性が高まります。

method属性はアドバイスボディとなるメソッドdoAccessCheck)を指しています。This method must be defined for the bean referenced by the aspect element containing the advice. データアクセス操作が実行される前(ポイントカット式にマッチするメソッド実行ジョインポイント)に、アスペクトビーンの"doAccessCheck"メソッドが実行されます。

After returning advice

After returningアドバイスはマッチしたメソッド実行の正常終了時に動作します。beforeアドバイス同様に<aop:aspect>に宣言します。例としては、

<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        method="doAccessCheck"/>

    ...

</aop:aspect>

@AspectJスタイルと同様に、アドバイスボディで戻り値を取得可能です。戻り値が渡されるパラメータ名を指定するにはreturning属性を使います。

<aop:aspect id="afterReturningExample" ref="aBean">

    <aop:after-returning
        pointcut-ref="dataAccessOperation"
        returning="retVal"
        method="doAccessCheck"/>

    ...

</aop:aspect>

doAccessCheckメソッドは名前付きパラメータretValを宣言する必要があります。このパラメータの型は@AfterReturningで説明したのと同様な方法でマッチングを制約します。たとえば、メソッドシグネチャは以下のように宣言します。

public void doAccessCheck(Object retVal) {...
After throwing advice

After throwingアドバイスは例外スローによってメソッド実行を抜けるのにマッチした場合に実行されます。after-throwing要素は<aop:aspect>内に宣言します。

<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        method="doRecoveryActions"/>

    ...

</aop:aspect>

@AspectJスタイルと同様に、アドバイスボディでスローされた例外を取得可能です。渡される例外をパラメータ名に指定するにはthrowing属性を使います。

<aop:aspect id="afterThrowingExample" ref="aBean">

    <aop:after-throwing
        pointcut-ref="dataAccessOperation"
        throwing="dataAccessEx"
        method="doRecoveryActions"/>

    ...

</aop:aspect>

doRecoveryActionsメソッドは名前付きパラメータdataAccessExを宣言する必要があります。このパラメータの型は@AfterThrowingで説明したのと同様な方法でマッチングを制約します。例として、メソッドシグネチャは以下のように宣言します。

public void doRecoveryActions(DataAccessException dataAccessEx) {...
After (finally) advice

After (finally)アドバイスはマッチしたメソッド実行を抜ける場合に実行されます。after要素で宣言します。

<aop:aspect id="afterFinallyExample" ref="aBean">

    <aop:after
        pointcut-ref="dataAccessOperation"
        method="doReleaseLock"/>

    ...

</aop:aspect>
Around advice

アドバイスの最後の種類はaroundアドバイスです。aroundアドバイスはマッチしたメソッド実行の"前後"(around)で動作します。メソッド実行の前後に処理を差し込むことが可能で、メソッドを実際にすべて実行するかどうかを、ある場合や条件に従う分岐が可能です。aroundアドバイスはスレッドセーフ下のメソッド実行前後で状態を共有する必要がある場合によく使われます(例としてはタイマーの開始と停止)。なお、要求に最も合致する最も狭い形式のアドバイスを使うようにしてください。beforeアドバイスでいいところを手抜きしてaroundアドバイスを使ってはいけません。

aroundアドバイスはaop:around要素で宣言します。アドバイスメソッドの最初のパラメータはProceedingJoinPoint型が必須です。アドバイスボディで、ProceedingJoinPointproceed()を呼ぶとアドバイス下のメソッドが実行されます。また、proceedメソッドObject[]を渡すことも可能で、配列値はproceed時のメソッド実行の引数として使われます。Object[]を利用したproceed呼び出しについてはthe section called “Around advice”を参照してください。

<aop:aspect id="aroundExample" ref="aBean">

    <aop:around
        pointcut-ref="businessService"
        method="doBasicProfiling"/>

    ...

</aop:aspect>

doBasicProfilingアドバイスの実装は@AspectJの例と同様になります(ただしアノテーションは除く)。

public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
    // start stopwatch
    Object retVal = pjp.proceed();
    // stop stopwatch
    return retVal;
}
Advice parameters

スキーマベース宣言スタイルは@AspectJで解説したのと同様な方法の完全型付アドバイスをサポートしています。これは、アドバイスメソッドのパラメータとのby nameでポイントカットとのマッチングにより行われます*9。詳細はthe section called “Advice parameters”を参照してください。もしアドバイスメソッドの引数名を明示的に指定したい場合(前述の検出方法に依存しない)、アドバイス要素のarg-names属性を使います。これはthe section called “Determining argument names”で解説したアドバイスのアノテーションの"argNames"属性と同じ方法で扱われます。

<aop:before
    pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
    method="audit"
    arg-names="auditable"/>

arg-names属性はパラメータ名のカンマ区切りリストです。

XSDベースのやや複雑な例として、以下に複数の強い型付パラメータを組み合わせて使うaroundアドバイスについて解説します。

package x.y.service;

public interface FooService {

    Foo getFoo(String fooName, int age);
}

public class DefaultFooService implements FooService {

    public Foo getFoo(String name, int age) {
        return new Foo(name, age);
    }
}

次はアスペクトです。profile(..)メソッド複数の強い型付パラメータを受け取る点に注意してください。ジョインポイントの最初の引数はproceedでメソッド呼び出しを行うのに使われます。このパラメータの存在がaroundアドバイスとしてprofile(..)を使うことを示しています。

package x.y;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;

public class SimpleProfiler {

    public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
        StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
        try {
            clock.start(call.toShortString());
            return call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
    }
}

最後に、特定のジョインポイントで上記のアドバイスを実行するのに必要なXMLコンフィグレーションを示します。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- this is the object that will be proxied by Spring's AOP infrastructure -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- this is the actual advice itself -->
    <bean id="profiler" class="x.y.SimpleProfiler"/>

    <aop:config>
        <aop:aspect ref="profiler">

            <aop:pointcut id="theExecutionOfSomeFooServiceMethod"
                expression="execution(* x.y.service.FooService.getFoo(String,int))
                and args(name, age)"/>

            <aop:around pointcut-ref="theExecutionOfSomeFooServiceMethod"
                method="profile"/>

        </aop:aspect>
    </aop:config>

</beans>

上記を実行するだけの簡単なコードは以下の通りで、標準出力に結果が出力されます。

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import x.y.service.FooService;

public final class Boot {

    public static void main(final String[] args) throws Exception {
        BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
        FooService foo = (FooService) ctx.getBean("fooService");
        foo.getFoo("Pengo", 12);
    }
}
StopWatch Profiling for 'Pengo and 12': running time (millis) = 0
-----------------------------------------
ms     %     Task name
-----------------------------------------
00000  ?  execution(getFoo)
Advice ordering

同一ジョインポイントに複数のアドバイスを実行したい場合、順序ルールはthe section called “Advice ordering”したものと同様です。アスペクト間の優先順位決定は、アスペクト下のビーンにOrderアノテーションを追加するか、Orderedインタフェースをビーンに実装することで行います。

9.3.4 Introductions

イントロダクション(AspectJで言うところのinter-type declarations)により指定のインタフェースを実装するアドバイスオブジェクトの宣言が可能です。また、それらのアドバイスオブジェクトの代わりにインタフェースの実装を与えられます。

イントロダクションはaop:aspect内のaop:declare-parents要素で宣言します。この要素はマッチする型が親を持つということを宣言するのに使います。たとえば、指定インタフェースUsageTrackedと、その実装DefaultUsageTrackedがあり、以下のアスペクトでサービスインタフェースの全実装はUsageTrackedを実装すると宣言しています。(例としてはJMX経由で統計を出力するためなどの使う)

<aop:aspect id="usageTrackerAspect" ref="usageTracking">

    <aop:declare-parents
        types-matching="com.xzy.myapp.service.*+"
        implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
        default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>

    <aop:before
        pointcut="com.xyz.myapp.SystemArchitecture.businessService()
            and this(usageTracked)"
            method="recordUsage"/>

</aop:aspect>

usageTrackingビーン下のクラスは以下のメソッドを持ちます。

public void recordUsage(UsageTracked usageTracked) {
    usageTracked.incrementUseCount();
}

インタフェース実装の決定はimplement-interface属性で行います。types-matching属性値はAspectJのタイプパターンで、マッチする型のビーンはUsageTrackedインタフェースを実装します。上記例のbeforeアドバイスでは、サービスビーンはUsageTrackedインタフェース実装として使うことも可能です。プログラム的にビーンにアクセスするには以下のようなコードを書きます。

UsageTracked usageTracked = (UsageTracked) context.getBean("myService");

9.3.5 Aspect instantiation models

スキーマ定義アスペクトでのインスタンス化モデルではシングルトンモデルのみサポートします。他のモデルについては将来リリースでサポートされるかもしれません。

9.3.6 Advisors

"advisors"の考え方はSpring 1.2で定義されたAOPサポートから引き継がれたもので、AspectJと全く同等というわけではありません。advisorとは、アドバイスの小さいパーツを持つ自己包含アスペクト、といったようなものです。アドバイス自体はビーンで表現し、Section 10.3.2, “Advice types in Spring”で解説したアドバイスインタフェースのうち一つを実装することが必須です。advisorはAspectJポイントカット式を利用可能です。

Springは<aop:advisor>要素でadvisorをサポートしています。良く見かけるものとしてはトランザクショナルなアドバイスを組み合わせるもので、Springでは自前の名前空間をサポートします。

<aop:config>

    <aop:pointcut id="businessService"
        expression="execution(* com.xyz.myapp.service.*.*(..))"/>

    <aop:advisor
        pointcut-ref="businessService"
        advice-ref="tx-advice"/>

</aop:config>

<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

上記例のpointcut-ref属性同様、インラインでポイントカット式を定義するのにpointcut属性を使う事も可能です。

アドバイスを順序に加えてadvisorの優先順位を定義するには、advisorでOrderedを定義するorder属性を使います。

9.3.7 Example

Section 9.2.7, “Example”で解説した同期化失敗時リトライの例をスキーマベースで書き直す場合を見ていきます。

ビジネスサービスの実行は同時実行起因で失敗することがあります(デッドロックによる切り捨てなど)。この場合、オペレーションをリトライすれば次回実行時は成功する可能性があります。何らかの状態でリトライするのが適切なビジネスサービス(べき等なオペレーションはコンフリクト解決にユーザへ操作を戻す必要が無い)では、PessimisticLockingFailureExceptionをクライアントに見せることなくオペレーションのリトライを透過的に行いたいケースがあります。この要求はサービスレイヤーの複数のサービスに交差(cut across)するのは明白であり、よって、アスペクトで実装するのが理想的です。

ここではオペレーションをリトライしたいので、複数回proceedが可能なaroundアドバイスを使う必要があります。ここでは基本的なアスペクトの実装を行います。

public class ConcurrentOperationExecutor implements Ordered {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }

}

注意点として、上記のアスペクトはOrderedインタフェースを実装していますが、これはトランザクションアドバイスよりも前の優先順位にするためです(リトライごとに新規トランザクションにするため)。maxRetriesorderプロパティは共にSpringが設定します。メインのアクションはdoConcurrentOperationaroundアドバイスです。ひとまずはすべてのbusinessService()sにリトライを適用します。proceed時にPessimisticLockingFailureExceptionで失敗する場合、上限回数に達するまでリトライし続けます。

このクラスは@AspectJの例のものとほぼ同一ですが、アノテーションは無くなっています。

対応するSpringコンフィギュレーションは以下の通りです。

<aop:config>

    <aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">

        <aop:pointcut id="idempotentOperation"
            expression="execution(* com.xyz.myapp.service.*.*(..))"/>

        <aop:around
            pointcut-ref="idempotentOperation"
            method="doConcurrentOperation"/>

    </aop:aspect>

</aop:config>

<bean id="concurrentOperationExecutor"
    class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
        <property name="maxRetries" value="3"/>
        <property name="order" value="100"/>
</bean>

ここではすべてのビジネスサービスがべき等であると仮定しています。そうでない場合はアスペクトを変更し、Idempotentアノテーションを追加して純粋なべき等操作のみリトライ可能にします。

@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}

このアノテーションをサービスオペレーションの実装に付与します。べき等操作のみリトライするようアスペクトを変更するには、ポイントカット式を編集し、@Idempotentオペレーションにのみマッチするようにします。

<aop:pointcut id="idempotentOperation"
        expression="execution(* com.xyz.myapp.service.*.*(..)) and
        @annotation(com.xyz.myapp.service.Idempotent)"/>

9.4 Choosing which AOP declaration style to use

要求を実装する最良の方法としてアスペクトを選択したあと、Spring AOPもしくはAspectJAspect言語スタイル・@AspectJアノテーションスタイル・Spring XMLスタイル、のいずれを選べば良いのでしょうか。この意思決定に関しては、アプリケーション要求・開発ツール・チームのAOP習熟度を含む、多数の要因に左右されます。

9.4.1 Spring AOP or full AspectJ?

最も簡単な手法を選びましょう。Spring AOPは完全なAspectJを使うよりかはシンプルで、その理由はAspectJコンパイラ/ウィーバーを開発およびビルドプロセスへ導入することが不要だからです。Springビーンの実行のみアドバイスするなら、Spring AOPが賢明な選択です。Springコンテナで非マネージドなオブジェクト(ドメインオブジェクトなど)をアドバイスする場合はAspectJを使う必要があります。また、単純なメソッド実行以外のジョインポイント(フィールドのget/setポイントなど)をアドバイスしたい場合もAspectJを使う必要があります。

AspectJの場合、AspectJ言語シンタックス("code style"と呼ばれるもの)か@AspectJアノテーションスタイルのどちらかを選択します。まず明らかなこととして、Java 5+を使わないのであれば……選択の余地なくcode styleになります。設計においてアスペクトが大きな役割を担い、EclipseAspectJ Development Tools (AJDT)を使用可能であれば、AspectJ言語シンタックスが望ましい選択肢です。何故ならこの言語はアスペクト記述用に設計されたからです。Eclipseを使わない場合、もしくは、アプリケーションで大きな役割を持たない小数のアスペクトのみであれば、@AspectJスタイルをお好みのIDEの標準的なJavaコンパイルに関連付けさせ、ビルドスクリプトにアスペクトのウィービングを追加することも可能です。

9.4.2 @AspectJ or XML for Spring AOP?

Spring AOPを使うことに決めた場合、次に@AspectJXMLスタイルのどちらかを選びます。ここには考慮すべきいくつかのトレードオフがあります。

XMLスタイルは既存のSpringユーザにとっては馴染みやすく、POJOの裏付けがあります*10エンタープライズサービスの設定ツールとしてAOPを使う場合、XMLは選択肢の一つになりえます(a good test is whether you consider the pointcut expression to be a part of your configuration you might want to change independently)。XMLスタイルでは設定からシステム上に現在存在するアスペクトが明確に読み取れます(異論は認める)。

XMLスタイルには二つの欠点があります。一つ目は、単一の場所で扱う要求の実装を完全にはカプセル化出来ないことです。DRY原則は、システム上での知識表現は、信頼出来て(authoritative)・矛盾なく・一か所にあるべき、と説いています。XMLスタイルの場合、要求が実装されている詳細は、設定ファイル上のXMLとバッキングビーンクラスの宣言に分割されます。@AspectJスタイルの場合は単一モジュールであるアスペクトにカプセル化されます。二つ目は、XMLスタイルは@AspectJよりも表現能力が限定されます。インスタンス化モデルは"singleton"アスペクトのみのサポートで、XMLで宣言した名前付きポイントカットの組み合わせも不可能です。例えば、@AspectJスタイルでは以下のような記述が可能です。

@Pointcut(execution(* get*()))
public void propertyAccess() {}

@Pointcut(execution(org.xyz.Account+ *(..))
public void operationReturningAnAccount() {}

@Pointcut(propertyAccess() && operationReturningAnAccount())
public void accountPropertyAccess() {}

XMLスタイルで最初の二つのポイントカットを宣言することは可能です。

<aop:pointcut id="propertyAccess"
        expression="execution(* get*())"/>

<aop:pointcut id="operationReturningAnAccount"
        expression="execution(org.xyz.Account+ *(..))"/>

XMLへ変換する場合、これらの定義を組み合わせたaccountPropertyAccessポイントカットは定義出来ません。

@AspectJスタイルは複数インスタンス化モデルと、より豊富なポイントカットの組み合わせをサポートします。モジュール化したユニットとしてアスペクトを扱える利点もあります。また、@AspectJアスペクトはSpring AOPAspectJ双方が解釈可能な利点もあります。よって、後になって追加要求を実装するのにAspectJが必要となる場合、AspectJベースに移行することは容易です。つまるところ、Springチームは、エンタープライズサービスのシンプルな"configuration"以上のことをするアスペクトの有無に関わらず、@AspectJスタイルを推奨します。

9.5 Mixing aspect types

異なる種類のアスペクトスタイルをミックスすることが可能です。autoproxyingでの@AspectJスタイルのアスペクト・スキーマ定義<aop:aspect>アスペクト・<aop:advisor>、また、同様な設定によるSpring 1.2スタイルのプロキシとインターセプターを含みます。これらは全て同じメカニズムで実装されており、同時に存在させる技術的困難さはありません*11

*1:Advice that executes before a join point, but which does not have the ability to prevent execution flow proceeding to the join pointが原文。ジョインポイントが実行中断機能を持たないのか、設定するジョインポイントによっては実行中断機能を持つものも持たないものあるのか、ちょっと訳に自信が無い。a join point, butだから後者だとは思うが…

*2:the least powerful advice typeだけどちょっとニュアンスが違うだろうか?

*3:上記の一覧はかなり微妙な訳な自覚はある……

*4:よくわからん

*5:よくわからん

*6:However, AspectJ can only work with what it is toldだが、さすがにこれは意訳しすぎかなぁ? たぶんだけど、最適化でコードが変更されるとはいっても基本的には書いた通りに動くので、goodな作法にはなるべく従ってね、という意味合いだとは思うのだが……

*7:and the value passed to proceed in a given argument position supplants the original value at the join point for the entity the value was bound to 上手く訳せてる自信が無い

*8:on the way inとかが上手く訳せなかったので、良い訳とは言えない…

*9:by matching pointcut parameters by name against advice method parametersが原文。ちっとうまいこと訳せず

*10:it is backed by genuine POJOsが原文。アノテーションが無いモノホンのPOJOという意味と思われる

*11:will co-exist without any difficulty.が原文。any difficultyを「技術的困難さ」にするのはちっと言いすぎか?