Spring 中的 Advice API
现在我们可以检查 Spring AOP 如何处理增强。
增强生命周期
每个增强都是一个 Spring Bean。一个增强实例可以在所有被增强对象之间共享,也可以对每个被增强对象都是唯一的。这对应于每个类或每个实例的增强。
每个类的增强最常使用。它适用于通用增强,例如事务增强。这些增强不依赖于代理对象的 state 或添加新的 state。它们只是对方法和参数进行操作。
每个实例的增强适用于引入,以支持 mixin。在这种情况下,增强会向代理对象添加 state。
您可以在同一个 AOP 代理中混合使用共享和每个实例的增强。
Spring 中的增强类型
Spring 提供了几种增强类型,并且可以扩展以支持任意增强类型。本节描述了基本概念和标准增强类型。
环绕增强拦截
Spring 中最基本的增强类型是环绕增强拦截。
Spring 符合 AOP Alliance
接口,该接口用于使用方法拦截的环绕增强。实现MethodInterceptor
并实现环绕增强的类也应该实现以下接口
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
invoke()
方法的参数MethodInvocation
公开了正在调用的方法、目标连接点、AOP 代理以及方法的参数。invoke()
方法应该返回调用的结果:连接点的返回值。
以下示例显示了一个简单的MethodInterceptor
实现
-
Java
-
Kotlin
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;
}
}
class DebugInterceptor : MethodInterceptor {
override fun invoke(invocation: MethodInvocation): Any {
println("Before: invocation=[$invocation]")
val rval = invocation.proceed()
println("Invocation returned")
return rval
}
}
请注意对MethodInvocation
的proceed()
方法的调用。这会沿着拦截器链向下传递到连接点。大多数拦截器都会调用此方法并返回其返回值。但是,MethodInterceptor
(像任何环绕增强一样)可以返回不同的值或抛出异常,而不是调用 proceed 方法。但是,如果没有充分的理由,您不希望这样做。
MethodInterceptor 实现提供了与其他符合 AOP Alliance 的 AOP 实现的互操作性。本节其余部分讨论的其他增强类型以 Spring 特定的方式实现了常见的 AOP 概念。虽然使用最具体的增强类型有优势,但如果您可能希望在另一个 AOP 框架中运行方面,请坚持使用MethodInterceptor 环绕增强。请注意,切点目前在框架之间不具有互操作性,并且 AOP Alliance 目前没有定义切点接口。 |
前置增强
更简单的增强类型是前置增强。它不需要MethodInvocation
对象,因为它只在进入方法之前被调用。
前置增强的主要优势在于无需调用proceed()
方法,因此也不可能无意中未能沿着拦截器链继续执行。
以下列表显示了MethodBeforeAdvice
接口
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
(Spring 的 API 设计允许进行字段前置增强,尽管通常的对象适用于字段拦截,并且 Spring 不太可能实现它。)
请注意,返回类型是void
。前置增强可以在连接点运行之前插入自定义行为,但不能更改返回值。如果前置增强抛出异常,它将停止拦截器链的进一步执行。异常会向上传播回拦截器链。如果它是未经检查的或在调用的方法的签名中,它将直接传递给客户端。否则,它将由 AOP 代理包装在未经检查的异常中。
以下示例显示了 Spring 中的前置增强,它计算所有方法调用次数
-
Java
-
Kotlin
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;
}
}
class CountingBeforeAdvice : MethodBeforeAdvice {
var count: Int = 0
override fun before(m: Method, args: Array<Any>, target: Any?) {
++count
}
}
前置增强可以与任何切点一起使用。 |
异常抛出增强
如果连接点抛出异常,则在连接点返回后调用异常抛出增强。Spring 提供了类型化异常抛出增强。请注意,这意味着org.springframework.aop.ThrowsAdvice
接口不包含任何方法。它是一个标记接口,用于标识给定对象实现了一个或多个类型化异常抛出增强方法。这些方法应该采用以下形式
afterThrowing([Method, args, target], subclassOfThrowable)
仅最后一个参数是必需的。方法签名可以有一个或四个参数,具体取决于增强方法是否对方法和参数感兴趣。接下来的两个列表显示了作为异常抛出增强示例的类。
如果抛出RemoteException
(包括来自子类的异常),则调用以下增强
-
Java
-
Kotlin
public class RemoteThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(RemoteException ex) throws Throwable {
// Do something with remote exception
}
}
class RemoteThrowsAdvice : ThrowsAdvice {
fun afterThrowing(ex: RemoteException) {
// Do something with remote exception
}
}
与前面的增强不同,下一个示例声明了四个参数,以便它可以访问调用的方法、方法参数和目标对象。如果抛出ServletException
,则调用以下增强
-
Java
-
Kotlin
public class ServletThrowsAdviceWithArguments implements ThrowsAdvice {
public void afterThrowing(Method m, Object[] args, Object target, ServletException ex) {
// Do something with all arguments
}
}
class ServletThrowsAdviceWithArguments : ThrowsAdvice {
fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
// Do something with all arguments
}
}
最后一个示例说明了如何在处理RemoteException
和ServletException
的单个类中使用这两种方法。任何数量的异常抛出增强方法都可以组合在一个类中。以下列表显示了最后一个示例
-
Java
-
Kotlin
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
}
}
class CombinedThrowsAdvice : ThrowsAdvice {
fun afterThrowing(ex: RemoteException) {
// Do something with remote exception
}
fun afterThrowing(m: Method, args: Array<Any>, target: Any, ex: ServletException) {
// Do something with all arguments
}
}
如果异常抛出增强方法本身抛出异常,则它将覆盖原始异常(即,它将抛出的异常更改为用户)。覆盖异常通常是 RuntimeException,它与任何方法签名兼容。但是,如果异常抛出增强方法抛出已检查异常,则它必须与目标方法的声明异常匹配,因此在某种程度上与特定的目标方法签名耦合。不要抛出与目标方法签名不兼容的未声明已检查异常! |
异常抛出增强可以与任何切点一起使用。 |
后置增强
Spring 中的后置增强必须实现org.springframework.aop.AfterReturningAdvice
接口,以下列表显示了该接口
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}
后置增强可以访问返回值(它不能修改)、调用的方法、方法的参数以及目标。
以下后置增强计算所有未抛出异常的成功方法调用次数
-
Java
-
Kotlin
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;
}
}
class CountingAfterReturningAdvice : AfterReturningAdvice {
var count: Int = 0
private set
override fun afterReturning(returnValue: Any?, m: Method, args: Array<Any>, target: Any?) {
++count
}
}
此增强不会更改执行路径。如果它抛出异常,则它将向上抛出到拦截器链,而不是返回值。
后置增强可以与任何切点一起使用。 |
引入增强
Spring 将引入增强视为一种特殊的拦截增强。
引入需要一个IntroductionAdvisor
和一个实现以下接口的IntroductionInterceptor
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}
从 AOP Alliance MethodInterceptor
接口继承的invoke()
方法必须实现引入。也就是说,如果调用的方法位于引入的接口上,则引入拦截器负责处理方法调用——它不能调用proceed()
。
引入增强不能与任何切点一起使用,因为它仅适用于类级别,而不是方法级别。您只能将引入增强与IntroductionAdvisor
一起使用,它具有以下方法
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
ClassFilter getClassFilter();
void validateInterfaces() throws IllegalArgumentException;
}
public interface IntroductionInfo {
Class<?>[] getInterfaces();
}
没有MethodMatcher
,因此也没有与引入增强关联的Pointcut
。只有类过滤是逻辑的。
getInterfaces()
方法返回此增强器引入的接口。
validateInterfaces()
方法在内部用于查看配置的IntroductionInterceptor
是否可以实现引入的接口。
考虑来自 Spring 测试套件的一个示例,并假设我们要将以下接口引入一个或多个对象
-
Java
-
Kotlin
public interface Lockable {
void lock();
void unlock();
boolean locked();
}
interface Lockable {
fun lock()
fun unlock()
fun locked(): Boolean
}
此示例演示了一个Mixin(混入)。我们希望能够将被增强对象转换为Lockable
,无论它们的类型是什么,并调用lock和unlock方法。如果我们调用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
-
Kotlin
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);
}
}
class LockMixin : DelegatingIntroductionInterceptor(), Lockable {
private var locked: Boolean = false
fun lock() {
this.locked = true
}
fun unlock() {
this.locked = false
}
fun locked(): Boolean {
return this.locked
}
override fun invoke(invocation: MethodInvocation): Any? {
if (locked() && invocation.method.name.indexOf("set") == 0) {
throw LockedException()
}
return super.invoke(invocation)
}
}
通常,您不需要重写invoke()
方法。DelegatingIntroductionInterceptor
的实现(如果方法被引入则调用delegate
方法,否则继续执行连接点)通常就足够了。在本例中,我们需要添加一个检查:如果处于锁定模式,则不能调用任何setter方法。
所需的引入只需要持有不同的LockMixin
实例并指定引入的接口(在本例中,只有Lockable
)。更复杂的示例可能会获取对引入拦截器(它将被定义为原型)的引用。在本例中,没有与LockMixin
相关的配置,因此我们使用new
来创建它。以下示例显示了我们的LockMixinAdvisor
类
-
Java
-
Kotlin
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
public LockMixinAdvisor() {
super(new LockMixin(), Lockable.class);
}
}
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)
我们可以非常简单地应用此顾问,因为它不需要任何配置。(但是,如果没有IntroductionAdvisor
,则无法使用IntroductionInterceptor
。)与引入通常一样,顾问必须是每个实例的,因为它是有状态的。我们需要一个不同的LockMixinAdvisor
实例,因此对于每个被增强对象,都需要一个不同的LockMixin
实例。顾问构成了被增强对象状态的一部分。
我们可以通过使用Advised.addAdvisor()
方法以编程方式应用此顾问,或者(推荐的方法)在XML配置中应用,就像任何其他顾问一样。下面讨论的所有代理创建选择,包括“自动代理创建器”,都正确地处理了引入和有状态的Mixin。