一个AOP示例
现在你已经了解了所有组成部分的工作原理,我们可以把它们组合起来,做一些有用的事情了。
业务服务的执行有时会因为并发问题而失败(例如,死锁)。如果重新尝试操作,下次很可能会成功。对于在这种条件下适合重试的业务服务(不需要返回给用户进行冲突解决的身份验证操作),我们希望能够透明地重试该操作,以避免客户端看到PessimisticLockingFailureException异常。这是一个明显涉及服务层中多个服务的要求,因此,通过切面实现这一功能是理想的选择。
因为我们需要重试该操作,所以必须使用某种机制(比如“around advice”),以便能够多次调用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方法中,该方法位于事务建议(advice)的周围。需要注意的是,目前我们将重试逻辑应用到每一个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
然后我们可以使用注释来标注服务操作的实现。将切面(aspect)修改为仅重试幂等操作(idempotent operations),需要精炼点切(pointcut)表达式,以便只有带有@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)
}