对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
Spring 中的建议 API
现在我们可以检查 Spring AOP 如何处理建议。
建议生命周期
每个建议都是一个 Spring bean。一个建议实例可以在所有建议的对象之间共享对象或对每个建议对象是唯一的。这对应于每个类或每个实例的建议。
最常使用每类通知。它适用于通用建议,例如事务顾问。这些不依赖于代理对象的状态或添加新的 州。 他们只是根据方法和论点采取行动。
每个实例的建议适用于介绍,以支持 mixin。在这种情况下,建议将状态添加到代理对象。
您可以在同一 AOP 代理中混合使用共享通知和每个实例通知。
春季的建议类型
Spring 提供了多种通知类型,并且可扩展以支持任意通知类型。本节介绍基本概念和标准通知类型。
围绕建议的拦截
Spring 中最基本的建议类型是围绕建议的拦截。
Spring 符合 AOPAlliance
使用方法的建议的接口 拦截。 实现MethodInterceptor
并且实现 around 建议也应该实现以下接口:
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
这MethodInvocation
参数设置为invoke()
方法公开正在调用的方法、目标连接点、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
}
}
请注意对proceed()
方法MethodInvocation
. 这会沿着拦截器链朝向连接点。大多数拦截器调用此方法并返回其返回值。但是,一个MethodInterceptor
,就像任何周围的建议一样,可以返回不同的值或抛出异常,而不是调用 proceed 方法。但是,您不想在没有充分理由的情况下这样做。
MethodInterceptor 实现提供与其他符合 AOP 联盟的 AOP 的互作性 实现。 本节其余部分讨论的其他建议类型实现常见的 AOP 概念,但以特定于 Spring 的方式。虽然有一个优势在使用最具体的建议类型时,请坚持使用MethodInterceptor 如果您可能希望在另一个 AOP 框架中运行该方面。请注意,切入点目前在框架之间不可互作,AOP 联盟也没有目前定义切入点接口。 |
建议前
一种更简单的建议类型是之前建议。这不需要MethodInvocation
对象,因为它仅在进入方法之前被调用。
before 通知的主要优点是无需调用proceed()
方法,因此,不可能无意中无法沿着拦截器链进行。
以下列表显示了MethodBeforeAdvice
接口:
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method m, Object[] args, Object target) throws Throwable;
}
(Spring 的 API 设计将允许field 在建议之前,尽管通常的对象适用于字段拦截,并且Spring 不太可能实现它。
请注意,返回类型为void
.Before 通知可以在联接之前插入自定义行为
point 运行,但无法更改返回值。如果 before 建议抛出
异常,它会停止拦截器链的进一步执行。例外
向上传播拦截器链。如果未选中或签名为
调用的方法,它直接传递给客户端。否则,它是
被 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
}
}
建议之前可以用于任何切入点。 |
投掷建议
如果引发连接点,则在返回连接点后调用抛出建议
异常。Spring 提供打字投掷建议。请注意,这意味着org.springframework.aop.ThrowsAdvice
interface 不包含任何方法。这是一个
标记接口,标识给定对象实现一个或多个类型化抛出
建议方法。这些应采用以下形式:
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
}
}
与前面的
建议,下一个示例声明了四个参数,以便它可以访问调用的方法 method
参数和目标对象。如果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 方法抛出已检查的异常,则它必须 匹配目标方法的声明异常,因此在某种程度上是 耦合到特定的目标方法签名。不要抛出未声明的选中 与目标方法的签名不兼容的异常! |
投掷建议可用于任何切入点。 |
退货后建议
Spring 中的返回通知后必须实现org.springframework.aop.AfterReturningAdvice
界面,如下表所示:
public interface AfterReturningAdvice extends Advice {
void afterReturning(Object returnValue, Method m, Object[] args, Object target)
throws Throwable;
}
返回通知后可以访问返回值(它无法修改), 调用的方法、方法的参数和目标。
返回通知后的以下内容会计算所有成功的方法调用,这些方法调用具有 not throwed 异常:
-
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);
}
这invoke()
继承自 AOP 联盟的方法MethodInterceptor
接口必须
实施介绍。也就是说,如果调用的方法位于引入的
接口,引入拦截器负责处理方法调用——它
无法调用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
}
这说明了混合。我们希望能够将建议对象转换为Lockable
,
无论它们的类型如何,并调用锁定和解锁方法。如果我们将lock()
方法,我们
希望所有 setter 方法抛出一个LockedException
.因此,我们可以添加一个方面
提供了使对象不可变的能力,而无需他们对此一无所知:
AOP 的一个很好的例子。
首先,我们需要一个IntroductionInterceptor
这完成了繁重的工作。在这个
case,我们将org.springframework.aop.support.DelegatingIntroductionInterceptor
便利类。我们可以实施IntroductionInterceptor
直接,但使用DelegatingIntroductionInterceptor
对于大多数情况来说是最好的。
这DelegatingIntroductionInterceptor
旨在将介绍委托给
实际实现引入的接口,隐蔽使用拦截
这样做。您可以使用构造函数参数将委托设置为任何对象。这
默认委托(当使用无参数构造函数时)是this
.因此,在下一个示例中,
委托是LockMixin
的子类DelegatingIntroductionInterceptor
.
给定一个委托(默认情况下,它本身),一个DelegatingIntroductionInterceptor
实例
查找委托实现的所有接口(除了IntroductionInterceptor
)并支持针对其中任何一个的介绍。
子类,例如LockMixin
可以调用suppressInterface(Class intf)
抑制不应公开的接口的方法。然而,无论有多少
接口IntroductionInterceptor
准备支持,IntroductionAdvisor
used 控制实际公开的接口。一
引入的接口隐藏目标对同一接口的任何实现。
因此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)
我们可以非常简单地应用这个顾问,因为它不需要配置。(然而,它
不可能使用IntroductionInterceptor
没有IntroductionAdvisor
.)与往常一样,顾问必须是每个实例,
因为它是有状态的。我们需要一个不同的实例LockMixinAdvisor
,因此LockMixin
,对于每个建议对象。顾问包括建议对象的一部分
州。
我们可以通过使用Advised.addAdvisor()
method 或
(推荐的方式)在 XML 配置中,就像任何其他顾问一样。所有代理创建
下面讨论的选择,包括“自动代理创建者”,可以正确处理介绍
和有状态混合。