# 5.4.7. AOP 示例

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

由于并发问题（例如，死锁失败者），业务服务的执行有时会失败。如果该操作被重试，则很可能在下一次尝试时成功。对于在这种情况下适合重试的业务服务（不需要返回给用户解决冲突的幂等操作），我们希望透明地重试操作以避免客户端看到 `PessimisticLockingFailureException`. 这是一个明确跨越服务层中多个服务的要求，因此非常适合通过切面实现。

因为我们要重试操作，所以我们需要使用环绕通知，以便我们可以多次调用`proceed`。以下清单显示了基本切面的实现：

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

请注意，切面实现了`Ordered`接口，以便我们可以将切面的优先级设置为高于事务通知（我们希望每次重试时都有一个新事务）。`maxRetries`和`order`属性都是由 Spring 配置的。主要动作`doConcurrentOperation`发生在环绕通知中。请注意，目前，我们将重试逻辑应用于每个`businessService()`. 我们尝试继续，如果我们失败了`PessimisticLockingFailureException`，我们再试一次，除非我们用尽了所有的重试尝试。

对应的Spring配置如下：

```xml
<aop:aspectj-autoproxy/>

<bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
    <property name="maxRetries" value="3"/>
    <property name="order" value="100"/>
</bean>
```

为了改进切面以使其仅重试幂等操作，我们可以定义以下 `Idempotent`注解：

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

然后我们可以使用注解来注解服务操作的实现。对仅重试幂等操作切面的更改涉及改进切入点表达式，以便仅`@Idempotent`操作匹配，如下所示：

```java
@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
    // ...
}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://doc.shiker.tech/spring-he-xin-gong-neng/5.-spring-mian-xiang-qie-mian-bian-cheng/5.4.-aspectj-zhi-chi/5.4.7.-aop-shi-li.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
