|
对于最新的稳定版本,请使用 Spring Framework 7.0.6! |
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()") (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? {
// ...
}