Spring 中的 Advice API

现在我们可以研究一下 Spring AOP 如何处理建议。spring-doc.cn

建议生命周期

每个建议都是一个 Spring bean。一个通知实例可以在所有被通知之间共享 对象,或者对于每个被通知的对象是唯一的。这对应于 per-class 或 per-instance 建议。spring-doc.cn

每类建议最常使用。它适用于通用建议,例如 交易顾问。这些不依赖于代理对象的状态或添加新的 州。它们仅作用于方法和参数。spring-doc.cn

per-instance advice 适合用于 introduction,以支持 mixin。在这种情况下, 该通知将 state 添加到代理对象。spring-doc.cn

您可以在同一个 AOP 代理中混合使用共享建议和每个实例的建议。spring-doc.cn

Spring 中的建议类型

Spring 提供了多种建议类型,并且可扩展以支持 arbitrary advice 类型。本节介绍基本概念和标准通知类型。spring-doc.cn

Interception Around 建议

Spring 中最基本的 advice 类型是 catchion around advice。spring-doc.cn

Spring 与 AOP 接口兼容,用于使用 method 的 around 通知 拦截。实现和围绕 advice 实现的类也应该实现 以下接口:AllianceMethodInterceptorspring-doc.cn

public interface MethodInterceptor extends Interceptor {

	Object invoke(MethodInvocation invocation) throws Throwable;
}

该方法的参数公开了 invoked、目标连接点、AOP 代理和方法的参数。该方法应返回调用的结果:join 的返回值 点。MethodInvocationinvoke()invoke()spring-doc.cn

以下示例显示了一个简单的实现:MethodInterceptorspring-doc.cn

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()MethodInvocationMethodInterceptorspring-doc.cn

MethodInterceptor实现提供与其他符合 AOP Alliance 的 AOP 的互操作性 实现。本节其余部分讨论的其他建议类型 实现常见的 AOP 概念,但以特定于 Spring 的方式实现。虽然有优势 在使用最具体的 advice 类型时,如果 您可能希望在另一个 AOP 框架中运行该方面。请注意,切入点 目前无法在框架之间互操作,并且 AOP 联盟不支持 当前定义切入点接口。MethodInterceptor

建议前

更简单的 advice 类型是 before advice。这不需要对象,因为它仅在进入方法之前调用。MethodInvocationspring-doc.cn

before 通知的主要优点是不需要调用该方法,因此,不可能无意中无法继续 拦截器链。proceed()spring-doc.cn

下面的清单显示了该接口:MethodBeforeAdvicespring-doc.cn

public interface MethodBeforeAdvice extends BeforeAdvice {

	void before(Method m, Object[] args, Object target) throws Throwable;
}

(Spring 的 API 设计将允许 field 之前,尽管通常的对象适用于字段拦截,并且它是 Spring 不太可能实现它。spring-doc.cn

请注意,返回类型为 .Before advice 可以在 join 之前插入自定义行为 point 运行,但无法更改返回值。如果 before 通知抛出 exception,它会停止拦截器链的进一步执行。异常 沿拦截器链向上传播。如果未选中或在 调用的方法,它将直接传递给客户端。否则,它是 包装在 AOP 代理的未选中异常中。voidspring-doc.cn

以下示例显示了 Spring 中的 before 通知,它计算所有方法调用:spring-doc.cn

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.ThrowsAdvicespring-doc.cn

afterThrowing([Method, args, target], subclassOfThrowable)

只需要最后一个参数。方法签名可以有一个或四个 参数,具体取决于通知方法是否对该方法感兴趣,以及 参数。接下来的两个清单显示了作为 throws advice 示例的类。spring-doc.cn

如果抛出 a (包括来自子类),则调用以下建议:RemoteExceptionspring-doc.cn

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,则调用以下建议:ServletExceptionspring-doc.cn

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
	}
}

最后一个示例说明了如何在单个类中使用这两种方法 ,它同时处理 和 。任意数量的投掷建议 方法可以组合到一个类中。下面的清单显示了最后一个示例:RemoteExceptionServletExceptionspring-doc.cn

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.AfterReturningAdvicespring-doc.cn

public interface AfterReturningAdvice extends Advice {

	void afterReturning(Object returnValue, Method m, Object[] args, Object target)
			throws Throwable;
}

返回后通知可以访问返回值(它无法修改), 调用的方法、方法的参数和 Target。spring-doc.cn

以下返回 advice 后,将计算所有具有 not throwown 异常:spring-doc.cn

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-doc.cn

返回后,建议可以与任何切入点一起使用。

介绍建议

Spring 将 introduction advice 视为一种特殊的拦截 advice。spring-doc.cn

引言需要一个 an 和一个 that 实现以下接口:IntroductionAdvisorIntroductionInterceptorspring-doc.cn

public interface IntroductionInterceptor extends MethodInterceptor {

	boolean implementsInterface(Class intf);
}

从 AOP Alliance 接口继承的方法必须 实施 Introduction。也就是说,如果调用的方法位于引入的 interface 中,INTRODUCTION interceptor 负责处理方法调用 — 它 无法调用 。invoke()MethodInterceptorproceed()spring-doc.cn

Introduction advice 不能与任何切入点一起使用,因为它仅适用于类 而不是 method, level.您只能将 introduction advice 与 一起使用,它具有以下方法:IntroductionAdvisorspring-doc.cn

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

	ClassFilter getClassFilter();

	void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

	Class<?>[] getInterfaces();
}

没有,因此,也没有与引言相关 建议。只有类过滤是合乎逻辑的。MethodMatcherPointcutspring-doc.cn

该方法返回此 advisor 引入的接口。getInterfaces()spring-doc.cn

该方法在内部用于查看 引入的接口可以通过配置的 来实现。validateInterfaces()IntroductionInterceptorspring-doc.cn

考虑 Spring 测试套件中的一个示例,假设我们想 将以下接口引入一个或多个对象:spring-doc.cn

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 的一个很好的例子。Lockablelock()LockedExceptionspring-doc.cn

首先,我们需要一个能完成繁重工作的人。在这个 case 中,我们扩展了 convenience 类。我们可以直接实现,但在大多数情况下,使用是最好的。IntroductionInterceptororg.springframework.aop.support.DelegatingIntroductionInterceptorIntroductionInterceptorDelegatingIntroductionInterceptorspring-doc.cn

旨在将 introduction 委托给 实际实现引入的接口,隐藏使用拦截 执行此操作。您可以使用 constructor 参数将委托设置为任何对象。这 default delegate(使用无参数构造函数时)为 .因此,在下一个示例中, 委托是 的子类。 给定一个委托(默认情况下,它本身),一个实例 查找由 delegate (except than )实现的所有接口,并支持针对其中任何一个接口的介绍。 子类(如 can 调用该方法)来禁止显示不应公开的接口。然而,无论多少 接口 an 准备支持,使用的控件实际公开了哪些接口。一 introduced interface 隐藏了目标对同一接口的任何实现。DelegatingIntroductionInterceptorthisLockMixinDelegatingIntroductionInterceptorDelegatingIntroductionInterceptorIntroductionInterceptorLockMixinsuppressInterface(Class intf)IntroductionInterceptorIntroductionAdvisorspring-doc.cn

因此,扩展并实现自身。超类会自动拾取可以支持的 introduction,所以我们不需要指定。我们可以引入任意数量的 接口。LockMixinDelegatingIntroductionInterceptorLockableLockablespring-doc.cn

请注意 instance 变量的使用。这有效地添加了额外的状态 附加到目标对象中持有的 ID。lockedspring-doc.cn

以下示例显示了示例类:LockMixinspring-doc.cn

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()DelegatingIntroductionInterceptordelegatespring-doc.cn

所需的 introduction 只需要持有一个不同的实例并指定引入的接口(在本例中,only )。更复杂的示例可能会引用 introduction interceptor (将被定义为原型)。在这种情况下,没有 与 A 相关的配置,因此我们使用 来创建它。 以下示例显示了我们的类:LockMixinLockableLockMixinnewLockMixinAdvisorspring-doc.cn

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

	public LockMixinAdvisor() {
		super(new LockMixin(), Lockable.class);
	}
}
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)

我们可以非常简单地应用这个 advisor,因为它不需要配置。(但是,它 不能在没有 .) 的情况下使用 an与通常的 introduction 一样,顾问程序必须是每个实例的, 因为它是有状态的。对于每个建议的对象,我们需要一个不同的 , 实例 ,因此 。顾问程序包含被建议对象的 州。IntroductionInterceptorIntroductionAdvisorLockMixinAdvisorLockMixinspring-doc.cn

我们可以使用以下方法以编程方式应用此 advisor 或 (推荐的方法)在 XML 配置中,就像任何其他 advisor 一样。所有代理创建 下面讨论的选项(包括“自动代理创建者”)可以正确处理介绍 和有状态的 mixin 中。Advised.addAdvisor()spring-doc.cn