Infra 中的 Advice API
现在我们可以通过检查 Infra AOP 如何处理 advice。
Advice 生命周期
每个 advice 都是一个 Infra bean。一个 advice 实例可以在所有被通知对象之间共享,也可以对每个被通知对象是唯一的。 这对应于每类(per-class)或每实例(per-instance)的 advice。
每类 advice 最常被使用。它适用于通用 advice,例如事务 advisor。 这些不依赖于代理对象的状态或添加新状态。它们仅作用于方法和参数。
每实例 advice 适用于引介(introduction),以支持 mixin。在这种情况下,advice 向代理对象添加状态。
你可以在同一个 AOP 代理中混合使用共享和每实例 advice。
Infra 中的 Advice 类型
Infra 提供了几种 advice 类型,并且可以扩展以支持任意 advice 类型。本节描述基本概念和标准 advice 类型。
拦截环绕 Advice
Infra 中最基本的 advice 类型是拦截环绕 advice。
Infra 符合 AOP Alliance 接口,用于使用方法拦截的环绕 advice。
实现 MethodInterceptor 并实现环绕 advice 的类也应该实现以下接口:
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 与任何环绕 advice 一样,
可以返回不同的值或抛出异常,而不是调用 proceed 方法。
但是,没有充分的理由不要这样做。
MethodInterceptor 实现提供与其他符合 AOP Alliance 的 AOP 实现的互操作性。
本节其余部分讨论的其他 advice 类型实现了常见的 AOP 概念,但以 Infra 特定的方式实现。
虽然使用最具体的 advice 类型有优势,但如果你可能希望在另一个 AOP 框架中运行切面,请坚持使用 MethodInterceptor 环绕 advice。
请注意,pointcut 目前在框架之间不可互操作,并且 AOP Alliance 目前未定义 pointcut 接口。
|
前置 Advice
一种更简单的 advice 类型是前置 advice。这不需要 MethodInvocation 对象,因为它仅在进入方法之前被调用。
前置 advice 的主要优点是不需要调用 proceed() 方法,因此不可能无意中未能沿着拦截器链继续。
以下清单显示了 MethodBeforeAdvice 接口:
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
(Infra API 设计允许字段前置 advice,尽管通常的对象适用于字段拦截,并且 Infra 不太可能实现它。)
请注意,返回类型为 void。前置 advice 可以在连接点运行之前插入自定义行为,但不能更改返回值。
如果前置 advice 抛出异常,它将停止拦截器链的进一步执行。异常会向上传播回拦截器链。
如果它是未检查的或在调用方法的签名上,它将直接传递给客户端。否则,它会被 AOP 代理包装在未检查的异常中。
以下示例显示了 Infra 中的前置 advice,它计算所有方法调用:
-
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;
}
}
| 前置 advice 可以与任何 pointcut 一起使用。 |
抛出 Advice
如果连接点抛出异常,则在连接点返回后调用抛出 advice。Infra 提供类型化的抛出 advice。
请注意,这意味着 infra.aop.ThrowsAdvice 接口不包含任何方法。
它是一个标记接口,标识给定对象实现了一个或多个类型化的抛出 advice 方法。这些应采用以下形式:
afterThrowing([Method, args, target], subclassOfThrowable)
只有最后一个参数是必需的。方法签名可以有一个或四个参数,具体取决于 advice 方法是否对方法和参数感兴趣。 接下来的两个清单显示了作为抛出 advice 示例的类。
如果抛出 RemoteException(包括子类),则调用以下 advice:
-
Java
public class RemoteThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// 对远程异常做点什么
}
}
与前面的 advice 不同,下一个示例声明了四个参数,以便它可以访问调用的方法、方法参数和目标对象。
如果抛出 MockException,则调用以下 advice:
-
Java
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// 对所有参数做点什么
}
}
最后一个示例说明了如何在处理 RemoteException 和 MockException 的单个类中使用这两个方法。
任意数量的抛出 advice 方法可以组合在一个类中。以下清单显示了最后一个示例:
-
Java
public static class CombinedThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// 对远程异常做点什么
}
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// 对所有参数做点什么
}
}
| 如果抛出 advice 方法本身抛出异常,它将覆盖原始异常(即,它更改抛给用户的异常)。 覆盖异常通常是 RuntimeException,它与任何方法签名兼容。 但是,如果抛出 advice 方法抛出已检查异常,它必须与目标方法的声明异常匹配,因此在某种程度上耦合到特定的目标方法签名。 不要抛出与目标方法签名不兼容的未声明的已检查异常! |
| 抛出 advice 可以与任何 pointcut 一起使用。 |
返回后 Advice
Infra 中的返回后 advice 必须实现 infra.aop.AfterReturningAdvice 接口,如下清单所示:
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}
返回后 advice 可以访问返回值(它无法修改)、调用的方法、方法的参数和目标。
以下返回后 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;
}
}
此 advice 不会更改执行路径。如果它抛出异常,则将其抛出拦截器链,而不是返回值。
| 返回后 advice 可以与任何 pointcut 一起使用。 |
引介 Advice
Infra 将引介 advice 视为一种特殊的拦截 advice。
引介需要一个 IntroductionAdvisor 和一个 IntroductionInterceptor,它们实现以下接口:
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}
从 AOP Alliance MethodInterceptor 接口继承的 invoke() 方法必须实现引介。
也就是说,如果调用的方法在引入的接口上,则引介拦截器负责处理方法调用——它不能调用 proceed()。
引介 advice 不能与任何 pointcut 一起使用,因为它仅适用于类级别,而不是方法级别。
你只能将引介 advice 与 IntroductionAdvisor 一起使用,它具有以下方法:
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
ClassFilter getClassFilter();
void validateInterfaces() throws IllegalArgumentException;
}
public interface IntroductionInfo {
Class<?>[] getInterfaces();
}
没有 MethodMatcher,因此没有与引介 advice 关联的 Pointcut。只有类过滤是合乎逻辑的。
getInterfaces() 方法返回此 advisor 引入的接口。
validateInterfaces() 方法在内部用于查看引入的接口是否可以由配置的 IntroductionInterceptor 实现。
考虑来自 Infra 测试套件的一个示例,假设我们想要将以下接口引入到一个或多个对象:
-
Java
public interface Lockable {
void lock();
void unlock();
boolean locked();
}
这说明了一个 mixin。我们希望能够将建议对象转换为 Lockable,无论其类型如何,并调用 lock 和 unlock 方法。
如果我们调用 lock() 方法,我们要让所有 setter 方法抛出 LockedException。
因此,我们可以添加一个切面,该切面提供使对象不可变的能力,而对象对此一无所知:这是 AOP 的一个很好的例子。
首先,我们需要一个 IntroductionInterceptor 来完成繁重的工作。
在这种情况下,我们扩展 infra.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);
}
}
我们可以非常简单地应用此 advisor,因为它不需要配置。(但是,如果不使用 IntroductionAdvisor,就不可能使用 IntroductionInterceptor。)
与引介一样,advisor 必须是每实例的,因为他是有状态的。我们需要为每个被通知对象提供不同的 LockMixinAdvisor 实例,从而提供不同的 LockMixin。
Advisor 包含被通知对象状态的一部分。
我们可以通过使用 Advised.addAdvisor() 方法以编程方式应用此 advisor,或者(推荐的方式)在 XML 配置中应用,就像任何其他 advisor 一样。
下面讨论的所有代理创建选择,包括“自动代理创建者”,都能正确处理引介和有状态 mixin。