ポイントカットの宣言

ポイントカットは、関心のあるジョインポイントを決定するため、アドバイスの実行時期を制御できます。Spring AOP は Spring Bean のメソッド実行ジョインポイントのみをサポートするため、ポイントカットは Spring Bean でのメソッドの実行と一致すると考えることができます。ポイントカット宣言には 2 つの部分があります。名前とパラメーターを含むシグネチャーと、対象のメソッド実行を正確に決定するポイントカット式です。AOP の @AspectJ アノテーションスタイルでは、ポイントカットシグネチャーは通常のメソッド定義によって提供されます。ポイントカット式は、@Pointcut アノテーションを使用して示されます(ポイントカットシグネチャーとして機能するメソッドには、void 戻り型が必要です)。

例は、ポイントカット署名とポイントカット表現のこの区別を明確にできます。次の例では、transfer という名前のメソッドの実行に一致する anyOldTransfer という名前のポイントカットを定義しています。

  • Java

  • Kotlin

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

@Pointcut アノテーションの値を形成するポイントカット式は、通常の AspectJ ポイントカット式です。AspectJ のポイントカットの言語の完全な議論については、(ラムニバスラダッドによる行動で Colyer ら、または AspectJ の方法によってそのような Eclipse AspectJ のよう)AspectJ プログラミングガイド (英語) (および、拡張のため、AspectJ 5 開発者向けノートブック (英語) )または AspectJ の上の本の一つを参照してください。

サポートされているポイントカット指定子

Spring AOP は、ポイントカット式で使用するために、次の AspectJ ポイントカット指定子(PCD)をサポートしています。

  • execution: マッチングメソッドの実行のジョインポイント。これは、Spring AOP で作業するときに使用する主要なポイントカット指定子です。

  • within: 特定の型内のジョインポイントへの一致を制限します(Spring AOP を使用する場合、一致する型内で宣言されたメソッドの実行)。

  • this: Bean 参照(Spring AOP プロキシ)が指定された型のインスタンスであるジョインポイント(Spring AOP を使用する場合のメソッドの実行)へのマッチングを制限します。

  • target: ターゲットオブジェクト(プロキシ化されるアプリケーションオブジェクト)が指定された型のインスタンスであるジョインポイント(Spring AOP を使用する場合のメソッドの実行)へのマッチングを制限します。

  • args: 引数が指定された型のインスタンスであるジョインポイント(Spring AOP を使用する場合のメソッドの実行)へのマッチングを制限します。

  • @target: 実行中のオブジェクトのクラスに特定の型のアノテーションがあるジョインポイント(Spring AOP を使用する場合のメソッドの実行)へのマッチングを制限します。

  • @args: 渡される実際の引数の実行時の型が指定された型のアノテーションを持っているジョインポイント(Spring AOP を使用する場合のメソッドの実行)へのマッチングを制限します。

  • @within: 指定されたアノテーションを持つ型内のジョインポイントへのマッチングを制限します(Spring AOP を使用する場合、指定されたアノテーションを持つ型で宣言されたメソッドの実行)。

  • @annotation: ジョインポイントのサブジェクト(Spring AOP で実行されているメソッド)に特定のアノテーションが付いているジョインポイントにマッチングを制限します。

その他のポイントカット型

完全な AspectJ ポイントカット言語は、Spring でサポートされていない追加のポイントカット指定子をサポートします: callgetsetpreinitializationstaticinitializationinitializationhandleradviceexecutionwithincodecflowcflowbelowif@this@withincode。Spring AOP によって解釈されるポイントカット式でこれらのポイントカット指定子を使用すると、IllegalArgumentException がスローされます。

Spring AOP でサポートされるポイントカット指定子のセットは、今後のリリースで拡張され、より多くの AspectJ ポイントカット指定子をサポートする可能性があります。

Spring AOP はメソッド実行のジョインポイントのみにマッチングを制限するため、ポイントカット指定子に関する前述の説明では、AspectJ プログラミングガイドで見つけることができるよりも狭い定義を示しています。さらに、AspectJ 自体には型ベースのセマンティクスがあり、実行ジョインポイントでは、this と target の両方が同じオブジェクト(メソッドを実行するオブジェクト)を参照します。Spring AOP はプロキシベースのシステムであり、プロキシオブジェクト自体(this にバインドされている)とプロキシの背後のターゲットオブジェクト(target にバインドされている)を区別します。

Spring の AOP フレームワークのプロキシベースの性質により、ターゲットオブジェクト内の呼び出しは、定義上、インターセプトされません。JDK プロキシの場合、プロキシ上のパブリックインターフェースメソッド呼び出しのみをインターセプトできます。CGLIB を使用すると、プロキシでのパブリックおよび protected メソッド呼び出しがインターセプトされます(必要に応じて、パッケージ private メソッドも)。ただし、プロキシを介した一般的な相互作用は、常に公開署名を介して設計する必要があります。

ポイントカットの定義は通常、インターセプトされたメソッドと一致することに注意してください。プロキシを介した潜在的な非公開相互作用がある CGLIB プロキシシナリオであっても、ポイントカットが厳密に公開専用であることを意図している場合、それに応じて定義する必要があります。

インターセプションに対象クラス内のメソッド呼び出しやコンストラクターを含める必要がある場合は、Spring のプロキシベースの AOP フレームワークではなく、Spring ベースのネイティブ AspectJ ウィービングの使用を検討してください。これは AOP の使用方法が異なり、特性も異なるため、決定を下す前にウィービングについて十分に理解しておく必要があります。

Spring AOP は、bean という名前の追加の PCD もサポートします。この PCD を使用すると、ジョインポイントの一致を特定の名前付き Spring Bean または名前付き Spring Bean のセット(ワイルドカードを使用する場合)に制限できます。bean PCD の形式は次のとおりです。

bean(idOrNameOfBean)

idOrNameOfBean トークンは、任意の Spring Bean の名前にすることができます。* 文字を使用する制限されたワイルドカードサポートが提供されるため、Spring Bean の命名規則を確立する場合、bean PCD 式を記述して選択できます。他のポイントカット指定子の場合と同様に、bean PCD は、&& (および)、|| (または)、! (否定)演算子でも使用できます。

bean PCD は Spring AOP でのみサポートされ、ネイティブの AspectJ ウィービングではサポートされません。これは、AspectJ が定義する標準 PCD に対する Spring 固有の拡張であるため、@Aspect モデルで宣言されたアスペクトでは使用できません。

bean PCD は、型レベル(ウィービングベースの AOP が制限されている)だけでなく、インスタンスレベル(Spring Bean の名前の概念に基づいて構築)で動作します。インスタンスベースのポイントカット指定子は、Spring のプロキシベースの AOP フレームワークの特別な機能であり、Spring Bean ファクトリとの緊密な統合であり、特定の Bean を名前で識別するのが自然で簡単です。

ポイントカット式の組み合わせ

&&, ||! を使用して、ポイントカット式を組み合わせることができます。ポイントカット式を名前で参照することもできます。次の例は、3 つのポイントカット式を示しています。

  • Java

  • Kotlin

package com.xyz;

public class Pointcuts {

	@Pointcut("execution(public * *(..))")
	public void publicMethod() {} (1)

	@Pointcut("within(com.xyz.trading..*)")
	public void inTrading() {} (2)

	@Pointcut("publicMethod() && inTrading()")
	public void tradingOperation() {} (3)
}
1publicMethod は、メソッド実行ジョインポイントが public メソッドの実行を表す場合に一致します。
2inTrading は、メソッドの実行が取引モジュール内にある場合に一致します。
3tradingOperation は、メソッドの実行が取引モジュールの public メソッドを表す場合に一致します。
package com.xyz

class Pointcuts {

	@Pointcut("execution(public * *(..))")
	fun publicMethod() {} (1)

	@Pointcut("within(com.xyz.trading..*)")
	fun inTrading() {} (2)

	@Pointcut("publicMethod() && inTrading()")
	fun tradingOperation() {} (3)
}
1publicMethod は、メソッド実行ジョインポイントが public メソッドの実行を表す場合に一致します。
2inTrading は、メソッドの実行が取引モジュール内にある場合に一致します。
3tradingOperation は、メソッドの実行が取引モジュールの public メソッドを表す場合に一致します。

上記のように、より小さな名前付きポイントカットからより複雑なポイントカット式を作成することをお勧めします。ポイントカットを名前で参照する場合、通常の Java 可視性ルールが適用されます (同じ型の private ポイントカット、階層内の protected ポイントカット、どこでも public ポイントカットなどを表示できます)。可視性はポイントカットマッチングには影響しません。

名前付きポイントカット定義の共有

エンタープライズアプリケーションを操作する場合、開発者は多くの場合、アプリケーションのモジュールや特定の操作セットをいくつかのアスペクトから参照する必要があります。この目的のために、一般的に使用される名前付きポイントカット式をキャプチャーする専用のクラスを定義することをお勧めします。このようなクラスは通常、次の CommonPointcuts の例に似ています (ただし、クラスにどのような名前を付けるかはあなた次第です)。

  • Java

  • Kotlin

package com.xyz;

import org.aspectj.lang.annotation.Pointcut;

public class CommonPointcuts {

	/**
	 * A join point is in the web layer if the method is defined
	 * in a type in the com.xyz.web package or any sub-package
	 * under that.
	 */
	@Pointcut("within(com.xyz.web..*)")
	public void inWebLayer() {}

	/**
	 * A join point is in the service layer if the method is defined
	 * in a type in the com.xyz.service package or any sub-package
	 * under that.
	 */
	@Pointcut("within(com.xyz.service..*)")
	public void inServiceLayer() {}

	/**
	 * A join point is in the data access layer if the method is defined
	 * in a type in the com.xyz.dao package or any sub-package
	 * under that.
	 */
	@Pointcut("within(com.xyz.dao..*)")
	public void inDataAccessLayer() {}

	/**
	 * A business service is the execution of any method defined on a service
	 * interface. This definition assumes that interfaces are placed in the
	 * "service" package, and that implementation types are in sub-packages.
	 *
	 * If you group service interfaces by functional area (for example,
	 * in packages com.xyz.abc.service and com.xyz.def.service) then
	 * the pointcut expression "execution(* com.xyz..service.*.*(..))"
	 * could be used instead.
	 *
	 * Alternatively, you can write the expression using the 'bean'
	 * PCD, like so "bean(*Service)". (This assumes that you have
	 * named your Spring service beans in a consistent fashion.)
	 */
	@Pointcut("execution(* com.xyz..service.*.*(..))")
	public void businessService() {}

	/**
	 * A data access operation is the execution of any method defined on a
	 * DAO interface. This definition assumes that interfaces are placed in the
	 * "dao" package, and that implementation types are in sub-packages.
	 */
	@Pointcut("execution(* com.xyz.dao.*.*(..))")
	public void dataAccessOperation() {}

}
package com.xyz

import org.aspectj.lang.annotation.Pointcut

class CommonPointcuts {

	/**
	 * A join point is in the web layer if the method is defined
	 * in a type in the com.xyz.web package or any sub-package
	 * under that.
	 */
	@Pointcut("within(com.xyz.web..*)")
	fun inWebLayer() {}

	/**
	 * A join point is in the service layer if the method is defined
	 * in a type in the com.xyz.service package or any sub-package
	 * under that.
	 */
	@Pointcut("within(com.xyz.service..*)")
	fun inServiceLayer() {}

	/**
	 * A join point is in the data access layer if the method is defined
	 * in a type in the com.xyz.dao package or any sub-package
	 * under that.
	 */
	@Pointcut("within(com.xyz.dao..*)")
	fun inDataAccessLayer() {}

	/**
	 * A business service is the execution of any method defined on a service
	 * interface. This definition assumes that interfaces are placed in the
	 * "service" package, and that implementation types are in sub-packages.
	 *
	 * If you group service interfaces by functional area (for example,
	 * in packages com.xyz.abc.service and com.xyz.def.service) then
	 * the pointcut expression "execution(* com.xyz..service.*.*(..))"
	 * could be used instead.
	 *
	 * Alternatively, you can write the expression using the 'bean'
	 * PCD, like so "bean(*Service)". (This assumes that you have
	 * named your Spring service beans in a consistent fashion.)
	 */
	@Pointcut("execution(* com.xyz..service.*.*(..))")
	fun businessService() {}

	/**
	 * A data access operation is the execution of any method defined on a
	 * DAO interface. This definition assumes that interfaces are placed in the
	 * "dao" package, and that implementation types are in sub-packages.
	 */
	@Pointcut("execution(* com.xyz.dao.*.*(..))")
	fun dataAccessOperation() {}

}

@Pointcut メソッドの名前と組み合わせたクラスの完全修飾名を参照することにより、ポイントカット式が必要などこでも、そのようなクラスで定義されたポイントカットを参照できます。例: サービス層をトランザクションにするには、pointcut という名前の com.xyz.CommonPointcuts.businessService() を参照する次のように記述できます。

<aop:config>
	<aop:advisor
		pointcut="com.xyz.CommonPointcuts.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> 要素については、スキーマベースの AOP サポートで説明しています。トランザクション要素については、トランザクション管理で説明しています。

サンプル

Spring AOP ユーザーは、execution ポイントカット指定子を最も頻繁に使用する可能性があります。実行式の形式は次のとおりです。

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

返される型パターン(前のスニペットの ret-type-pattern)、名前パターン、パラメーターパターンを除くすべての部分はオプションです。戻り値の型パターンは、ジョインポイントが一致するためにメソッドの戻り値の型を決定します。* は、戻り値の型パターンとして最も頻繁に使用されます。すべての戻り値の型に一致します。メソッドが指定された型を返す場合のみ、完全修飾型名が一致します。名前パターンはメソッド名と一致します。* ワイルドカードを名前パターンのすべてまたは一部として使用できます。宣言型パターンを指定する場合は、末尾の . を含めて名前パターンコンポーネントに結合します。パラメーターパターンはやや複雑です。() はパラメーターを取らないメソッドと一致しますが、(..) は任意の数(ゼロ以上)のパラメーターと一致します。(*) パターンは、任意の型の 1 つのパラメーターを取るメソッドと一致します。(*,String) は、2 つのパラメーターを取るメソッドと一致します。最初のものは任意の型で、2 番目のものは String でなければなりません。詳細については、AspectJ プログラミングガイドの言語セマンティクス (英語) セクションを参照してください。

次の例は、いくつかの一般的なポイントカット式を示しています。

  • public メソッドの実行:

    execution(public * *(..))
  • set で始まる名前のメソッドの実行:

    execution(* set*(..))
  • AccountService インターフェースによって定義されたメソッドの実行:

    execution(* com.xyz.service.AccountService.*(..))
  • service パッケージで定義されたメソッドの実行:

    execution(* com.xyz.service.*.*(..))
  • サービスパッケージまたはそのサブパッケージのいずれかで定義されたメソッドの実行:

    execution(* com.xyz.service..*.*(..))
  • サービスパッケージ内の任意のジョインポイント(Spring AOP でのメソッド実行のみ):

    within(com.xyz.service.*)
  • サービスパッケージ内またはそのサブパッケージ内の任意のジョインポイント(Spring AOP のみでのメソッド実行):

    within(com.xyz.service..*)
  • プロキシが AccountService インターフェースを実装する任意のジョインポイント(Spring AOP でのメソッド実行のみ):

    this(com.xyz.service.AccountService)
    this は、バインディング形式でより一般的に使用されます。アドバイス本文でプロキシオブジェクトを使用可能にする方法については、アドバイスを宣言するセクションを参照してください。
  • ターゲットオブジェクトが AccountService インターフェースを実装する任意のジョインポイント(Spring AOP でのメソッド実行のみ):

    target(com.xyz.service.AccountService)
    target は、バインディング形式でより一般的に使用されます。アドバイス本文でターゲットオブジェクトを使用可能にする方法については、アドバイスを宣言するセクションを参照してください。
  • 単一のパラメーターを取り、実行時に渡される引数が Serializable であるジョインポイント(Spring AOP でのメソッド実行のみ):

    args(java.io.Serializable)
    args は、バインディング形式でより一般的に使用されます。アドバイス本文でメソッド引数を使用可能にする方法については、アドバイスを宣言するセクションを参照してください。

    この例で指定されたポイントカットは execution(* *(java.io.Serializable)) とは異なることに注意してください。実行時に渡される引数が Serializable の場合、args バージョンは一致し、メソッドシグネチャーが Serializable 型の単一のパラメーターを宣言する場合、実行バージョンは一致します。

  • ターゲットオブジェクトに @Transactional アノテーションがある任意のジョインポイント(Spring AOP でのみメソッドを実行):

    @target(org.springframework.transaction.annotation.Transactional)
    @target をバインディング形式で使用することもできます。アノテーションオブジェクトをアドバイス本文で使用できるようにする方法については、アドバイスを宣言するセクションを参照してください。
  • ターゲットオブジェクトの宣言された型に @Transactional アノテーションがあるジョインポイント(Spring AOP でのみメソッドを実行):

    @within(org.springframework.transaction.annotation.Transactional)
    @within をバインディング形式で使用することもできます。アノテーションオブジェクトをアドバイス本文で使用できるようにする方法については、アドバイスを宣言するセクションを参照してください。
  • 実行中のメソッドに @Transactional アノテーションがあるジョインポイント(Spring AOP でのメソッド実行のみ):

    @annotation(org.springframework.transaction.annotation.Transactional)
    @annotation をバインディング形式で使用することもできます。アノテーションオブジェクトをアドバイス本文で使用できるようにする方法については、アドバイスを宣言するセクションを参照してください。
  • 単一のパラメーターを取り、渡された引数の実行時型に @Classified アノテーションがあるジョインポイント(Spring AOP でのメソッド実行のみ):

    @args(com.xyz.security.Classified)
    @args をバインディング形式で使用することもできます。アノテーションオブジェクトをアドバイス本文で使用できるようにする方法については、アドバイスを宣言するセクションを参照してください。
  • tradeService という名前の Spring Bean 上の任意のジョインポイント(Spring AOP のみでのメソッド実行):

    bean(tradeService)
  • ワイルドカード表現 *Service に一致する名前を持つ Spring Bean のジョインポイント(Spring AOP のみでのメソッド実行):

    bean(*Service)

良いポイントカットを書く

コンパイル時に、AspectJ はマッチングパフォーマンスを最適化するためにポイントカットを処理します。コードを調べて、各ジョインポイントが(静的または動的に)特定のポイントカットと一致するかどうかを判断するのは、コストのかかるプロセスです。(動的一致とは、静的分析から完全に一致を判断することはできず、コードの実行時に実際の一致があるかどうかを判断するテストがコードに配置されることを意味します)。最初にポイントカット宣言に遭遇すると、AspectJ はそれを一致プロセスに最適な形式に書き換えます。これは何を意味するのでしょうか? 基本的に、ポイントカットは DNF(Disjunctive Normal Form)で書き直され、ポイントカットのコンポーネントは、評価が安価なコンポーネントが最初にチェックされるようにソートされます。つまり、さまざまなポイントカット指定子のパフォーマンスを理解する必要はなく、ポイントカット宣言で任意の順序で指定できます。

ただし、AspectJ は指定された内容でしか機能しません。マッチングの最適なパフォーマンスを得るには、何を達成しようとしているのかを考え、定義内で可能な限りマッチングの検索スペースを狭める必要があります。既存の指定子は、当然、種類、スコープ、コンテキストの 3 つのグループのいずれかに分類されます。

  • 種類指定子は、特定の種類のジョインポイント executiongetsetcallhandler を選択します。

  • スコープ指定子は、関心のあるジョインポイントのグループ(おそらく多くの種類)を選択します: within および withincode

  • コンテキスト指定子は、コンテキストに基づいて一致します(オプションでバインドします): thistarget@annotation

適切に記述されたポイントカットには、少なくとも最初の 2 つの型(種類とスコープ)が含まれている必要があります。ジョインポイントコンテキストに基づいて照合するコンテキスト指定子を含めるか、アドバイスで使用するためにそのコンテキストをバインドできます。種類指定子のみ、またはコンテキスト指定子のみを指定すると機能しますが、追加の処理と分析により、ウィービングパフォーマンス(使用される時間とメモリ)に影響を与える可能性があります。スコープ指定子は一致するのが非常に速く、使用することは、AspectJ がそれ以上処理されるべきではないジョインポイントのグループを非常に迅速に却下できることを意味します。良いポイントカットには、可能であれば常に 1 つ含める必要があります。