スキーマベースの AOP サポート

XML ベースのフォーマットを希望する場合、Spring は aop 名前空間タグを使用してアスペクトを定義するためのサポートも提供します。@AspectJ スタイルを使用する場合とまったく同じポイントカット式とアドバイスの種類がサポートされています。このセクションでは、その構文に焦点を当て、前のセクション(@AspectJ サポート)の説明を参照して、ポイントカット式の記述とアドバイスパラメーターのバインディングについて理解します。

このセクションで説明されている aop 名前空間タグを使用するには、XML スキーマベースの構成に従って、spring-aop スキーマをインポートする必要があります。aop 名前空間にタグをインポートする方法については、AOP スキーマを参照してください。

Spring 構成内では、すべてのアスペクトおよびアドバイザー要素を <aop:config> 要素内に配置する必要があります(アプリケーションコンテキスト構成内に複数の <aop:config> 要素を含めることができます)。<aop:config> 要素には、ポイントカット、アドバイザー、アスペクト要素を含めることができます(これらはこの順序で宣言する必要があることに注意してください)。

<aop:config> スタイルの構成では、Spring の自動プロキシメカニズムを多用します。すでに BeanNameAutoProxyCreator などを使用して明示的な自動プロキシを使用している場合、これにより課題 (アドバイスが織り込まれないなど) が発生する可能性があります。推奨される使用パターンは、<aop:config> スタイルのみまたは AutoProxyCreator スタイルのみを使用し、これらを決して混合しないことです。

アスペクトを宣言する

スキーマサポートを使用する場合、アスペクトは Spring アプリケーションコンテキストで Bean として定義された通常の Java オブジェクトです。状態と動作はオブジェクトのフィールドとメソッドにキャプチャーされ、ポイントカットとアドバイス情報は XML にキャプチャーされます。

次の例に示すように、<aop:aspect> 要素を使用してアスペクトを宣言し、ref 属性を使用してバッキング Bean を参照できます。

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

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

もちろん、他の Spring Bean と同様に、アスペクト(この場合は aBean)をサポートする Bean を構成し、依存関係を注入できます。

ポイントカットの宣言

<aop:config> 要素内で名前付きポイントカットを宣言して、ポイントカット定義をいくつかのアスペクトとアドバイザで共有できます。

サービス層でのビジネスサービスの実行を表すポイントカットは、次のように定義できます。

<aop:config>

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

</aop:config>

ポイントカット式自体は、@AspectJ サポートで説明されているのと同じ AspectJ ポイントカット式言語を使用することに注意してください。スキーマベースの宣言スタイルを使用する場合、ポイントカット式内の @Aspect 型で定義された名前付きポイントカットを参照することもできます。上記のポイントカットを定義する別の方法は次のようになります。

<aop:config>

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

</aop:config>
1 名前付きポイントカット定義の共有で定義された businessService という名前のポイントカットを参照します。

次の例に示すように、アスペクト内でポイントカットを宣言することは、トップレベルのポイントカットを宣言することと非常によく似ています。

<aop:config>

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

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

		...
	</aop:aspect>

</aop:config>

@AspectJ アスペクトとほぼ同じ方法で、スキーマベースの定義スタイルを使用して宣言されたポイントカットは、ジョインポイントコンテキストを収集できます。例: 次のポイントカットは、this オブジェクトをジョインポイントコンテキストとして収集し、アドバイスに渡します。

<aop:config>

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

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

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

		...
	</aop:aspect>

</aop:config>

次のように、一致する名前のパラメーターを含めることにより、収集されたジョインポイントコンテキストを受け取るようにアドバイスを宣言する必要があります。

  • Java

  • Kotlin

public void monitor(Object service) {
	// ...
}
fun monitor(service: Any) {
	// ...
}

ポイントカットの部分式を組み合わせる場合、&amp;&amp; は XML ドキュメント内で扱いにくいため、&amp;&amp;||! の代わりに andornot キーワードをそれぞれ使用できます。例: 前のポイントカットは、次のように書く方が適切です。

<aop:config>

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

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

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

		...
	</aop:aspect>

</aop:config>

この方法で定義されたポイントカットは、XML id によって参照され、複合ポイントカットを形成するための名前付きポイントカットとして使用できないことに注意してください。スキーマベースの定義スタイルでの名前付きポイントカットのサポートは、@AspectJ スタイルで提供されるものよりも制限されています。

アドバイスを宣言する

スキーマベースの AOP サポートは、@AspectJ スタイルと同じ 5 種類のアドバイスを使用します。これらのセマンティクスはまったく同じです。

Before アドバイス

Before アドバイスは、一致したメソッドが実行される前に実行されます。次の例に示すように、<aop:before> 要素を使用して <aop:aspect> 内で宣言されます。

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

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

	...

</aop:aspect>

上記の例では、dataAccessOperation は、最上位 (<aop:config>) レベルで定義された名前付きポイントカットの id です ( ポイントカットの宣言を参照)。

@AspectJ スタイルの説明で記述されていたように、名前付きポイントカットを使用すると、コードの可読性が大幅に向上します。詳細については、名前付きポイントカット定義の共有を参照してください。

代わりにポイントカットをインラインで定義するには、次のように pointcut-ref 属性を pointcut 属性に置き換えます。

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

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

	...

</aop:aspect>

method 属性は、アドバイスの本文を提供するメソッド(doAccessCheck)を識別します。このメソッドは、アドバイスを含むアスペクト要素によって参照される Bean に対して定義する必要があります。データアクセス操作が実行される前に(ポイントカット式と一致するメソッド実行ジョインポイント)、アスペクト Bean の doAccessCheck メソッドが呼び出されます。

After Returning アドバイス

After returning アドバイスは、一致したメソッドの実行が正常に完了すると実行されます。これは、アドバイスの前と同じ方法で <aop:aspect> 内で宣言されます。次の例は、宣言方法を示しています。

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

	<aop:after-returning
		pointcut="execution(* com.xyz.dao.*.*(..))"
		method="doAccessCheck"/>

	...
</aop:aspect>

@AspectJ スタイルと同様に、アドバイス本文内で戻り値を取得できます。これを行うには、次の例に示すように、returning 属性を使用して、戻り値が渡されるパラメーターの名前を指定します。

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

	<aop:after-returning
		pointcut="execution(* com.xyz.dao.*.*(..))"
		returning="retVal"
		method="doAccessCheck"/>

	...
</aop:aspect>

doAccessCheck メソッドは、retVal という名前のパラメーターを宣言する必要があります。このパラメーターの型は、@AfterReturning について説明したのと同じ方法でマッチングを制限します。例: 次のようにメソッドシグネチャーを宣言できます。

  • Java

  • Kotlin

public void doAccessCheck(Object retVal) {...
fun doAccessCheck(retVal: Any) {...

After Throwing アドバイス

After throwing アドバイスは、一致したメソッドの実行が例外をスローして終了したときに実行されます。次の例に示すように、after-throwing 要素を使用して <aop:aspect> 内で宣言されます。

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

	<aop:after-throwing
		pointcut="execution(* com.xyz.dao.*.*(..))"
		method="doRecoveryActions"/>

	...
</aop:aspect>

@AspectJ スタイルと同様に、スローされた例外をアドバイス本文内で取得できます。これを行うには、次の例に示すように、throwing 属性を使用して、例外を渡す必要があるパラメーターの名前を指定します。

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

	<aop:after-throwing
		pointcut="execution(* com.xyz.dao.*.*(..))"
		throwing="dataAccessEx"
		method="doRecoveryActions"/>

	...
</aop:aspect>

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

  • Java

  • Kotlin

public void doRecoveryActions(DataAccessException dataAccessEx) {...
fun doRecoveryActions(dataAccessEx: DataAccessException) {...

After (Finally) アドバイス

After (finally) アドバイスは、一致したメソッドの実行がどのように終了しても実行されます。次の例に示すように、after 要素を使用して宣言できます。

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

	<aop:after
		pointcut="execution(* com.xyz.dao.*.*(..))"
		method="doReleaseLock"/>

	...
</aop:aspect>

Around アドバイス

最後の種類のアドバイスアドバイスに関するものです。Around アドバイスは、一致したメソッドの実行の「周り」で実行されます。メソッドの実行前と実行後の両方で作業を行い、いつ、どのように、メソッドが実際に実行されるかどうかを判断する機会があります。Around アドバイスは、メソッドの実行の前後でスレッドセーフな方法で状態を共有する必要がある場合によく使用されます。たとえば、タイマーの開始と停止などです。

要件を満たす最も強力でない形式のアドバイスを常に使用してください。

例: アドバイスがあなたのニーズ十分である場合は、アドバイスの周囲を使用しないでください。

aop:around 要素を使用してアドバイスを宣言できます。Advice メソッドはその戻り型として Object を宣言する必要があり、メソッドの最初のパラメーターは ProceedingJoinPoint 型である必要があります。アドバイスメソッドの本体内で、基になるメソッドを実行するために、ProceedingJoinPoint で proceed() を呼び出す必要があります。引数なしで proceed() を呼び出すと、呼び出し元の元の引数が、呼び出されたときに基になるメソッドに提供されます。高度なユースケースでは、引数の配列(Object[])を受け入れる proceed() メソッドのオーバーロードされたバリアントがあります。配列内の値は、呼び出されたときに基になるメソッドへの引数として使用されます。Object[] を使用して proceed を呼び出す際の注意事項については、Around アドバイスを参照してください。

次の例は、XML でアドバイスを宣言する方法を示しています。

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

	<aop:around
		pointcut="execution(* com.xyz.service.*.*(..))"
		method="doBasicProfiling"/>

	...
</aop:aspect>

doBasicProfiling アドバイスの実装は、次の例に示すように、@AspectJ の例とまったく同じにすることができます(もちろん、アノテーションを除く)。

  • Java

  • Kotlin

public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
	// start stopwatch
	Object retVal = pjp.proceed();
	// stop stopwatch
	return retVal;
}
fun doBasicProfiling(pjp: ProceedingJoinPoint): Any? {
	// start stopwatch
	val retVal = pjp.proceed()
	// stop stopwatch
	return pjp.proceed()
}

アドバイスパラメーター

スキーマベースの宣言スタイルは、@AspectJ サポートで説明したのと同じ方法で、アドバイスメソッドのパラメーターと名前でポイントカットパラメーターを照合することによって、完全に型付けされたアドバイスをサポートします。詳細は、アドバイスパラメーターを参照してください。アドバイスメソッド (前述の検出戦略に頼らない) の引数名を明示的に指定する場合は、アドバイス要素の arg-names 属性を使用します。この属性は、アドバイスアノテーション ( 引数名の決定の説明) の argNames 属性と同じ方法で処理されます。次の例では、XML で引数名を指定する方法を示します。

<aop:before
	pointcut="com.xyz.Pointcuts.publicMethod() and @annotation(auditable)" (1)
	method="audit"
	arg-names="auditable" />
1 ポイントカット式の組み合わせで定義された publicMethod という名前のポイントカットを参照します。

arg-names 属性は、パラメーター名のコンマ区切りリストを受け入れます。

次の XSD ベースのアプローチのやや複雑な例は、いくつかの厳密に型指定されたパラメーターと組み合わせて使用されるアドバイスを示しています。

  • Java

  • Kotlin

package com.xyz.service;

public interface PersonService {

	Person getPerson(String personName, int age);
}

public class DefaultPersonService implements PersonService {

	public Person getPerson(String name, int age) {
		return new Person(name, age);
	}
}
package com.xyz.service

interface PersonService {

	fun getPerson(personName: String, age: Int): Person
}

class DefaultPersonService : PersonService {

	fun getPerson(name: String, age: Int): Person {
		return Person(name, age)
	}
}

次はアスペクトです。profile(..) メソッドは、多くの厳密に型指定されたパラメーターを受け入れることに注意してください。最初のパラメーターは、メソッド呼び出しを進めるために使用されるジョインポイントです。このパラメーターの存在は、次の例が示すように、profile(..) が around アドバイスとして使用されることを示しています。

  • Java

  • Kotlin

package com.xyz;

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());
		}
	}
}
package com.xyz

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

class SimpleProfiler {

	fun profile(call: ProceedingJoinPoint, name: String, age: Int): Any? {
		val clock = StopWatch("Profiling for '$name' and '$age'")
		try {
			clock.start(call.toShortString())
			return call.proceed()
		} finally {
			clock.stop()
			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
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop
		https://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- this is the object that will be proxied by Spring's AOP infrastructure -->
	<bean id="personService" class="com.xyz.service.DefaultPersonService"/>

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

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

			<aop:pointcut id="theExecutionOfSomePersonServiceMethod"
				expression="execution(* com.xyz.service.PersonService.getPerson(String,int))
				and args(name, age)"/>

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

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

</beans>

次のドライバースクリプトを検討してください。

  • Java

  • Kotlin

public class Boot {

	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
		PersonService person = ctx.getBean(PersonService.class);
		person.getPerson("Pengo", 12);
	}
}
fun main() {
	val ctx = ClassPathXmlApplicationContext("beans.xml")
	val person = ctx.getBean(PersonService.class)
	person.getPerson("Pengo", 12)
}

このような Boot クラスを使用すると、標準出力で次のような出力が得られます。

StopWatch 'Profiling for 'Pengo' and '12': running time (millis) = 0
-----------------------------------------
ms     %     Task name
-----------------------------------------
00000  ?  execution(getFoo)

アドバイスのオーダー

複数のアドバイスを同じジョインポイント(実行メソッド)で実行する必要がある場合、順序付けルールはアドバイスのオーダーで説明されています。アスペクト間の優先順位は、<aop:aspect> 要素の order 属性を介して、またはアスペクトをサポートする Bean に @Order アノテーションを追加するか、Bean に Ordered インターフェースを実装させることによって決定されます。

同じ @Aspect クラスで定義されたアドバイスメソッドの優先順位規則とは対照的に、同じ <aop:aspect> 要素で定義された 2 つのアドバイスが両方とも同じジョインポイントで実行される必要がある場合、優先順位はアドバイス要素の順序によって決定されます。囲まれた <aop:aspect> 要素内で、最高から最低の優先順位で宣言されます。

例: 同じジョインポイントに適用される同じ <aop:aspect> 要素で定義された around アドバイスと before アドバイスが与えられた場合、around アドバイスが before アドバイスよりも優先されるようにするには、<aop:before> 要素の前に <aop:around> 要素を宣言する必要があります。

一般的な経験則として、同じジョインポイントに適用される同じ <aop:aspect> 要素で定義された複数のアドバイスがある場合、そのようなアドバイスメソッドを各 <aop:aspect> 要素のジョインポイントごとに 1 つのアドバイスメソッドにまとめるか、アドバイスの断片をアスペクトレベルで順序付けできる別の <aop:aspect> 要素にリファクタリングすることを検討してください。

導入

はじめに(AspectJ で型間宣言として知られる)アスペクトは、アドバイスされたオブジェクトが特定のインターフェースを実装し、それらのオブジェクトに代わってそのインターフェースの実装を提供することを宣言させます。

aop:aspect 内で aop:declare-parents 要素を使用して、導入を行うことができます。aop:declare-parents エレメントを使用して、一致する型に新しい親があることを宣言できます(そのため名前があります)。例: UsageTracked という名前のインターフェースと DefaultUsageTracked という名前のインターフェースの実装を指定すると、次のアスペクトは、サービスインターフェースのすべての実装者も UsageTracked インターフェースを実装することを宣言します。(たとえば、JMX を通じて統計を公開するため。)

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

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

	<aop:before
		pointcut="execution(* com.xyz..service.*.*(..))
			and this(usageTracked)"
			method="recordUsage"/>

</aop:aspect>

usageTracking Bean を支援するクラスには、次のメソッドが含まれます。

  • Java

  • Kotlin

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

実装されるインターフェースは、implement-interface 属性によって決定されます。types-matching 属性の値は、AspectJ 型のパターンです。一致する型の Bean は、UsageTracked インターフェースを実装します。前の例の前のアドバイスでは、サービス Bean を UsageTracked インターフェースの実装として直接使用できることに注意してください。Bean にプログラムでアクセスするには、次のように記述できます。

  • Java

  • Kotlin

UsageTracked usageTracked = context.getBean("myService", UsageTracked.class);
val usageTracked = context.getBean("myService", UsageTracked.class)

アスペクトのインスタンス化モデル

スキーマ定義のアスペクトでサポートされるインスタンス化モデルは、シングルトンモデルのみです。他のインスタンス化モデルは、将来のリリースでサポートされる可能性があります。

アドバイザー

「アドバイザー」の概念は、Spring で定義された AOP サポートに由来し、AspectJ には直接同等のものはありません。アドバイザーは、1 つのアドバイスを含む小さな自己完結型のアスペクトのようなものです。アドバイス自体は Bean によって表され、Spring のアドバイス型で説明されているアドバイスインターフェースの 1 つを実装する必要があります。アドバイザは、AspectJ ポイントカット式を利用できます。

Spring は、<aop:advisor> エレメントでアドバイザーの概念をサポートします。最も一般的には、Spring で独自のネームスペースをサポートしているトランザクションアドバイスと組み合わせて使用されます。次の例はアドバイザーを示しています。

<aop:config>

	<aop:pointcut id="businessService"
		expression="execution(* com.xyz.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 属性を使用して、ポイントカット式をインラインで定義することもできます。

アドバイスが順序付けに参加できるようにアドバイザーの優先順位を定義するには、order 属性を使用してアドバイザーの Ordered 値を定義します。

AOP スキーマの例

このセクションでは、AOP の例からの同時ロック失敗の再試行の例が、スキーマサポートで書き換えられたときの様子を示します。

ビジネスサービスの実行は、同時実行性の課題のために失敗することがあります(たとえば、デッドロックの敗者)。操作が再試行された場合、次の試行で成功する可能性があります。このような条件で再試行することが適切なビジネスサービス(競合解決のためにユーザーに戻る必要のないべき等操作)の場合、クライアントが PessimisticLockingFailureException を認識しないように透過的に操作を再試行します。これは、サービスレイヤーの複数のサービスに明確に適用される要件であるため、アスペクトを介した実装に最適です。

操作を再試行するため、proceed を複数回呼び出せるように、around advice を使用する必要があります。次のリストは、基本的なアスペクトの実装を示しています(これは、スキーマサポートを使用する通常の Java クラスです)。

  • Java

  • Kotlin

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;
	}
}
class ConcurrentOperationExecutor : Ordered {

	private val DEFAULT_MAX_RETRIES = 2

	private var maxRetries = DEFAULT_MAX_RETRIES
	private var order = 1

	fun setMaxRetries(maxRetries: Int) {
		this.maxRetries = maxRetries
	}

	override fun getOrder(): Int {
		return this.order
	}

	fun setOrder(order: Int) {
		this.order = order
	}

	fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
		var numAttempts = 0
		var lockFailureException: PessimisticLockingFailureException
		do {
			numAttempts++
			try {
				return pjp.proceed()
			} catch (ex: PessimisticLockingFailureException) {
				lockFailureException = ex
			}

		} while (numAttempts <= this.maxRetries)
		throw lockFailureException
	}
}

アスペクトは Ordered インターフェースを実装するため、アスペクトの優先順位をトランザクションアドバイスより高く設定できることに注意してください(再試行するたびに新しいトランザクションが必要です)。maxRetries および order プロパティは両方とも Spring によって構成されます。主なアクションは、doConcurrentOperation アラウンドアドバイスメソッドで発生します。続行しようとします。PessimisticLockingFailureException で失敗した場合は、すべての再試行を使い果たしていない限り、再試行します。

このクラスは、@AspectJ の例で使用したものと同じですが、アノテーションが削除されています。

対応する Spring 構成は次のとおりです。

<aop:config>

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

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

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

	</aop:aspect>

</aop:config>

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

当面は、すべてのビジネスサービスがべき等であると想定していることに注意してください。そうでない場合は、次の例に示すように、Idempotent アノテーションを導入し、アノテーションを使用してサービスオペレーションの実装にアノテーションを付けることにより、真にべき等のオペレーションのみを再試行するようにアスペクトを調整できます。

  • Java

  • Kotlin

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

べき等操作のみを再試行するアスペクトの変更には、次のように、@Idempotent 操作のみが一致するようにポイントカット式を改善することが含まれます。

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