# 6.2. Spring 中的 Advice API

现在我们可以检查 Spring AOP 如何处理通知。

**6.2.1. 切面生命周期**

每个切面都是一个 Spring bean。切面实例可以在所有切面对象之间共享，或者对于每个切面对象都是唯一的。这对应于每个类或每个实例的切面。

每类切面最常使用。它适用于通用切面，例如事务Advisor。这些不依赖于代理对象的状态或添加新状态。它们仅作用于方法和参数。

每个实例的切面适用于介绍，以支持 mixins。在这种情况下，切面将状态添加到代理对象。

您可以在同一个 AOP 代理中混合使用共享切面和实例切面。

**6.2.2. spring的切面类型**

Spring 提供了几种切面类型，并且可以扩展以支持任意切面类型。本节介绍基本概念和标准通知类型。

**环绕切面**

Spring 中最基本的通知类型是围绕通知的拦截。

Spring 与使用方法拦截的环绕通知的 AOP `Alliance`接口兼容。实现`MethodInterceptor`和围绕通知实现的类也应该实现以下接口：

```java
public interface MethodInterceptor extends Interceptor {

    Object invoke(MethodInvocation invocation) throws Throwable;
}
```

`invoke()`方法的参数`MethodInvocation`暴露了被调用的方法、目标连接点、AOP 代理和方法的参数。 `invoke()`方法应该返回调用的结果：连接点的返回值。

以下示例显示了一个简单的`MethodInterceptor`实现：

```java
public class DebugInterceptor implements MethodInterceptor {

    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("Before: invocation=[" + invocation + "]");
        Object rval = invocation.proceed();
        System.out.println("Invocation returned");
        return rval;
    }
}
```

注意对`MethodInvocation`的`proceed()`方法的调用。这沿着拦截器链向连接点前进。大多数拦截器调用此方法并返回其返回值。但是， `MethodInterceptor`与任何周围的切面一样，可以返回不同的值或抛出异常，而不是调用proceed 方法。但是，您不想在没有充分理由的情况下执行此操作。

`MethodInterceptor`实现提供与其他符合 AOP 联盟的 AOP 实现的互操作性。本节其余部分讨论的其他通知类型实现了常见的 AOP 概念，但以特定于 Spring 的方式。虽然使用最具体的通知类型有优势，但如果您可能希望在另一个 AOP 框架中运行方面，请坚持使用`MethodInterceptor`的环绕通知。请注意，切入点目前在框架之间不能互操作，AOP 联盟目前没有定义切入点接口。

**前置通知**

更简单的通知类型是之前的通知。这不需要`MethodInvocation` 对象，因为它只在进入方法之前被调用。

before 通知的主要优点是不需要调用`proceed()` 方法，因此不会因疏忽而未能沿拦截器链继续执行。

以下清单显示了该`MethodBeforeAdvice`接口：

```java
public interface MethodBeforeAdvice extends BeforeAdvice {

    void before(Method m, Object[] args, Object target) throws Throwable;
}
```

（Spring 的 API 设计允许在通知之前使用字段，尽管通常的对象适用于字段拦截，而且 Spring 不太可能实现它。）

请注意，返回类型是`void`. 之前通知可以在连接点运行之前插入自定义行为，但不能更改返回值。如果之前的通知抛出异常，它会停止拦截器链的进一步执行。异常会沿拦截器链向上传播。如果未选中或在调用方法的签名上，则直接将其传递给客户端。否则，它会被 AOP 代理包装在未经检查的异常中。

以下示例显示了 Spring 中的 before 通知，它计算所有方法调用：

```java
public class CountingBeforeAdvice implements MethodBeforeAdvice {

    private int count;

    public void before(Method m, Object[] args, Object target) throws Throwable {
        ++count;
    }

    public int getCount() {
        return count;
    }
}
```

前置通知可以与任何切入点一起使用。

**异常通知**

如果连接点抛出异常，则在连接点返回后调用 Throws 通知。Spring 提供了类型化的 throws 切面。请注意，这意味着该 `org.springframework.aop.ThrowsAdvice`接口不包含任何方法。它是一个标签接口，标识给定对象实现了一个或多个类型化的 throws 切面方法。这些应采用以下形式：

```java
afterThrowing([Method, args, target], subclassOfThrowable)
```

只有最后一个参数是必需的。方法签名可能有一个或四个参数，这取决于通知方法是否对方法和参数感兴趣。接下来的两个清单显示了作为 throws 切面示例的类。

如果抛出`RemoteException`（包括来自子类），则会调用以下切面：

```java
public class RemoteThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }
}
```

与前面的通知不同，下一个示例声明了四个参数，以便它可以访问调用的方法、方法参数和目标对象。如果抛出`ServletException`，则调用以下切面：

```java
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}
```

最后一个示例说明了如何在处理`RemoteException`和`ServletException`的单个类中使用这两种方法。任意数量的 throws 切面方法可以组合在一个类中。以下清单显示了最后一个示例：

```java
public static class CombinedThrowsAdvice implements ThrowsAdvice {

    public void afterThrowing(RemoteException ex) throws Throwable {
        // Do something with remote exception
    }

    public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
        // Do something with all arguments
    }
}
```

如果 throws-advice 方法本身抛出异常，它会覆盖原始异常（即，它会更改抛出给用户的异常）。覆盖异常通常是 RuntimeException，它与任何方法签名兼容。但是，如果 throws-advice 方法抛出检查异常，它必须匹配目标方法声明的异常，因此在某种程度上与特定目标方法签名耦合。*不要抛出与目标方法的签名不兼容的未声明的检查异常！*

抛出的切面可以与任何切入点一起使用。

**返回通知**

Spring 中的后返回通知必须实现该 `org.springframework.aop.AfterReturningAdvice`接口，如下清单所示：

```java
public interface AfterReturningAdvice extends Advice {

    void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable;
}
```

返回后的通知可以访问返回值（它不能修改）、调用的方法、方法的参数和目标。

返回通知后的以下内容计算所有未引发异常的成功方法调用：

```java
public class CountingAfterReturningAdvice implements AfterReturningAdvice {

    private int count;

    public void afterReturning(Object returnValue, Method m, Object[] args, Object target)
            throws Throwable {
        ++count;
    }

    public int getCount() {
        return count;
    }
}
```

此切面不会更改执行路径。如果它抛出异常，它会被抛出拦截器链而不是返回值。

返回后的通知可以与任何切入点一起使用。

**引入通知**

Spring 将引入通知视为一种特殊的拦截通知。

简介需要实现以下接口的 `IntroductionAdvisor`和 `IntroductionInterceptor`：

```java
public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}
```

从AOP联盟`MethodInterceptor`接口继承的`invoke()`方法必须实现引入。也就是说，如果被调用的方法在引入的接口上，则引入拦截器负责处理方法调用——它不能调用`proceed()`。

引入通知不能与任何切入点一起使用，因为它仅适用于类，而不是方法级别。您只能将引入通知与`IntroductionAdvisor`一起使用 ，它具有以下方法：

```java
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

    Class<?>[] getInterfaces();
}
```

没有`MethodMatcher`，因此，没有`Pointcut`与介绍切面相关联。只有类过滤是合乎逻辑的。

该`getInterfaces()`方法返回此Advisor引入的接口。

该`validateInterfaces()`方法用于内部查看引入的接口是否可以被`IntroductionInterceptor`配置的.

考虑一个来自 Spring 测试套件的示例，假设我们想要为一个或多个对象引入以下接口：

```java
public interface Lockable {
    void lock();
    void unlock();
    boolean locked();
}
```

这说明了一个混合。我们希望能够将切面对象转换为`Lockable`，无论它们的类型如何，并调用锁定和解锁方法。如果我们调用该`lock()`方法，我们希望所有的 setter 方法都抛出一个`LockedException`. 因此，我们可以添加一个方面，该方面提供了使对象不可变的能力，而他们对此一无所知：AOP 的一个很好的例子。

首先，我们需要一个可以完成繁重工作的`IntroductionInterceptor`。在这种情况下，我们扩展了`org.springframework.aop.support.DelegatingIntroductionInterceptor` 便利类。我们可以直接实现`IntroductionInterceptor`，但在大多数情况下使用`DelegatingIntroductionInterceptor`是最好的。

`DelegatingIntroductionInterceptor`旨在将介绍委托给所引入接口的实际实现，隐藏使用拦截来做到这一点。您可以使用构造函数参数将委托设置为任何对象。默认委托（使用无参数构造函数时）是`this`. 因此，在下一个示例中，委托是`DelegatingIntroductionInterceptor`的子类`LockMixin` 。给定一个委托（默认情况下，是它本身），一个`DelegatingIntroductionInterceptor`实例会查找委托实现的所有接口（除了 `IntroductionInterceptor`）并支持对其中任何一个的介绍。诸如子类`LockMixin`可以调用该`suppressInterface(Class intf)` 方法来抑制不应该暴露的接口。然而，无论`IntroductionInterceptor`准备支持多少接口，都应该使用 `IntroductionAdvisor`控制实际暴露的接口。引入的接口隐藏了目标对同一接口的任何实现。

因此，`LockMixin`扩展`DelegatingIntroductionInterceptor`并自身实现`Lockable` 了。超类会自动选择可以支持引入的`Lockable`，所以我们不需要指定。我们可以通过这种方式引入任意数量的接口。

注意`locked`实例变量的使用。这有效地为目标对象中保存的状态添加了额外的状态。

以下示例显示了`LockMixin`类：

```java
public class LockMixin extends DelegatingIntroductionInterceptor implements Lockable {

    private boolean locked;

    public void lock() {
        this.locked = true;
    }

    public void unlock() {
        this.locked = false;
    }

    public boolean locked() {
        return this.locked;
    }

    public Object invoke(MethodInvocation invocation) throws Throwable {
        if (locked() && invocation.getMethod().getName().indexOf("set") == 0) {
            throw new LockedException();
        }
        return super.invoke(invocation);
    }

}
```

通常，您不需要重写该`invoke()`方法。`DelegatingIntroductionInterceptor`的实现（如果方法被引入，则调用 `delegate`方法，否则向连接点接入）通常就足够了。在本例中，我们需要添加一个检查：如果处于锁定模式，则不能调用任何 setter 方法。

所需的引入只需要保存一个不同的 `LockMixin`实例并指定引入的接口（在这种情况下，只有 `Lockable`）。一个更复杂的示例可能会引用引入拦截器（将被定义为原型）。在这种情况下，没有与`LockMixin`相关的配置，因此我们使用`new`. 下面的例子展示了我们的`LockMixinAdvisor`类：

```java
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

    public LockMixinAdvisor() {
        super(new LockMixin(), Lockable.class);
    }
}
```

我们可以非常简单地应用这个切面，因为它不需要配置。（但是，没有`IntroductionAdvisor`是不可能使用`IntroductionInterceptor`的 。）与引入一样，Advisor必须是每个实例的，因为它是有状态的。对于每个被切入的对象，我们需要一个不同的`LockMixinAdvisor`实例，因此需要 `LockMixin`。 `Advisor` 包含被切入对象状态的一部分。

我们可以通过使用 `Advised.addAdvisor()` 方法或（推荐的方式）在 XML 配置中以编程方式应用此advisor 程序，就像任何其他advisor 程序一样。下面讨论的所有代理创建选项，包括“自动代理创建器”，都可以正确处理引入和有状态混合。


---

# 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/6.-spring-aop-api/6.2.-spring-zhong-de-advice-api.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.
