AOP の例

すべての構成要素がどのように機能するかを見てきたため、まとめて何か役に立つことをすることができます。

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

操作を再試行するため、proceed を複数回呼び出せるように、around advice を使用する必要があります。次のリストは、基本的なアスペクトの実装を示しています。

  • Java

  • Kotlin

@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.CommonPointcuts.businessService()") (1)
	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;
	}
}
1 名前付きポイントカット定義の共有で定義された businessService という名前のポイントカットを参照します。
@Aspect
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
	}

	@Around("com.xyz.CommonPointcuts.businessService()") (1)
	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
	}
}
1 名前付きポイントカット定義の共有で定義された businessService という名前のポイントカットを参照します。

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

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

<aop:aspectj-autoproxy/>

<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 操作のみが一致するようにポイントカット式を改善することが含まれます。

  • Java

  • Kotlin

@Around("execution(* com.xyz..service.*.*(..)) && " +
		"@annotation(com.xyz.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
	// ...
}
@Around("execution(* com.xyz..service.*.*(..)) && " +
		"@annotation(com.xyz.service.Idempotent)")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
	// ...
}