对于最新的稳定版本,请使用 Spring Framework 6.2.0spring-doc.cadn.net.cn

申报通知书

通知与切入点表达式相关联,并在方法之前、之后或周围运行 与 pointcut 匹配的执行。切入点表达式可以是内联 切入点或对命名切入点的引用。spring-doc.cadn.net.cn

建议前

你可以使用@Before注解。spring-doc.cadn.net.cn

下面的示例使用内联切入点表达式。spring-doc.cadn.net.cn

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

	@Before("execution(* com.xyz.dao.*.*(..))")
	public void doAccessCheck() {
		// ...
	}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before

@Aspect
class BeforeExample {

	@Before("execution(* com.xyz.dao.*.*(..))")
	fun doAccessCheck() {
		// ...
	}
}

如果我们使用命名的切入点,我们可以重写前面的示例 如下:spring-doc.cadn.net.cn

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class BeforeExample {

	@Before("com.xyz.CommonPointcuts.dataAccessOperation()")
	public void doAccessCheck() {
		// ...
	}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before

@Aspect
class BeforeExample {

	@Before("com.xyz.CommonPointcuts.dataAccessOperation()")
	fun doAccessCheck() {
		// ...
	}
}

退货后通知

返回后,通知将在匹配的方法执行正常返回时运行。 您可以使用@AfterReturning注解。spring-doc.cadn.net.cn

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

	@AfterReturning("execution(* com.xyz.dao.*.*(..))")
	public void doAccessCheck() {
		// ...
	}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning

@Aspect
class AfterReturningExample {

	@AfterReturning("execution(* com.xyz.dao.*.*(..))")
	fun doAccessCheck() {
		// ...
	}
}
您可以有多个 advice 声明(以及其他成员), 都在同一个方面。在这些 examples 来集中每个 API 的效果。

有时,您需要在通知正文中访问返回的实际值。 您可以使用@AfterReturning,该 API 绑定返回值以获取 access,如下例所示:spring-doc.cadn.net.cn

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;

@Aspect
public class AfterReturningExample {

	@AfterReturning(
		pointcut="execution(* com.xyz.dao.*.*(..))",
		returning="retVal")
	public void doAccessCheck(Object retVal) {
		// ...
	}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterReturning

@Aspect
class AfterReturningExample {

	@AfterReturning(
		pointcut = "execution(* com.xyz.dao.*.*(..))",
		returning = "retVal")
	fun doAccessCheck(retVal: Any?) {
		// ...
	}
}

returningattribute 必须与参数的名称相对应 在 Advice 方法中。当方法执行返回时,返回值将传递给 将 Advice 方法作为相应的参数值。一个returning子句 将匹配限制为仅返回 指定类型(在本例中为Object,它与任何返回值匹配)。spring-doc.cadn.net.cn

请注意,在以下情况下,无法返回完全不同的引用 使用后返回建议。spring-doc.cadn.net.cn

抛出后的建议

抛出后,当匹配的方法执行退出时,通过抛出一个 例外。您可以使用@AfterThrowing注解,作为 以下示例显示:spring-doc.cadn.net.cn

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

	@AfterThrowing("execution(* com.xyz.dao.*.*(..))")
	public void doRecoveryActions() {
		// ...
	}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing

@Aspect
class AfterThrowingExample {

	@AfterThrowing("execution(* com.xyz.dao.*.*(..))")
	fun doRecoveryActions() {
		// ...
	}
}

通常,您希望通知仅在引发给定类型的异常时运行。 而且你还经常需要访问 Advice Body 中引发的异常。您可以 使用throwing属性来限制匹配(如果需要 — 使用Throwable作为异常类型),并将引发的异常绑定到 Advice 参数。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;

@Aspect
public class AfterThrowingExample {

	@AfterThrowing(
		pointcut="execution(* com.xyz.dao.*.*(..))",
		throwing="ex")
	public void doRecoveryActions(DataAccessException ex) {
		// ...
	}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.AfterThrowing

@Aspect
class AfterThrowingExample {

	@AfterThrowing(
		pointcut = "execution(* com.xyz.dao.*.*(..))",
		throwing = "ex")
	fun doRecoveryActions(ex: DataAccessException) {
		// ...
	}
}

throwingattribute 必须与 建议方法。当方法执行通过引发异常退出时,异常 作为相应的参数值传递给通知方法。一个throwing第 还将匹配限制为仅那些抛出 指定类型 (DataAccessException,在本例中)。spring-doc.cadn.net.cn

请注意,@AfterThrowing不表示常规异常处理回调。 具体来说,@AfterThrowing通知方法只应该接收异常 从连接点(用户声明的 target 方法)本身,而不是从附带的@After/@AfterReturning方法。spring-doc.cadn.net.cn

之后(最后)建议

After (finally) 通知在匹配的方法执行退出时运行。它由 使用@After注解。建议后必须准备好处理正常和 异常返回条件。它通常用于释放资源和类似的 目的。以下示例演示如何使用 after finally 建议:spring-doc.cadn.net.cn

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;

@Aspect
public class AfterFinallyExample {

	@After("execution(* com.xyz.dao.*.*(..))")
	public void doReleaseLock() {
		// ...
	}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.After

@Aspect
class AfterFinallyExample {

	@After("execution(* com.xyz.dao.*.*(..))")
	fun doReleaseLock() {
		// ...
	}
}

请注意,@After在 AspectJ 中,advice 被定义为“after finally advice”,类似于 添加到 try-catch 语句中的 finally 块。它将针对任何结果调用 从连接点(用户声明的目标方法)引发的正常返回或异常, 与@AfterReturning仅适用于成功的正常退货。spring-doc.cadn.net.cn

周边建议

最后一种建议是围绕建议。环绕建议 “绕过” 匹配的 方法的执行。它有机会在方法之前和之后都做工作 运行,并确定该方法何时、如何运行,甚至是否真的开始运行。 如果你需要在方法之前和之后共享状态,通常会使用 Around 通知 以线程安全的方式执行 – 例如,启动和停止计时器。spring-doc.cadn.net.cn

始终使用满足您要求的最弱的建议形式。spring-doc.cadn.net.cn

例如,如果 before advice 足以满足您的需求,请不要使用 around advice。spring-doc.cadn.net.cn

Around 通知是通过在 Git-Bean 中用@Around注解。这 method 应该声明Object作为其返回类型,以及该方法的第一个参数 必须是 typeProceedingJoinPoint.在 advice 方法的主体中,您必须 调用proceed()ProceedingJoinPoint为了让底层方法 跑。调用proceed()不带参数将导致调用方的原始 在调用底层方法时提供给底层方法的参数。高级用途 的情况下,存在proceed()方法接受 参数 (Object[]).数组中的值将用作 底层方法。spring-doc.cadn.net.cn

的行为proceed当使用Object[]与 的行为proceedfor around advice 由 AspectJ 编译器编译。对于周围 advice,传递给proceed必须与传递给 around 通知的参数数量匹配(而不是数量 的参数),并将传递给 continue 的值以 given argument position 替换实体 value 绑定到 (如果现在没有意义,请不要担心)。spring-doc.cadn.net.cn

Spring 采用的方法更简单,并且更符合其基于代理的 仅执行语义。只有在编译@AspectJ为 Spring 编写的方面和使用proceedwith arguments 替换为 AspectJ compiler 和 weaver 的 Fabric.有一种方法可以编写 100% 兼容的这些方面 Spring AOP 和 AspectJ 都有,这将在下面关于通知参数的部分中讨论。spring-doc.cadn.net.cn

around 通知返回的值是 方法。例如,如果 simple caching 方面有 一个或invokeproceed()(并返回该值)。请注意,proceed可以在 around 建议的正文中调用一次、多次或根本不调用。都 其中是合法的。spring-doc.cadn.net.cn

如果你将 around 通知方法的返回类型声明为void,null将始终返回给调用者,从而有效地忽略任何调用的结果 之proceed().因此,建议 around 通知方法声明一个 return 类型Object.通知方法通常应返回从 调用proceed(),即使底层方法具有voidreturn 类型。 但是,该通知可以选择返回缓存值、包装值或其他一些值 值。

下面的示例展示了如何使用 around 建议:spring-doc.cadn.net.cn

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;

@Aspect
public class AroundExample {

	@Around("execution(* com.xyz..service.*.*(..))")
	public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
		// start stopwatch
		Object retVal = pjp.proceed();
		// stop stopwatch
		return retVal;
	}
}
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.ProceedingJoinPoint

@Aspect
class AroundExample {

	@Around("execution(* com.xyz..service.*.*(..))")
	fun doBasicProfiling(pjp: ProceedingJoinPoint): Any? {
		// start stopwatch
		val retVal = pjp.proceed()
		// stop stopwatch
		return retVal
	}
}

Advice 参数

Spring 提供了完全类型化的通知,这意味着您可以在 advice 签名(正如我们之前看到的 return 和 throw 示例)而不是 使用Object[]数组。我们了解如何进行论证和其他上下文 值可用于本节后面的通知正文。首先,我们来看看如何 编写 generic advice,可以了解该 Advice 当前建议的方法。spring-doc.cadn.net.cn

访问当前JoinPoint

任何通知方法都可以将 type 为org.aspectj.lang.JoinPoint.请注意,需要 around advice 来声明第一个 type 为ProceedingJoinPoint,它是JoinPoint.spring-doc.cadn.net.cn

JoinPointinterface 提供了许多有用的方法:spring-doc.cadn.net.cn

有关更多详细信息,请参阅 javadocspring-doc.cadn.net.cn

将参数传递给 Advice

我们已经看到了如何绑定返回值或异常值(使用 after 返回和抛出建议后)。使参数值可用于通知 body 中,你可以使用args.如果使用参数名称代替 在argsexpression 时,相应参数的值将作为 调用通知时的 parameter 值。一个例子应该更清楚地说明这一点。 假设您要建议执行采用Accountobject 作为第一个参数,您需要在 Advice Body 中访问该账户。 您可以编写以下内容:spring-doc.cadn.net.cn

@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
public void validateAccount(Account account) {
	// ...
}
@Before("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
fun validateAccount(account: Account) {
	// ...
}

args(account,..)切入点表达式的一部分有两个用途。首先,它 将匹配限制为仅该方法至少采用一个 parameter 的 Alpha 参数,并且传递给该参数的参数是Account. 其次,它使实际的Account对象可通过account参数。spring-doc.cadn.net.cn

另一种写法是声明一个切入点,它 “提供”Accountobject 值,然后引用命名的切入点 来自建议。这将如下所示:spring-doc.cadn.net.cn

@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
private void accountDataAccessOperation(Account account) {}

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
	// ...
}
@Pointcut("execution(* com.xyz.dao.*.*(..)) && args(account,..)")
private fun accountDataAccessOperation(account: Account) {
}

@Before("accountDataAccessOperation(account)")
fun validateAccount(account: Account) {
	// ...
}

有关更多详细信息,请参阅 AspectJ 编程指南。spring-doc.cadn.net.cn

代理对象 (this)、目标对象 (target) 和注释 (@within,@target,@annotation@args) 都可以以类似的方式绑定。下一个 一组示例展示了如何匹配带有@Auditable注释并提取审计代码:spring-doc.cadn.net.cn

下面显示了@Auditable注解:spring-doc.cadn.net.cn

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
	AuditCode value();
}
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Auditable(val value: AuditCode)

下面显示了匹配@Auditable方法:spring-doc.cadn.net.cn

@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") (1)
public void audit(Auditable auditable) {
	AuditCode code = auditable.value();
	// ...
}
1 引用publicMethod命名切入点在 组合切入点表达式 中定义。
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") (1)
fun audit(auditable: Auditable) {
	val code = auditable.value()
	// ...
}
1 引用publicMethod命名切入点在 组合切入点表达式 中定义。

通知参数和泛型

Spring AOP 可以处理类声明和方法参数中使用的泛型。假设 您有一个泛型类型,如下所示:spring-doc.cadn.net.cn

public interface Sample<T> {
	void sampleGenericMethod(T param);
	void sampleGenericCollectionMethod(Collection<T> param);
}
interface Sample<T> {
	fun sampleGenericMethod(param: T)
	fun sampleGenericCollectionMethod(param: Collection<T>)
}

您可以通过以下方式将方法类型的拦截限制为某些参数类型 将 Advice 参数与要拦截方法的参数类型绑定:spring-doc.cadn.net.cn

@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
public void beforeSampleMethod(MyType param) {
	// Advice implementation
}
@Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
fun beforeSampleMethod(param: MyType) {
	// Advice implementation
}

此方法不适用于泛型集合。所以你不能定义 切入如下:spring-doc.cadn.net.cn

@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
public void beforeSampleMethod(Collection<MyType> param) {
	// Advice implementation
}
@Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
fun beforeSampleMethod(param: Collection<MyType>) {
	// Advice implementation
}

要做到这一点,我们必须检查集合的每个元素,而 合理,因为我们也无法决定如何对待null值。为了实现 与此类似,您必须键入参数Collection<?>和手动 检查元素的类型。spring-doc.cadn.net.cn

确定参数名称

通知调用中的参数绑定依赖于匹配切入点中使用的名称 Expression 添加到在 Advice 和 PointCut 方法签名中声明的参数名称。spring-doc.cadn.net.cn

本节可互换使用术语 argumentparameter,因为 AspectJ API 将参数名称称为参数名称。

Spring AOP 使用以下内容ParameterNameDiscoverer要确定的 implementations 参数名称。每个发现者都有机会发现参数名称,并且 第一个成功的 Discoverer 获胜。如果所有已注册的发现者都无法 确定参数名称时,将引发异常。spring-doc.cadn.net.cn

AspectJAnnotationParameterNameDiscoverer

使用已显式的参数名称 由用户通过argNames属性,或者 切入点注释。有关详细信息,请参阅 显式参数名称spring-doc.cadn.net.cn

KotlinReflectionParameterNameDiscoverer

使用 Kotlin 反射 API 确定 参数名称。仅当 Classpath 上存在此类 API 时,才使用此发现器。spring-doc.cadn.net.cn

StandardReflectionParameterNameDiscoverer

使用标准java.lang.reflect.Parameter用于确定参数名称的 API。要求使用-parameters的标志javac.Java 8+ 上的推荐方法。spring-doc.cadn.net.cn

LocalVariableTableParameterNameDiscoverer

分析可用的局部变量表 在 Advice 类的字节码中,从 Debug 信息中确定参数名称。 要求使用调试符号 (-g:vars至少)。荒废的 从 Spring Framework 6.0 开始,在 Spring Framework 6.1 中删除以支持编译 代码替换为-parameters.在 GraalVM 本机映像中不受支持。spring-doc.cadn.net.cn

AspectJAdviceParameterNameDiscoverer

从切入点推导出参数名称 表达returningthrowing第。有关所用算法的详细信息,请参阅 javadocspring-doc.cadn.net.cn

显式参数名称

@AspectJ advice 和 pointcut annotation 都有一个可选的argNames属性,您 可用于指定带 Comments 的方法的参数名称。spring-doc.cadn.net.cn

如果 @AspectJ 方面已被 AspectJ 编译器编译(ajc),即使没有 debug 信息,则无需添加argNames属性,因为编译器 保留所需的信息。spring-doc.cadn.net.cn

同样,如果 @AspectJ 方面已使用javac使用-parameters标志,则无需添加argNames属性,因为编译器会保留 需要的信息。spring-doc.cadn.net.cn

以下示例演示如何使用argNames属性:spring-doc.cadn.net.cn

@Before(
	value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", (1)
	argNames = "bean,auditable") (2)
public void audit(Object bean, Auditable auditable) {
	AuditCode code = auditable.value();
	// ... use code and bean
}
1 引用publicMethod命名切入点在 组合切入点表达式 中定义。
2 声明beanauditable作为参数名称。
@Before(
	value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", (1)
	argNames = "bean,auditable") (2)
fun audit(bean: Any, auditable: Auditable) {
	val code = auditable.value()
	// ... use code and bean
}
1 引用publicMethod命名切入点在 组合切入点表达式 中定义。
2 声明beanauditable作为参数名称。

如果第一个参数的类型为JoinPoint,ProceedingJoinPointJoinPoint.StaticPart中,您可以从argNames属性。例如,如果您修改前面的建议以接收 join point 对象、argNamesattribute 不需要包含它:spring-doc.cadn.net.cn

@Before(
	value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", (1)
	argNames = "bean,auditable") (2)
public void audit(JoinPoint jp, Object bean, Auditable auditable) {
	AuditCode code = auditable.value();
	// ... use code, bean, and jp
}
1 引用publicMethod命名切入点在 组合切入点表达式 中定义。
2 声明beanauditable作为参数名称。
@Before(
	value = "com.xyz.Pointcuts.publicMethod() && target(bean) && @annotation(auditable)", (1)
	argNames = "bean,auditable") (2)
fun audit(jp: JoinPoint, bean: Any, auditable: Auditable) {
	val code = auditable.value()
	// ... use code, bean, and jp
}
1 引用publicMethod命名切入点在 组合切入点表达式 中定义。
2 声明beanauditable作为参数名称。

对 type 的第一个参数进行特殊处理JoinPoint,ProceedingJoinPointJoinPoint.StaticPart特别方便咨询 不收集任何其他 Join Point 上下文的方法。在这种情况下,您可以 省略argNames属性。例如,以下建议不需要声明 这argNames属性:spring-doc.cadn.net.cn

@Before("com.xyz.Pointcuts.publicMethod()") (1)
public void audit(JoinPoint jp) {
	// ... use jp
}
1 引用publicMethod命名切入点在 组合切入点表达式 中定义。
@Before("com.xyz.Pointcuts.publicMethod()") (1)
fun audit(jp: JoinPoint) {
	// ... use jp
}
1 引用publicMethod命名切入点在 组合切入点表达式 中定义。

继续参数

我们之前说过,我们将描述如何编写proceed调用 在 Spring AOP 和 AspectJ 中一致工作的参数。解决方案是 以确保 Advice 签名按顺序绑定每个 Method 参数。 以下示例显示了如何执行此作:spring-doc.cadn.net.cn

@Around("execution(List<Account> find*(..)) && " +
		"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
		"args(accountHolderNamePattern)") (1)
public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
		String accountHolderNamePattern) throws Throwable {
	String newPattern = preProcess(accountHolderNamePattern);
	return pjp.proceed(new Object[] {newPattern});
}
1 引用inDataAccessLayer在共享命名切入点定义中定义的命名切入点。
@Around("execution(List<Account> find*(..)) && " +
		"com.xyz.CommonPointcuts.inDataAccessLayer() && " +
		"args(accountHolderNamePattern)") (1)
fun preProcessQueryPattern(pjp: ProceedingJoinPoint,
						accountHolderNamePattern: String): Any? {
	val newPattern = preProcess(accountHolderNamePattern)
	return pjp.proceed(arrayOf<Any>(newPattern))
}
1 引用inDataAccessLayer在共享命名切入点定义中定义的命名切入点。

在许多情况下,您仍然会执行此绑定(如前面的示例所示)。spring-doc.cadn.net.cn

建议订购

当多个建议都想在同一个连接点运行时,会发生什么情况? Spring AOP 遵循与 AspectJ 相同的优先规则来确定通知的顺序 执行。最高优先级的建议首先运行“在途中”(因此,给定两个部分 of before 建议,则优先级最高的那个先运行)。“On the way out” 从 join point 时,最高优先级 advice 将运行在 last 之后(因此,给定两个 after advice 的 Advice 中,优先级最高的 ID 将排在第二位)。spring-doc.cadn.net.cn

当在不同方面定义的两条通知都需要在同一条下运行时 join point 的执行顺序,除非你另有指定,否则执行顺序是 undefined。您可以 通过指定 Precedence 来控制执行顺序。这是在正常情况下完成的 Spring 方式,方法是实现org.springframework.core.Ordered接口输入 aspect 类或使用@Order注解。给定两个方面, aspect 返回Ordered.getOrder()(或 annotation 值)具有 优先级更高。spring-doc.cadn.net.cn

特定方面的每种不同的建议类型在概念上都是要适用的 直接连接到连接点。因此,@AfterThrowing建议方法不是 应该从随附的@After/@AfterReturning方法。spring-doc.cadn.net.cn

从 Spring Framework 5.2.7 开始,在相同的@Aspect类 需要在同一连接点运行,则根据它们在 以下顺序,从最高优先级到最低优先级:@Around,@Before,@After,@AfterReturning,@AfterThrowing.但是请注意,一个@Afteradvice 方法将 有效地在任何@AfterReturning@AfterThrowing建议方法 在同一个方面,遵循 AspectJ 的 “after finally advice” 语义@After.spring-doc.cadn.net.cn

当两条相同类型的建议(例如,两个@After建议方法) 定义在@Aspectclass 都需要在同一个连接点运行,则 Ordering 未定义(因为无法通过 Reflection for JavaC 编译的类)。考虑将这些 advice 方法合并为一个 每个 join point 中每个 join 点的通知方法@Aspect类或将建议重构为 分开@Aspect您可以通过Ordered@Order.spring-doc.cadn.net.cn