AOP 示例

现在你已经了解了所有组成部分是如何工作的,我们可以将它们放在一起做一些有用的事情。

业务服务的执行有时会由于并发问题(例如,死锁失败者)而失败。 如果重试该操作,很可能会在下一次尝试中成功。 对于适合在这种情况下重试的业务服务(不需要返回给用户进行冲突解决的幂等操作), 我们希望透明地重试该操作,以避免客户端看到 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;
  }
}

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

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

相应的 Infra 配置如下:

@Configuration
@EnableAspectJAutoProxy
public class ApplicationConfiguration {

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

}

为了改进切面,使其仅重试幂等操作,我们可能会定义以下 Idempotent 注解:

@Retention(RetentionPolicy.RUNTIME)
// 标记注解
public @interface Idempotent {
}

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

@Service
public class SampleService {

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

}