此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10spring-doc.cadn.net.cn

Spring 中的建议 API

现在我们可以检查 Spring AOP 如何处理建议。spring-doc.cadn.net.cn

建议生命周期

每个建议都是春豆。建议实例可以在所有建议的 对象或对每个建议对象都是唯一的。这对应于每个类或 每个实例的建议。spring-doc.cadn.net.cn

最常使用每类建议。它适用于通用建议,例如 交易顾问。这些不依赖于代理对象的状态或添加新的 州。他们只是根据方法和论点采取行动。spring-doc.cadn.net.cn

每个实例的建议适用于介绍,以支持混合。在这种情况下, 建议将状态添加到代理对象。spring-doc.cadn.net.cn

您可以在同一 AOP 代理中混合使用共享通知和每个实例通知。spring-doc.cadn.net.cn

春季的建议类型

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

围绕建议的拦截

Spring 中最基本的建议类型是围绕建议的拦截spring-doc.cadn.net.cn

Spring 符合 AOP 联盟接口,用于使用方法 拦截。因此,围绕建议实现的类应该实现 以后MethodInterceptor接口从org.aopalliance.intercept包:spring-doc.cadn.net.cn

public interface MethodInterceptor extends Interceptor {

	Object invoke(MethodInvocation invocation) throws Throwable;
}

MethodInvocation参数设置为invoke()method 公开了 调用,目标连接点,AOP代理和方法的参数。这invoke()方法应返回调用的结果:通常是 连接点。spring-doc.cadn.net.cn

以下示例显示了一个简单的MethodInterceptor实现:spring-doc.cadn.net.cn

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()方法MethodInvocation.这会继续进行 拦截器链朝向连接点。大多数拦截器调用此方法和 返回其返回值。但是,一个MethodInterceptor,就像任何周围的建议一样,可以 返回不同的值或抛出异常,而不是调用 proceed 方法。 但是,您不想在没有充分理由的情况下这样做。spring-doc.cadn.net.cn

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

建议前

一种更简单的建议类型是之前的建议。这不需要MethodInvocation对象,因为它仅在进入方法之前被调用。spring-doc.cadn.net.cn

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

以下列表显示了MethodBeforeAdvice接口:spring-doc.cadn.net.cn

public interface MethodBeforeAdvice extends BeforeAdvice {

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

请注意,返回类型为void.Before 通知可以在联接之前插入自定义行为 point 运行,但无法更改返回值。如果 before 建议抛出 异常,它会停止拦截器链的进一步执行。例外 向上传播拦截器链。如果未选中或签名为 调用的方法,它直接传递给客户端。否则,它是 被 AOP 代理包装在未经检查的异常中。spring-doc.cadn.net.cn

以下示例显示了 Spring 中的 before 通知,它计算所有方法调用:spring-doc.cadn.net.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
	}
}
建议之前可以用于任何切入点。

投掷建议

如果引发连接点,则在返回连接点后调用抛出建议 异常。Spring 提供打字投掷建议。请注意,这意味着org.springframework.aop.ThrowsAdviceinterface 不包含任何方法。这是一个 标记接口,标识给定对象实现一个或多个类型化抛出 建议方法。这些应采用以下形式:spring-doc.cadn.net.cn

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

只需要最后一个参数。方法签名可以有一个或四个 参数,具体取决于建议方法是否对方法感兴趣,并且 参数。接下来的两个列表显示了作为抛出建议示例的类。spring-doc.cadn.net.cn

如果RemoteException被抛出(包括RemoteException):spring-doc.cadn.net.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
	}
}

与前面的建议不同,下一个示例声明了四个参数,因此它有 访问调用的方法、方法参数和目标对象。以下建议 如果ServletException被抛出:spring-doc.cadn.net.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
	}
}

最后一个示例说明了如何在单个类中使用这两种方法 处理两者RemoteExceptionServletException.任意数量的投掷建议 方法可以组合在单个类中。以下列表显示了最后一个示例:spring-doc.cadn.net.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 方法抛出已检查的异常,则它必须 匹配目标方法的声明异常,因此在某种程度上是 耦合到特定的目标方法签名。不要抛出未声明的选中 与目标方法的签名不兼容的异常!
投掷建议可用于任何切入点。

退货后建议

Spring 中的返回通知后必须实现org.springframework.aop.AfterReturningAdvice界面,如下表所示:spring-doc.cadn.net.cn

public interface AfterReturningAdvice extends Advice {

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

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

返回通知后的以下内容会计算所有成功的方法调用,这些方法调用具有 not throwed 异常:spring-doc.cadn.net.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.cadn.net.cn

返回后,建议可用于任何切入点。

介绍建议

Spring 将介绍建议视为一种特殊的拦截建议。spring-doc.cadn.net.cn

引言需要一个IntroductionAdvisorIntroductionInterceptor那 实现以下接口:spring-doc.cadn.net.cn

public interface IntroductionInterceptor extends MethodInterceptor {

	boolean implementsInterface(Class intf);
}

invoke()继承自 AOP 联盟的方法MethodInterceptor接口必须 实施介绍。也就是说,如果调用的方法位于引入的 接口,引入拦截器负责处理方法调用——它 无法调用proceed().spring-doc.cadn.net.cn

介绍建议不能与任何切入点一起使用,因为它仅适用于课堂, 而不是方法,水平。您只能将介绍建议与IntroductionAdvisor,它有以下方法:spring-doc.cadn.net.cn

public interface IntroductionAdvisor extends Advisor, IntroductionInfo {

	ClassFilter getClassFilter();

	void validateInterfaces() throws IllegalArgumentException;
}

public interface IntroductionInfo {

	Class<?>[] getInterfaces();
}

没有MethodMatcher因此,没有Pointcut与介绍相关 建议。只有类过滤是合乎逻辑的。spring-doc.cadn.net.cn

getInterfaces()方法返回此顾问引入的接口。spring-doc.cadn.net.cn

validateInterfaces()方法在内部使用,以查看 引入的接口可以通过配置的IntroductionInterceptor.spring-doc.cadn.net.cn

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

public interface Lockable {
	void lock();
	void unlock();
	boolean locked();
}
interface Lockable {
	fun lock()
	fun unlock()
	fun locked(): Boolean
}

这说明了混合。我们希望能够将建议对象转换为Lockable, 无论它们的类型如何,并调用锁定和解锁方法。如果我们将lock()方法,我们 希望所有 setter 方法抛出一个LockedException.因此,我们可以添加一个方面 提供了使对象不可变的能力,而无需他们对此一无所知: AOP 的一个很好的例子。spring-doc.cadn.net.cn

首先,我们需要一个IntroductionInterceptor这完成了繁重的工作。在这个 case,我们将org.springframework.aop.support.DelegatingIntroductionInterceptor便利类。我们可以实施IntroductionInterceptor直接,但使用DelegatingIntroductionInterceptor对于大多数情况来说是最好的。spring-doc.cadn.net.cn

DelegatingIntroductionInterceptor旨在将介绍委托给 实际实现引入的接口,隐蔽使用拦截 这样做。您可以使用构造函数参数将委托设置为任何对象。这 默认委托(当使用无参数构造函数时)是this.因此,在下一个示例中, 委托是LockMixin的子类DelegatingIntroductionInterceptor. 给定一个委托(默认情况下,它本身),一个DelegatingIntroductionInterceptor实例 查找委托实现的所有接口(除了IntroductionInterceptor)并支持针对其中任何一个的介绍。 子类,例如LockMixin可以调用suppressInterface(Class intf)抑制不应公开的接口的方法。然而,无论有多少 接口IntroductionInterceptor准备支持,IntroductionAdvisorused 控制实际公开的接口。一 引入的接口隐藏目标对同一接口的任何实现。spring-doc.cadn.net.cn

因此LockMixin延伸DelegatingIntroductionInterceptor和机具Lockable本身。超类会自动拾取Lockable可以支持 介绍,所以我们不需要具体说明。我们可以引入任意数量的 接口。spring-doc.cadn.net.cn

请注意使用locked实例变量。这有效地添加了额外的状态 到目标对象中持有的那个。spring-doc.cadn.net.cn

以下示例显示了示例LockMixin类:spring-doc.cadn.net.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)
	}
}

通常,您不需要覆盖invoke()方法。这DelegatingIntroductionInterceptor实现(调用delegate方法 如果 引入该方法,否则朝着连接点前进)通常 够。在本例中,我们需要添加一个检查:不能调用任何 setter 方法 如果处于锁定模式。spring-doc.cadn.net.cn

所需的引言只需要保留一个不同的LockMixin实例并指定引入的接口(在本例中,仅Lockable).更复杂的示例可能会参考引言 拦截器(将被定义为原型)。在这种情况下,没有 与LockMixin,因此我们使用new. 以下示例显示了我们的LockMixinAdvisor类:spring-doc.cadn.net.cn

public class LockMixinAdvisor extends DefaultIntroductionAdvisor {

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

我们可以非常简单地应用这个顾问,因为它不需要配置。(然而,它 不可能使用IntroductionInterceptor没有IntroductionAdvisor.)与往常一样,顾问必须是每个实例, 因为它是有状态的。我们需要一个不同的实例LockMixinAdvisor,因此LockMixin,对于每个建议对象。顾问包括建议对象的一部分 州。spring-doc.cadn.net.cn

我们可以通过使用Advised.addAdvisor()method 或 (推荐的方式)在 XML 配置中,就像任何其他顾问一样。所有代理创建 下面讨论的选择,包括“自动代理创建者”,可以正确处理介绍 和有状态混合。spring-doc.cadn.net.cn