AOP 範例

既然您已經了解所有組成部分如何運作,我們可以將它們組合起來做一些有用的事情。

由於並行問題(例如,死鎖失敗者),商業服務的執行有時可能會失敗。如果重試操作,則很可能在下次嘗試時成功。對於在這種情況下適合重試的商業服務(不需要返回給使用者以解決衝突的等冪操作),我們希望透明地重試操作,以避免客户端看到 PessimisticLockingFailureException。這是一個顯然跨越服務層中多個服務的需求,因此非常適合透過切面實作。

因為我們想要重試操作,所以我們需要使用環繞通知,以便我們可以多次調用 proceed。以下清單顯示了基本的切面實作

  • 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()")
	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;
	}
}
@Aspect
class ConcurrentOperationExecutor : Ordered {

	companion object {
		private const val DEFAULT_MAX_RETRIES = 2
	}

	var maxRetries = DEFAULT_MAX_RETRIES

	private var order = 1

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

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

	@Around("com.xyz.CommonPointcuts.businessService()")
	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!!
	}

@Around("com.xyz.CommonPointcuts.businessService()") 參考了 共享命名切入點定義 中定義的 businessService 命名切入點。

請注意,切面實作了 Ordered 介面,以便我們可以將切面的優先順序設定為高於交易建議(我們希望每次重試都有新的交易)。maxRetriesorder 屬性都由 Spring 組態。主要動作發生在 doConcurrentOperation 環繞通知中。請注意,目前,我們將重試邏輯應用於每個 businessService。我們嘗試繼續進行,如果我們因 PessimisticLockingFailureException 而失敗,我們會再次嘗試,除非我們已用盡所有重試次數。

相應的 Spring 組態如下

  • Java

  • Kotlin

  • Xml

@Configuration
@EnableAspectJAutoProxy
public class ApplicationConfiguration {

	@Bean
	public ConcurrentOperationExecutor concurrentOperationExecutor() {
		ConcurrentOperationExecutor executor = new ConcurrentOperationExecutor();
		executor.setMaxRetries(3);
		executor.setOrder(100);
		return executor;

	}
}
@Configuration
@EnableAspectJAutoProxy
class ApplicationConfiguration {

	@Bean
	fun concurrentOperationExecutor() = ConcurrentOperationExecutor().apply {
		maxRetries = 3
		order = 100
	}
}
<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">

	<aop:aspectj-autoproxy />

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

</beans>

為了改進切面,使其僅重試等冪操作,我們可以定義以下 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 {
	// ...
	return pjp.proceed(pjp.getArgs());
}
@Around("execution(* com.xyz..service.*.*(..)) && " +
		"@annotation(com.xyz.service.Idempotent)")
fun doConcurrentOperation(pjp: ProceedingJoinPoint): Any? {
	// ...
	return pjp.proceed(pjp.args)
}