此版本仍在开发中,尚未被视为稳定版本。最新的稳定版本请使用 Spring Framework 6.1.13! |
此版本仍在开发中,尚未被视为稳定版本。最新的稳定版本请使用 Spring Framework 6.1.13! |
现在我们可以研究一下 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 接口兼容,用于使用 method 的 around 通知
拦截。实现和围绕 advice 实现的类也应该实现
以下接口:Alliance
MethodInterceptor
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
该方法的参数公开了
invoked、目标连接点、AOP 代理和方法的参数。该方法应返回调用的结果:join 的返回值
点。MethodInvocation
invoke()
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
}
}
请注意对 .这将沿着
interceptor 链。大多数拦截器调用此方法,并且
返回其返回值。但是,与 周围的任何建议一样,a 可以
返回不同的值或引发异常,而不是调用 proceed 方法。
但是,您不想在没有充分理由的情况下这样做。proceed()
MethodInvocation
MethodInterceptor
MethodInterceptor 实现提供与其他符合 AOP Alliance 的 AOP 的互操作性
实现。本节其余部分讨论的其他建议类型
实现常见的 AOP 概念,但以特定于 Spring 的方式实现。虽然有优势
在使用最具体的 advice 类型时,如果
您可能希望在另一个 AOP 框架中运行该方面。请注意,切入点
目前无法在框架之间互操作,并且 AOP 联盟不支持
当前定义切入点接口。MethodInterceptor |
建议前
更简单的 advice 类型是 before advice。这不需要对象,因为它仅在进入方法之前调用。MethodInvocation
before 通知的主要优点是不需要调用该方法,因此,不可能无意中无法继续
拦截器链。proceed()
下面的清单显示了该接口:MethodBeforeAdvice
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
(Spring 的 API 设计将允许 field 之前,尽管通常的对象适用于字段拦截,并且它是 Spring 不太可能实现它。
请注意,返回类型为 .Before advice 可以在 join 之前插入自定义行为
point 运行,但无法更改返回值。如果 before 通知抛出
exception,它会停止拦截器链的进一步执行。异常
沿拦截器链向上传播。如果未选中或在
调用的方法,它将直接传递给客户端。否则,它是
包装在 AOP 代理的未选中异常中。void
以下示例显示了 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 建议。请注意,这意味着该接口不包含任何方法。它是一个
标记接口,用于标识给定对象实现一个或多个类型化 throw
建议方法。这些应采用以下形式:org.springframework.aop.ThrowsAdvice
afterThrowing([Method, args, target], subclassOfThrowable)
只需要最后一个参数。方法签名可以有一个或四个 参数,具体取决于通知方法是否对该方法感兴趣,以及 参数。接下来的两个清单显示了作为 throws advice 示例的类。
如果抛出 a (包括来自子类),则调用以下建议: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
}
}
与前面的
advice 中,下一个示例声明了四个参数,以便它可以访问被调用的方法 Method
arguments 和 target 对象。如果抛出 a,则调用以下建议: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。
引言需要一个 an 和一个 that
实现以下接口:IntroductionAdvisor
IntroductionInterceptor
public interface IntroductionInterceptor extends MethodInterceptor {
boolean implementsInterface(Class intf);
}
从 AOP Alliance 接口继承的方法必须
实施 Introduction。也就是说,如果调用的方法位于引入的
interface 中,INTRODUCTION interceptor 负责处理方法调用 — 它
无法调用 。invoke()
MethodInterceptor
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
该方法返回此 advisor 引入的接口。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。我们希望能够将 Advice 对象转换为 ,
无论它们的类型和调用 lock 和 unlock 方法。如果我们调用该方法,则
希望所有 setter 方法都抛出一个 .因此,我们可以添加一个 aspect
提供了使对象不可变的能力,而无需他们知道它:
AOP 的一个很好的例子。Lockable
lock()
LockedException
首先,我们需要一个能完成繁重工作的人。在这个
case 中,我们扩展了 convenience 类。我们可以直接实现,但在大多数情况下,使用是最好的。IntroductionInterceptor
org.springframework.aop.support.DelegatingIntroductionInterceptor
IntroductionInterceptor
DelegatingIntroductionInterceptor
旨在将 introduction 委托给
实际实现引入的接口,隐藏使用拦截
执行此操作。您可以使用 constructor 参数将委托设置为任何对象。这
default delegate(使用无参数构造函数时)为 .因此,在下一个示例中,
委托是 的子类。
给定一个委托(默认情况下,它本身),一个实例
查找由 delegate (except than )实现的所有接口,并支持针对其中任何一个接口的介绍。
子类(如 can 调用该方法)来禁止显示不应公开的接口。然而,无论多少
接口 an 准备支持,使用的控件实际公开了哪些接口。一
introduced interface 隐藏了目标对同一接口的任何实现。DelegatingIntroductionInterceptor
this
LockMixin
DelegatingIntroductionInterceptor
DelegatingIntroductionInterceptor
IntroductionInterceptor
LockMixin
suppressInterface(Class intf)
IntroductionInterceptor
IntroductionAdvisor
因此,扩展并实现自身。超类会自动拾取可以支持的
introduction,所以我们不需要指定。我们可以引入任意数量的
接口。LockMixin
DelegatingIntroductionInterceptor
Lockable
Lockable
请注意 instance 变量的使用。这有效地添加了额外的状态
附加到目标对象中持有的 ID。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)
}
}
通常,您不需要重写该方法。实现(调用 if
该方法,否则继续向连接点前进)通常
够。在当前情况下,我们需要添加一个检查:不能调用 setter 方法
如果处于锁定模式。invoke()
DelegatingIntroductionInterceptor
delegate
所需的 introduction 只需要持有一个不同的实例并指定引入的接口(在本例中,only )。更复杂的示例可能会引用 introduction
interceptor (将被定义为原型)。在这种情况下,没有
与 A 相关的配置,因此我们使用 来创建它。
以下示例显示了我们的类: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)
我们可以非常简单地应用这个 advisor,因为它不需要配置。(但是,它
不能在没有 .) 的情况下使用 an与通常的 introduction 一样,顾问程序必须是每个实例的,
因为它是有状态的。对于每个建议的对象,我们需要一个不同的 , 实例 ,因此 。顾问程序包含被建议对象的
州。IntroductionInterceptor
IntroductionAdvisor
LockMixinAdvisor
LockMixin
我们可以使用以下方法以编程方式应用此 advisor 或
(推荐的方法)在 XML 配置中,就像任何其他 advisor 一样。所有代理创建
下面讨论的选项(包括“自动代理创建者”)可以正确处理介绍
和有状态的 mixin 中。Advised.addAdvisor()
MethodInterceptor 实现提供与其他符合 AOP Alliance 的 AOP 的互操作性
实现。本节其余部分讨论的其他建议类型
实现常见的 AOP 概念,但以特定于 Spring 的方式实现。虽然有优势
在使用最具体的 advice 类型时,如果
您可能希望在另一个 AOP 框架中运行该方面。请注意,切入点
目前无法在框架之间互操作,并且 AOP 联盟不支持
当前定义切入点接口。MethodInterceptor |
Before 建议可以与任何切入点一起使用。 |
如果 throws-advice 方法本身引发异常,它会覆盖 原始异常 (即,它更改引发给用户的异常) 。覆盖 exception 通常是 RuntimeException,它与任何方法都兼容 签名。但是,如果 throws-advice 方法引发 checked 异常,则它必须 匹配 Target 方法的声明的异常,因此,在某种程度上是 与特定的 Target 方法签名耦合。不要抛出未声明的 checked 与 Target 方法的签名不兼容的 exception! |
投掷建议可用于任何切入点。 |
返回后,建议可以与任何切入点一起使用。 |