此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
Spring 中的 Advice API
现在我们可以研究一下 Spring AOP 如何处理建议。
建议生命周期
每个建议都是一个 Spring bean。一个通知实例可以在所有被通知之间共享 对象,或者对于每个被通知的对象都是唯一的。这对应于 per-class 或 per-instance 建议。
每类建议最常使用。它适用于通用建议,例如 交易顾问。这些不依赖于代理对象的状态或添加新的 州。它们仅作用于方法和参数。
per-instance advice 适合用于 introduction,以支持 mixin。在这种情况下, 该通知将 state 添加到代理对象。
您可以在同一个 AOP 代理中混合使用共享建议和每个实例的建议。
Spring 中的建议类型
Spring 提供了多种建议类型,并且可扩展以支持 arbitrary advice 类型。本节介绍基本概念和标准通知类型。
Interception Around 建议
Spring 中最基本的 advice 类型是 catchion around advice。
Spring 与 AOP Alliance 接口兼容,用于使用方法
拦截。因此,围绕 advice 实现的类应该实现
以后MethodInterceptor
接口中org.aopalliance.intercept
包:
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
这MethodInvocation
参数传递给invoke()
method 公开了 method is
invoked、目标连接点、AOP 代理和方法的参数。这invoke()
方法应返回调用的结果:通常
连接点。
以下示例显示了一个简单的MethodInterceptor
实现:
-
Java
-
Kotlin
public class DebugInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Before: invocation=[" + invocation + "]");
Object result = invocation.proceed();
System.out.println("Invocation returned");
return result;
}
}
class DebugInterceptor : MethodInterceptor {
override fun invoke(invocation: MethodInvocation): Any {
println("Before: invocation=[$invocation]")
val result = invocation.proceed()
println("Invocation returned")
return result
}
}
请注意对proceed()
method 的MethodInvocation
.这将沿着
interceptor 链。大多数拦截器调用此方法,并且
返回其返回值。但是,MethodInterceptor
,就像任何周围的建议一样,可以
返回不同的值或引发异常,而不是调用 proceed 方法。
但是,您不想在没有充分理由的情况下这样做。
MethodInterceptor 实现提供与其他符合 AOP Alliance 的 AOP 的互作性
实现。本节其余部分讨论的其他建议类型
实现常见的 AOP 概念,但以特定于 Spring 的方式实现。虽然有优势
在使用最具体的建议类型时,请坚持使用MethodInterceptor 如果 around 建议
您可能希望在另一个 AOP 框架中运行该方面。请注意,切入点
目前无法在框架之间互作,并且 AOP 联盟不支持
当前定义切入点接口。 |
建议前
更简单的 advice 类型是 before advice。这不需要MethodInvocation
object,因为它仅在进入方法之前调用。
before 通知的主要优点是不需要调用proceed()
方法,因此,不可能无意中无法继续执行
拦截器链。
下面的清单显示了MethodBeforeAdvice
接口:
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
请注意,返回类型为void
.Before advice 可以在 join 之前插入自定义行为
point 运行,但无法更改返回值。如果 before 通知抛出
exception,它会停止拦截器链的进一步执行。异常
沿拦截器链向上传播。如果未选中或在
调用的方法,它将直接传递给客户端。否则,它是
包装在 AOP 代理的未选中异常中。
以下示例显示了 Spring 中的 before 通知,它计算所有方法调用:
-
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
}
}
Before 建议可以与任何切入点一起使用。 |
抛出建议
如果 join point 抛出
异常。Spring 提供 typed throws 建议。请注意,这意味着org.springframework.aop.ThrowsAdvice
interface 不包含任何方法。它是一个
marker 接口,用于标识给定对象实现一个或多个类型化 throw
建议方法。这些应采用以下形式:
afterThrowing([Method, args, target], subclassOfThrowable)
只需要最后一个参数。方法签名可以有一个或四个 参数,具体取决于通知方法是否对该方法感兴趣,以及 参数。接下来的两个清单显示了作为 throws advice 示例的类。
如果RemoteException
被抛出(包括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
}
}
与前面的建议不同,下一个示例声明了四个参数,因此它有
访问被调用的方法、方法参数和 Target 对象。以下建议
如果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
}
}
如果 throws-advice 方法本身引发异常,它会覆盖 原始异常 (即,它更改引发给用户的异常) 。覆盖 exception 通常是 RuntimeException,它与任何方法都兼容 签名。但是,如果 throws-advice 方法引发 checked 异常,则它必须 匹配 Target 方法的声明的异常,因此,在某种程度上是 与特定的 Target 方法签名耦合。不要抛出未声明的 checked 与 Target 方法的签名不兼容的 exception! |
投掷建议可用于任何切入点。 |
退货后通知
Spring 中的 after returning 通知必须实现org.springframework.aop.AfterReturningAdvice
接口,下面的清单显示了:
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}
返回后通知可以访问返回值(它无法修改), 调用的方法、方法的参数和 Target。
以下返回 advice 后,将计算所有具有 not throwown 异常:
-
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 将 introduction advice 视为一种特殊的拦截 advice。
Introduction 需要一个IntroductionAdvisor
以及一个IntroductionInterceptor
那
实现以下接口:
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}
这invoke()
从 AOP 联盟继承的方法MethodInterceptor
接口必须
实施 Introduction。也就是说,如果调用的方法位于引入的
interface 中,INTRODUCTION interceptor 负责处理方法调用 — 它
无法调用proceed()
.
Introduction advice 不能与任何切入点一起使用,因为它仅适用于类
而不是 method, level.您只能将 introduction advice 与IntroductionAdvisor
,该方法包括以下方法:
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
ClassFilter getClassFilter();
void validateInterfaces() throws IllegalArgumentException;
}
public interface IntroductionInfo {
Class<?>[] getInterfaces();
}
没有MethodMatcher
因此,没有Pointcut
关联 引言
建议。只有类过滤是合乎逻辑的。
这getInterfaces()
method 返回此 advisor 引入的接口。
这validateInterfaces()
method 来查看
引入的接口可以通过配置的IntroductionInterceptor
.
考虑 Spring 测试套件中的一个示例,假设我们想 将以下接口引入一个或多个对象:
-
Java
-
Kotlin
public interface Lockable {
void lock();
void unlock();
boolean locked();
}
interface Lockable {
fun lock()
fun unlock()
fun locked(): Boolean
}
这说明了一个 mixin。我们希望能够将 Suggested 对象强制转换为Lockable
,
无论它们的类型和调用 lock 和 unlock 方法。如果我们调用lock()
方法,我们
希望所有 setter 方法都抛出一个LockedException
.因此,我们可以添加一个 aspect
提供了使对象不可变的能力,而无需他们知道它:
AOP 的一个很好的例子。
首先,我们需要一个IntroductionInterceptor
这完成了繁重的工作。在这个
case 中,我们扩展了org.springframework.aop.support.DelegatingIntroductionInterceptor
便利类。我们可以实施IntroductionInterceptor
直接使用,但使用DelegatingIntroductionInterceptor
最适合大多数情况。
这DelegatingIntroductionInterceptor
旨在将简介委托给
实际实现引入的接口,隐藏使用拦截
执行此作。您可以使用 constructor 参数将委托设置为任何对象。这
default delegate (当使用无参数构造函数时) 为this
.因此,在下一个示例中,
委托是LockMixin
子类DelegatingIntroductionInterceptor
.
给定一个委托(默认情况下,它本身),一个DelegatingIntroductionInterceptor
实例
查找委托实现的所有接口(除了IntroductionInterceptor
) 并支持针对其中任何一个的 Introduction。
子类,例如LockMixin
可以调用suppressInterface(Class intf)
方法来抑制不应公开的接口。然而,无论多少
接口和IntroductionInterceptor
已准备好支持IntroductionAdvisor
used 控制实际公开的接口。一
introduced interface 隐藏了目标对同一接口的任何实现。
因此LockMixin
延伸DelegatingIntroductionInterceptor
并实现Lockable
本身。超类会自动拾取该Lockable
可以支持
introduction,所以我们不需要指定。我们可以引入任意数量的
接口。
请注意locked
instance 变量。这有效地添加了额外的状态
附加到目标对象中持有的 ID。
以下示例显示了该示例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
method 如果
该方法,否则继续向连接点前进)通常
够。在当前情况下,我们需要添加一个检查:不能调用 setter 方法
如果处于锁定模式。
所需的介绍只需要持有一个不同的LockMixin
实例并指定引入的接口(在本例中,仅Lockable
).更复杂的示例可能会引用 introduction
interceptor (将被定义为原型)。在这种情况下,没有
与LockMixin
,因此我们使用new
.
以下示例显示了我们的LockMixinAdvisor
类:
-
Java
-
Kotlin
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
public LockMixinAdvisor() {
super(new LockMixin(), Lockable.class);
}
}
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)
我们可以非常简单地应用这个 advisor,因为它不需要配置。(但是,它
不能使用IntroductionInterceptor
没有IntroductionAdvisor
.)与通常的 introduction 一样,顾问程序必须是每个实例的,
因为它是有状态的。我们需要一个不同的LockMixinAdvisor
,因此LockMixin
,对于每个被建议的对象。顾问程序包含被建议对象的
州。
我们可以使用Advised.addAdvisor()
method 或
(推荐的方法)在 XML 配置中,就像任何其他 advisor 一样。所有代理创建
下面讨论的选项(包括“自动代理创建者”)可以正确处理介绍
和有状态的 mixin 中。