跳到主要内容

一个 AOP 示例

ChatGPT-4o 中英对照 An AOP Example

现在您已经了解了所有组成部分的工作原理,我们可以将它们组合在一起做一些有用的事情。

业务服务的执行有时可能会由于并发问题而失败(例如,死锁失败者)。如果重试该操作,很可能在下一次尝试时成功。对于在这种情况下适合重试的业务服务(幂等操作,不需要返回用户进行冲突解决),我们希望透明地重试操作,以避免客户端看到 PessimisticLockingFailureException。这是一个明显跨越服务层多个服务的需求,因此,通过切面来实现是理想的选择。

因为我们想要重试该操作,所以我们需要使用环绕通知,以便可以多次调用 proceed。以下列表显示了基本的方面实现:

@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;
}
}
java

@Around("com.xyz.CommonPointcuts.businessService()") 引用了在共享命名切入点定义中定义的 businessService 命名切入点。

请注意,该方面实现了 Ordered 接口,以便我们可以将该方面的优先级设置得高于事务通知(我们希望每次重试时都有一个新的事务)。maxRetriesorder 属性都是由 Spring 配置的。主要操作发生在 doConcurrentOperation 环绕通知中。请注意,目前我们将重试逻辑应用于每个 businessService。我们尝试继续操作,如果由于 PessimisticLockingFailureException 而失败,我们会重试,除非我们已经耗尽了所有的重试尝试。

相应的 Spring 配置如下:

@Configuration
@EnableAspectJAutoProxy
public class ApplicationConfiguration {

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

}
}
java

为了优化该方面以便仅重试幂等操作,我们可以定义以下 Idempotent 注解:

@Retention(RetentionPolicy.RUNTIME)
// marker annotation
public @interface Idempotent {
}
java

然后我们可以使用注解来标注服务操作的实现。更改切面以仅重试幂等操作涉及到细化切入点表达式,以便只有 @Idempotent 操作匹配,如下所示:

@Around("execution(* com.xyz..service.*.*(..)) && " +
"@annotation(com.xyz.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
// ...
return pjp.proceed(pjp.getArgs());
}
java