现在我们可以研究一下 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 实现的类也应该实现
以下接口:AllianceMethodInterceptor
public interface MethodInterceptor extends Interceptor {
	Object invoke(MethodInvocation invocation) throws Throwable;
}
该方法的参数公开了
invoked、目标连接点、AOP 代理和方法的参数。该方法应返回调用的结果:join 的返回值
点。MethodInvocationinvoke()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()MethodInvocationMethodInterceptor
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
	}
}
最后一个示例说明了如何在单个类中使用这两种方法
,它同时处理 和 。任意数量的投掷建议
方法可以组合到一个类中。下面的清单显示了最后一个示例:RemoteExceptionServletException
- 
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
实现以下接口:IntroductionAdvisorIntroductionInterceptor
public interface IntroductionInterceptor extends MethodInterceptor {
	boolean implementsInterface(Class intf);
}
从 AOP Alliance 接口继承的方法必须
实施 Introduction。也就是说,如果调用的方法位于引入的
interface 中,INTRODUCTION interceptor 负责处理方法调用 — 它
无法调用 。invoke()MethodInterceptorproceed()
Introduction advice 不能与任何切入点一起使用,因为它仅适用于类
而不是 method, level.您只能将 introduction advice 与 一起使用,它具有以下方法:IntroductionAdvisor
public interface IntroductionAdvisor extends Advisor, IntroductionInfo {
	ClassFilter getClassFilter();
	void validateInterfaces() throws IllegalArgumentException;
}
public interface IntroductionInfo {
	Class<?>[] getInterfaces();
}
没有,因此,也没有与引言相关
建议。只有类过滤是合乎逻辑的。MethodMatcherPointcut
该方法返回此 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 的一个很好的例子。Lockablelock()LockedException
首先,我们需要一个能完成繁重工作的人。在这个
case 中,我们扩展了 convenience 类。我们可以直接实现,但在大多数情况下,使用是最好的。IntroductionInterceptororg.springframework.aop.support.DelegatingIntroductionInterceptorIntroductionInterceptorDelegatingIntroductionInterceptor
旨在将 introduction 委托给
实际实现引入的接口,隐藏使用拦截
执行此操作。您可以使用 constructor 参数将委托设置为任何对象。这
default delegate(使用无参数构造函数时)为 .因此,在下一个示例中,
委托是 的子类。
给定一个委托(默认情况下,它本身),一个实例
查找由 delegate (except than )实现的所有接口,并支持针对其中任何一个接口的介绍。
子类(如 can 调用该方法)来禁止显示不应公开的接口。然而,无论多少
接口 an 准备支持,使用的控件实际公开了哪些接口。一
introduced interface 隐藏了目标对同一接口的任何实现。DelegatingIntroductionInterceptorthisLockMixinDelegatingIntroductionInterceptorDelegatingIntroductionInterceptorIntroductionInterceptorLockMixinsuppressInterface(Class intf)IntroductionInterceptorIntroductionAdvisor
因此,扩展并实现自身。超类会自动拾取可以支持的
introduction,所以我们不需要指定。我们可以引入任意数量的
接口。LockMixinDelegatingIntroductionInterceptorLockableLockable
请注意 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()DelegatingIntroductionInterceptordelegate
所需的 introduction 只需要持有一个不同的实例并指定引入的接口(在本例中,only )。更复杂的示例可能会引用 introduction
interceptor (将被定义为原型)。在这种情况下,没有
与 A 相关的配置,因此我们使用 来创建它。
以下示例显示了我们的类:LockMixinLockableLockMixinnewLockMixinAdvisor
- 
Java
 - 
Kotlin
 
public class LockMixinAdvisor extends DefaultIntroductionAdvisor {
	public LockMixinAdvisor() {
		super(new LockMixin(), Lockable.class);
	}
}
class LockMixinAdvisor : DefaultIntroductionAdvisor(LockMixin(), Lockable::class.java)
我们可以非常简单地应用这个 advisor,因为它不需要配置。(但是,它
不能在没有 .) 的情况下使用 an与通常的 introduction 一样,顾问程序必须是每个实例的,
因为它是有状态的。对于每个建议的对象,我们需要一个不同的 , 实例 ,因此 。顾问程序包含被建议对象的
州。IntroductionInterceptorIntroductionAdvisorLockMixinAdvisorLockMixin
我们可以使用以下方法以编程方式应用此 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! | 
| 投掷建议可用于任何切入点。 | 
| 返回后,建议可以与任何切入点一起使用。 |