一個 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 介面,這樣我們就可以將切面的優先順序設定得高於事務通知(我們希望每次重試時都有一個新的事務)。maxRetries 和 order 屬性都由 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)
}