# 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 程序一样。下面讨论的所有代理创建选项，包括“自动代理创建器”，都可以正确处理引入和有状态混合。
