# 6.2Advice API

下面我们看一下Spring AOP怎么处理advice。

## 6.2.1 Advice生命周期

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

Per-class advice最常用。它适用于一般的advice，如事务advisors。这些不依赖于代理对象的状态或添加新状态。他们只根据方法和参数行事。

Per-instance的advice都适用于introductions，以支持混合。在这种情况下，建议将状态添加到代理对象。

你可以在同一个AOP代理中混合使用共享和基于实例的建议。

## 6.2.2 Advice类型

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

**环绕拦截Advice**

Spring最基本的建议类型是围绕建议进行拦截。

Spring与AOP联盟接口兼容，以获取使用方法拦截的 around advice。实现MethodInterceptor和实现around advice的类还应实现以下接口：

```java
public interface MethodInterceptor extends Interceptor {

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

invoke（）方法的methodInvocation参数公开了被调用的方法、目标连接点、AOP代理以及该方法的参数。invoke（）方法应返回调用的结果：连接点的返回值。

下面的示例显示了一个简单的SystemInterceptor实现：

```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（）方法的调用。这将沿着拦截器链向连接点前进。大多数拦截器调用此方法并返回其返回值。但是，与任何around通知一样，MethodIntercetor可以返回不同的值或引发异常，而不是调用继续方法。但是，如果没有充分的理由，你不想这样做。

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

**Before Advice**

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

before通知的主要优点是不需要调用proceed（）方法，因此，不可能无意中中断拦截器链。

下面的列表显示了MethodBeforeAdvice接口：

```java
public interface MethodBeforeAdvice extends BeforeAdvice {

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

（Spring的API设计将允许在建议之前提供字段，尽管通常的对象适用于字段拦截，但是Spring不太可能实现它。）

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

下面的示例显示了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;
    }
}
```

> Before advice可以用在任意pointcut中。

**Throws Advice**

如果连接点引发异常，则在返回连接点后调用throws通知。Spring提供 typed throws advice。注意，这意味着org.springframework.aop.ThrowsAdvice接口不包含任何方法。它是一个标记接口，标识给定对象实现一个或多个类型化的throw advice方法。应采用以下形式：

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

只需要最后一个参数。方法签名可以有一个或四个参数，这取决于advice方法对方法和参数是否感兴趣。下面的两个列表显示了属于throw建议示例的类。

如果抛出了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的单个类中使用。可以在单个类中组合任意数量的throw advice方法。下面的列表显示了最后一个示例：

```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，它与任何方法签名都兼容。但是，如果throw advice方法抛出一个选中的异常，它必须与目标方法的声明异常匹配，因此在某种程度上与特定的目标方法签名耦合。不要抛出与目标方法的签名不兼容的未声明的已检查异常！
>
> Throws advice可以在任何pointcut使用。

**After Returning Advice**

在Spring中after returning advice，必须实现org.springframework.aop.AfterReturningAdvice接口，如下所示：

```
public interface AfterReturningAdvice extends Advice {

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

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

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

```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;
    }
}
```

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

> After returning advice可以用在任何pointcut。

**Introduction Advice**

Spring将Introduction Advice视为一种特殊的拦截Advice。

Introduction需要一个IntroductionAdvisor和一个实现以下接口的IntroductionInterceptor：

```java
public interface IntroductionInterceptor extends MethodInterceptor {

    boolean implementsInterface(Class intf);
}
```

从AOP Alliance MethodIntercetor接口继承的invoke（）方法必须实现introduction。也就是说，如果被调用的方法在引入的接口上，则引入拦截器负责处理方法调用-它不能调用proceed（）。

Introduction advice不能与任何切入点一起使用，因为它只适用于类，而不是方法级别。你只能将 introduction advice 与IntroductionAdvisor一起使用，其方法如下：

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

    ClassFilter getClassFilter();

    void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

    Class[] getInterfaces();
}
```

没有methodMatcher，因此没有与 introduction advice相关联的切入点。只有类筛选是合乎逻辑的。

getInterfaces（）方法返回此advisor引入的接口。

validateInterfaces（）方法在内部用于查看所引入的接口是否可以由配置的IntroductionInterceptor实现。

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

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

这里展示一个mixin。我们希望能够将建议的对象强制转换为Lockable的对象，无论其类型如何，并调用锁定和解锁方法。如果调用lock（）方法，我们希望所有setter方法都抛出lockedException。因此，我们可以添加一个方面，使对象在不了解它的情况下保持不变：AOP的一个很好的例子。

首先，我们需要一个IntroductionInterceptor来完成这项工作。在本例中，我们扩展了org.springframework.aop.support.DelegatingIntroductionInterceptor便利类。我们可以直接实现IntroductionInterceptor，但对于大多数情况，使用DelegatingIntroductionInterceptor是最好的。

DelegingIntroductionInterceptor设计用于将introduction委托给所引入接口的实际实现，从而隐藏使用拦截来完成此操作。可以使用构造函数参数将委托设置为任何对象。默认委托（使用无参数构造函数时）是this。

因此，在下一个示例中，委托是DelegatingIntroductionInterceptorr的lockMixin子类。对于委托（默认情况下，它本身），DelegatingIntroductionInterceptor实例查找委托实现的所有接口（而不是IntroductionInterceptor），并支持针对其中任何一个进行introductions。LockMixin等子类可以调用SuppressInterface（Class Intf）方法来抑制不应公开的接口。然而，不管一个IntroductionInterceptor准备支持多少个接口，IntroductionAdvisor被用来控制哪些接口实际上被公开。引入的接口隐藏了目标对同一接口的任何实现。

因此，lockmixin扩展了DelegatingIntroductionInterceptor并实现了Lockable接口。超类会自动选取可以支持introduction的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方法。

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

```java
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

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

我们可以非常简单地应用这个advisor，因为它不需要配置。（然而，在没有IntroductionAdvisor的情况下，不可能使用IntroductionInterceptor。）与通常的introductions一样，顾问必须是有状态的。对于每个建议的对象，我们需要一个不同的LockMixinAdvisor实例，因此需要LockMixin。advisor是被advised对象状态的一部分。

我们可以通过在XML配置中使用advised.addAdvisor（）方法或（推荐的方法）以编程方式应用此Advisor，就像任何其他Advisor一样。下面讨论的所有代理创建选项，包括“auto-proxy creators”，正确处理introductions 和 stateful 的混合。


---

# 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://docs.flydean.com/spring-framework-documentation5/core-technologies/6.spring-aop-apis/6.2advice-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.
