Declaring Advice

通知与切点表达式关联,并在与切点匹配的方法执行之前、之后或前后运行。切点表达式可以是 inline pointcut,也可以是对 named pointcut 的引用。

Advice is associated with a pointcut expression and runs before, after, or around method executions matched by the pointcut. The pointcut expression may be either an inline pointcut or a reference to a named pointcut.

Before Advice

您可以使用 @Before 批注在方面中声明之前的建议。

You can declare before advice in an aspect by using the @Before annotation.

以下示例使用内嵌切入点表达式。

The following example uses an inline pointcut expression.

  • Java

  • Kotlin

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() {
		// ...
	}
}

如果我们使用 named pointcut,我们可以按如下方式重写前一个示例:

If we use a named pointcut, we can rewrite the preceding example as follows:

  • Java

  • Kotlin

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() {
		// ...
	}
}

After Returning Advice

当匹配方法执行正常返回时,返回通知运行。你可以通过使用 @AfterReturning 注释声明它。

After returning advice runs when a matched method execution returns normally. You can declare it by using the @AfterReturning annotation.

  • Java

  • Kotlin

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声明(以及其他成员),全部在同一个aspect中。我们在这些示例中仅显示一个advice声明,以重点关注每个advice的效果。

You can have multiple advice declarations (and other members as well), all inside the same aspect. We show only a single advice declaration in these examples to focus the effect of each one.

有时,你需要在通知正文中访问实际的返回值。你可以使用 @AfterReturning 的形式将返回值与 getthataccess 绑定,如下面的示例所示:

Sometimes, you need access in the advice body to the actual value that was returned. You can use the form of @AfterReturning that binds the return value to get that access, as the following example shows:

  • Java

  • Kotlin

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?) {
		// ...
	}
}

returning 特性中的名称必须与通知方法中的参数名称对应。当方法执行返回时,返回值作为相应的参数值传递给通知方法。returning 子句还将匹配限制为仅返回指定类型(在这种情况下,Object 匹配任何返回值)的那些方法执行。

The name used in the returning attribute must correspond to the name of a parameter in the advice method. When a method execution returns, the return value is passed to the advice method as the corresponding argument value. A returning clause also restricts matching to only those method executions that return a value of the specified type (in this case, Object, which matches any return value).

请注意,在使用返回通知后不可能返回完全不同的引用。

Please note that it is not possible to return a totally different reference when using after returning advice.

After Throwing Advice

当匹配的方法执行通过抛出一个异常退出时,抛出通知运行。你可以通过使用 @AfterThrowing 注释声明它,如下面的示例所示:

After throwing advice runs when a matched method execution exits by throwing an exception. You can declare it by using the @AfterThrowing annotation, as the following example shows:

  • Java

  • Kotlin

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() {
		// ...
	}
}

通常,你希望仅在抛出给定类型的异常时才运行通知,并且还经常需要在通知正文中访问抛出的异常。你可以使用 throwing 特性来限制匹配(如果需要 - 否则将 Throwable 用作异常类型)并将抛出的异常绑定到通知参数。以下示例演示如何执行此操作:

Often, you want the advice to run only when exceptions of a given type are thrown, and you also often need access to the thrown exception in the advice body. You can use the throwing attribute to both restrict matching (if desired — use Throwable as the exception type otherwise) and bind the thrown exception to an advice parameter. The following example shows how to do so:

  • Java

  • Kotlin

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) {
		// ...
	}
}

throwing 特性中使用的名称必须与通知方法中的参数名称对应。当方法执行通过抛出一个异常退出时,异常作为相应的参数值传递给通知方法。throwing 子句还将匹配限制为仅抛出指定类型(在本例中为 DataAccessException)异常的那些方法执行。

The name used in the throwing attribute must correspond to the name of a parameter in the advice method. When a method execution exits by throwing an exception, the exception is passed to the advice method as the corresponding argument value. A throwing clause also restricts matching to only those method executions that throw an exception of the specified type (DataAccessException, in this case).

请注意,@AfterThrowing 并不表示一般的异常处理回调。具体来说,@AfterThrowing 通知方法只应该从连接点(用户声明的目标方法)本身接收异常,而不是从伴随的 @After/@AfterReturning 方法。

Note that @AfterThrowing does not indicate a general exception handling callback. Specifically, an @AfterThrowing advice method is only supposed to receive exceptions from the join point (user-declared target method) itself but not from an accompanying @After/@AfterReturning method.

After (Finally) Advice

当匹配的方法执行退出时,最终(finally)通知运行。它通过使用 @After 注释声明。最终通知必须准备处理正常和异常返回条件。它通常用于释放资源和类似的用途。以下示例演示如何使用最终通知:

After (finally) advice runs when a matched method execution exits. It is declared by using the @After annotation. After advice must be prepared to handle both normal and exception return conditions. It is typically used for releasing resources and similar purposes. The following example shows how to use after finally advice:

  • Java

  • Kotlin

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() {
		// ...
	}
}

请注意,AspectJ 中的 @After 通知被定义为“最终通知”,类似于 try-catch 语句中的 finally 块。它将针对连接点(用户声明的目标方法)引发的任何结果(正常返回或异常)调用,与 @AfterReturning 相反,后者仅适用于成功的正常返回。

Note that @After advice in AspectJ is defined as "after finally advice", analogous to a finally block in a try-catch statement. It will be invoked for any outcome, normal return or exception thrown from the join point (user-declared target method), in contrast to @AfterReturning which only applies to successful normal returns.

Around Advice

最后一种通知是 around 通知。Around 通知在匹配方法的执行周围运行。它有机会在方法运行之前和之后都进行工作,并确定方法何时、如何,甚至是否真正运行。如果您需要在多线程方式下在方法执行之前和之后共享状态(例如,启动和停止计时器),则经常使用 around 通知。

The last kind of advice is around advice. Around advice runs "around" a matched method’s execution. It has the opportunity to do work both before and after the method runs and to determine when, how, and even if the method actually gets to run at all. Around advice is often used if you need to share state before and after a method execution in a thread-safe manner – for example, starting and stopping a timer.

始终使用满足您要求中最不强大的通知形式。

Always use the least powerful form of advice that meets your requirements.

例如,如果 before 通知足以满足您的需要,请勿使用 around 通知。

For example, do not use around advice if before advice is sufficient for your needs.

通过使用 @Around 注解为方法添加注释来声明周围建议。该方法应声明 Object 作为其返回类型,并且该方法的第一个参数的类型必须是 ProceedingJoinPoint。在建议方法的主体中,你必须在 ProceedingJoinPoint 上调用 proceed() 才能让底层方法运行。在没有参数的情况下调用 proceed() 将导致在调用底层方法时将调用者的原始参数提供给底层方法。对于高级用例,proceed() 方法有一个过载变量,它接受一个参数数组 (Object[])。数组中的值将在调用底层方法时用作底层方法的参数。

Around advice is declared by annotating a method with the @Around annotation. The method should declare Object as its return type, and the first parameter of the method must be of type ProceedingJoinPoint. Within the body of the advice method, you must invoke proceed() on the ProceedingJoinPoint in order for the underlying method to run. Invoking proceed() without arguments will result in the caller’s original arguments being supplied to the underlying method when it is invoked. For advanced use cases, there is an overloaded variant of the proceed() method which accepts an array of arguments (Object[]). The values in the array will be used as the arguments to the underlying method when it is invoked.

在使用 Object[] 调用时 proceed 的行为与 AspectJ 编译器编译后的连接点建议行为稍有不同。对于使用传统 AspectJ 语言编写的连接点建议,传递给 proceed 的参数数量必须与传递给连接点建议的参数数量相匹配(不是底层连接点获取的参数数量),并且在给定参数位置传递给 proceed 的值会取代连接点中与该值绑定的实体的原始值(现在如果这没有意义,不用担心)。

The behavior of proceed when called with an Object[] is a little different than the behavior of proceed for around advice compiled by the AspectJ compiler. For around advice written using the traditional AspectJ language, the number of arguments passed to proceed must match the number of arguments passed to the around advice (not the number of arguments taken by the underlying join point), and the value passed to proceed in a given argument position supplants the original value at the join point for the entity the value was bound to (do not worry if this does not make sense right now).

Spring 采取的方法更简单,并且更符合它基于代理,只执行语义的方式。如果你为 Spring 编写 "@1" 切面并且使用 "@2" 与 AspectJ 编译器和织入器一起用参数,你只需要知道这个差别。针对 Spring AOP 和 AspectJ,有一种方法可以编写 100% 兼容的切面,这个方法在 "@3" 中进行了讨论。

The approach taken by Spring is simpler and a better match to its proxy-based, execution-only semantics. You only need to be aware of this difference if you compile @AspectJ aspects written for Spring and use proceed with arguments with the AspectJ compiler and weaver. There is a way to write such aspects that is 100% compatible across both Spring AOP and AspectJ, and this is discussed in the following section on advice parameters.

环绕通知返回的值是由方法的调用者看到的返回值。例如,一个简单的缓存方面可以返回缓存中的值(如果有),如果没有,则调用 proceed()(并返回该值)。请注意,可以在环绕通知的主体中一次、多次或根本不调用 proceed。所有这些都是合法的。

The value returned by the around advice is the return value seen by the caller of the method. For example, a simple caching aspect could return a value from a cache if it has one or invoke proceed() (and return that value) if it does not. Note that proceed may be invoked once, many times, or not at all within the body of the around advice. All of these are legal.

如果您将 around advice 方法的返回类型声明为 voidnull 总是会被返回给调用者,进而忽略了对 proceed() 的任何调用结果。因此,建议 around advice 方法声明 Object 的返回类型。advice 方法通常应该返回从对 proceed() 的调用中返回的值,即使底层方法具有 void 返回类型。然而,advice 可以根据用例有选择地返回缓存值、包装值或其他值。

If you declare the return type of your around advice method as void, null will always be returned to the caller, effectively ignoring the result of any invocation of proceed(). It is therefore recommended that an around advice method declare a return type of Object. The advice method should typically return the value returned from an invocation of proceed(), even if the underlying method has a void return type. However, the advice may optionally return a cached value, a wrapped value, or some other value depending on the use case.

以下示例演示如何使用环绕通知:

The following example shows how to use around advice:

  • Java

  • Kotlin

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 Parameters

Spring 提供全类型建议,这意味着您在建议签名中声明所需的用于建议签名的参数(如我们之前看到的返回和抛出示例)而不是始终处理 Object[] 数组。我们之后在本文中了解如何将参数和其他上下文值提供给建议主体。首先,我们了解如何编写通用建议,以查找建议当前建议的方法。

Spring offers fully typed advice, meaning that you declare the parameters you need in the advice signature (as we saw earlier for the returning and throwing examples) rather than work with Object[] arrays all the time. We see how to make argument and other contextual values available to the advice body later in this section. First, we take a look at how to write generic advice that can find out about the method the advice is currently advising.

Access to the Current JoinPoint

任何通知方法都可以将其第一个参数声明为类型为 org.aspectj.lang.JoinPoint 的参数。请注意,环绕通知必须声明类型为 ProceedingJoinPoint 的第一个参数,后者是 JoinPoint 的子类。

Any advice method may declare, as its first parameter, a parameter of type org.aspectj.lang.JoinPoint. Note that around advice is required to declare a first parameter of type ProceedingJoinPoint, which is a subclass of JoinPoint.

JoinPoint 接口提供了许多有用的方法:

The JoinPoint interface provides a number of useful methods:

  • getArgs(): Returns the method arguments.

  • getThis(): Returns the proxy object.

  • getTarget(): Returns the target object.

  • getSignature(): Returns a description of the method that is being advised.

  • toString(): Prints a useful description of the method being advised.

请参阅 javadoc 了解更多详细信息。

See the javadoc for more detail.

Passing Parameters to Advice

我们已经了解了如何绑定返回的值或异常值(使用 afterreturning 和 after throwing 建议)。要使参数值可用于建议正文,您可以使用 args 的绑定形式。如果您在 args 表达式中使用参数名称来代替 atype 名称,那么在调用建议时将值与对应的参数一起传递。一个示例可以使这一点更清楚。假设您希望建议执行将 Account 对象作为第一个参数的 DAO 操作的执行,并且您需要在建议正文中访问这个帐户。您可以编写以下内容:

We have already seen how to bind the returned value or exception value (using after returning and after throwing advice). To make argument values available to the advice body, you can use the binding form of args. If you use a parameter name in place of a type name in an args expression, the value of the corresponding argument is passed as the parameter value when the advice is invoked. An example should make this clearer. Suppose you want to advise the execution of DAO operations that take an Account object as the first parameter, and you need access to the account in the advice body. You could write the following:

  • Java

  • Kotlin

@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,..) 部分有两个作用。首先,它将匹配限制为只匹配方法至少有一个参数的方法执行,并且传递给该参数的参数是 Account 的实例。其次,它通过 account 参数使实际 Account 对象可以用于建议。

The args(account,..) part of the pointcut expression serves two purposes. First, it restricts matching to only those method executions where the method takes at least one parameter, and the argument passed to that parameter is an instance of Account. Second, it makes the actual Account object available to the advice through the account parameter.

另一种写法是声明一个切入点,以便在与连接点匹配时“提供”Account 对象值,然后在建议中引用命名的切入点。如下所示:

Another way of writing this is to declare a pointcut that "provides" the Account object value when it matches a join point, and then refer to the named pointcut from the advice. This would look as follows:

  • Java

  • Kotlin

@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 编程指南。

See the AspectJ programming guide for more details.

代理对象 (this)、目标对象 (target) 和注释 (@within@target@annotation@args) 都可以采用类似的方式绑定。下一组示例显示了如何匹配使用 @Auditable 注释注释的方法的执行并提取审计代码:

The proxy object (this), target object (target), and annotations (@within, @target, @annotation, and @args) can all be bound in a similar fashion. The next set of examples shows how to match the execution of methods annotated with an @Auditable annotation and extract the audit code:

以下代码显示了 @Auditable 注释的定义:

The following shows the definition of the @Auditable annotation:

  • Java

  • Kotlin

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

以下代码显示了匹配 @Auditable 方法的执行的建议:

The following shows the advice that matches the execution of @Auditable methods:

Java
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") (1)
public void audit(Auditable auditable) {
	AuditCode code = auditable.value();
	// ...
}
1 References the publicMethod named pointcut defined in Combining Pointcut Expressions.
Kotlin
@Before("com.xyz.Pointcuts.publicMethod() && @annotation(auditable)") (1)
fun audit(auditable: Auditable) {
	val code = auditable.value()
	// ...
}
2 References the publicMethod named pointcut defined in Combining Pointcut Expressions.

Advice Parameters and Generics

Spring AOP 可以处理类声明和方法参数中使用的泛型。假设您有一个类似于以下内容的泛型类型:

Spring AOP can handle generics used in class declarations and method parameters. Suppose you have a generic type like the following:

  • Java

  • Kotlin

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

您可以通过将建议参数绑定到您要拦截方法的参数类型来将方法类型的拦截限制为某些参数类型:

You can restrict interception of method types to certain parameter types by tying the advice parameter to the parameter type for which you want to intercept the method:

  • Java

  • Kotlin

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

此方法不适用于泛型集合。因此,您无法定义如下切入点:

This approach does not work for generic collections. So you cannot define a pointcut as follows:

  • Java

  • Kotlin

@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<?> 并手动检查元素的类型。

To make this work, we would have to inspect every element of the collection, which is not reasonable, as we also cannot decide how to treat null values in general. To achieve something similar to this, you have to type the parameter to Collection<?> and manually check the type of the elements.

Determining Argument Names

建议调用中的参数绑定依赖于将切入点表达式中使用的名称与建议和切入点方法签名中声明的参数名称相匹配。

Parameter binding in advice invocations relies on matching the names used in pointcut expressions to the parameter names declared in advice and pointcut method signatures.

本部分可互换地使用术语 argumentparameter,因为 AspectJ API 将参数名称为变量名。

This section uses the terms argument and parameter interchangeably, since AspectJ APIs refer to parameter names as argument names.

Spring AOP 使用以下 ParameterNameDiscoverer 实现来确定参数名称。每个发现者将有机会发现参数名称,第一个成功的发现者获胜。如果未注册的发现者能够确定参数名称,则会抛出异常。

Spring AOP uses the following ParameterNameDiscoverer implementations to determine parameter names. Each discoverer will be given a chance to discover parameter names, and the first successful discoverer wins. If none of the registered discoverers is capable of determining parameter names, an exception will be thrown.

AspectJAnnotationParameterNameDiscoverer

Uses parameter names that have been explicitly specified by the user via the argNames attribute in the corresponding advice or pointcut annotation. See Explicit Argument Names for details.

KotlinReflectionParameterNameDiscoverer

Uses Kotlin reflection APIs to determine parameter names. This discoverer is only used if such APIs are present on the classpath.

StandardReflectionParameterNameDiscoverer

Uses the standard java.lang.reflect.Parameter API to determine parameter names. Requires that code be compiled with the -parameters flag for javac. Recommended approach on Java 8+.

AspectJAdviceParameterNameDiscoverer

Deduces parameter names from the pointcut expression, returning, and throwing clauses. See the javadoc for details on the algorithm used.

Explicit Argument Names

@AspectJ 建议和切入点注释具有可选的 argNames 属性,可以用来指定带注释的方法的参数名称。

@AspectJ advice and pointcut annotations have an optional argNames attribute that you can use to specify the argument names of the annotated method.

如果 @AspectJ 方面是由 AspectJ 编译器 (ajc) 编制的,即使没有调试信息,您也不必添加 argNames 属性,因为编译器会保留所需的信息。

If an @AspectJ aspect has been compiled by the AspectJ compiler (ajc) even without debug information, you do not need to add the argNames attribute, since the compiler retains the needed information.

类似地,如果一个 @AspectJ 方面是使用 -parameters 标志和 javac 编制的,则不必添加 argNames 属性,因为编译器会保留所需的信息。

Similarly, if an @AspectJ aspect has been compiled with javac using the -parameters flag, you do not need to add the argNames attribute, since the compiler retains the needed information.

以下示例展示了如何使用 argNames 属性:

The following example shows how to use the argNames attribute:

Java
@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 References the publicMethod named pointcut defined in Combining Pointcut Expressions.
2 Declares bean and auditable as the argument names.
Kotlin
@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
}
3 References the publicMethod named pointcut defined in Combining Pointcut Expressions.
4 Declares bean and auditable as the argument names.

如果第一个参数的类型是 JoinPointProceedingJoinPointJoinPoint.StaticPart,则可以从 argNames 属性的值中省略参数的名称。例如,如果您修改前面的建议以接收连接点对象,则 argNames 属性不必包含它:

If the first parameter is of type JoinPoint, ProceedingJoinPoint, or JoinPoint.StaticPart, you can omit the name of the parameter from the value of the argNames attribute. For example, if you modify the preceding advice to receive the join point object, the argNames attribute does not need to include it:

Java
@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 References the publicMethod named pointcut defined in Combining Pointcut Expressions.
2 Declares bean and auditable as the argument names.
Kotlin
@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
}
3 References the publicMethod named pointcut defined in Combining Pointcut Expressions.
4 Declares bean and auditable as the argument names.

对于类型为 JoinPointProceedingJoinPointJoinPoint.StaticPart 的第一个参数给予的特殊处理对于不收集任何其他连接点上下文的建议方法来说特别方便。在这种情况下,您可以省略 argNames 属性。例如,以下建议不必声明 argNames 属性:

The special treatment given to the first parameter of type JoinPoint, ProceedingJoinPoint, or JoinPoint.StaticPart is particularly convenient for advice methods that do not collect any other join point context. In such situations, you may omit the argNames attribute. For example, the following advice does not need to declare the argNames attribute:

Java
@Before("com.xyz.Pointcuts.publicMethod()") (1)
public void audit(JoinPoint jp) {
	// ... use jp
}
1 References the publicMethod named pointcut defined in Combining Pointcut Expressions.
Kotlin
@Before("com.xyz.Pointcuts.publicMethod()") (1)
fun audit(jp: JoinPoint) {
	// ... use jp
}
2 References the publicMethod named pointcut defined in Combining Pointcut Expressions.

Proceeding with Arguments

我们前面说过将描述如何编写 proceed 调用,它的参数可以在 Spring AOP 和 AspectJ 中保持一致。解决方案是确保建议签名按顺序绑定每个方法参数。以下示例展示了如何执行此操作:

We remarked earlier that we would describe how to write a proceed call with arguments that works consistently across Spring AOP and AspectJ. The solution is to ensure that the advice signature binds each of the method parameters in order. The following example shows how to do so:

Java
@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 References the inDataAccessLayer named pointcut defined in Sharing Named Pointcut Definitions.
Kotlin
@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))
}
2 References the inDataAccessLayer named pointcut defined in Sharing Named Pointcut Definitions.

在许多情况下,你无论如何都会进行此绑定(如前面的示例所示)。

In many cases, you do this binding anyway (as in the preceding example).

Advice Ordering

当多条建议都希望在同一连接点运行时会发生什么?Spring AOP 遵循与 AspectJ 相同的优先级规则,以确定建议执行的顺序。优先级最高的建议在“进入”时首先运行(因此,给定两条 before 建议,优先级最高的建议首先运行)。从连接点“退出”时,优先级最高的建议最后执行(因此,给定两条 after 建议,优先级最高的建议将第二个运行)。

What happens when multiple pieces of advice all want to run at the same join point? Spring AOP follows the same precedence rules as AspectJ to determine the order of advice execution. The highest precedence advice runs first "on the way in" (so, given two pieces of before advice, the one with highest precedence runs first). "On the way out" from a join point, the highest precedence advice runs last (so, given two pieces of after advice, the one with the highest precedence will run second).

当定义在不同方面中的两条建议都需要在同一连接点运行时,除非你另有指定,否则执行顺序是不确定的。你可以通过指定优先级来控制执行顺序。这是通过正常 Spring 方式完成的,即在方面类中实现 org.springframework.core.Ordered 接口或对其进行 @Order 注释。给定两个方面,从 Ordered.getOrder()(或注释值)返回较低值的方面具有较高的优先级。

When two pieces of advice defined in different aspects both need to run at the same join point, unless you specify otherwise, the order of execution is undefined. You can control the order of execution by specifying precedence. This is done in the normal Spring way by either implementing the org.springframework.core.Ordered interface in the aspect class or annotating it with the @Order annotation. Given two aspects, the aspect returning the lower value from Ordered.getOrder() (or the annotation value) has the higher precedence.

某个方面的不同建议类型在概念上打算直接应用于连接点。因此,@AfterThrowing 建议方法不应从随同的 @After/@AfterReturning 方法接收异常。

Each of the distinct advice types of a particular aspect is conceptually meant to apply to the join point directly. As a consequence, an @AfterThrowing advice method is not supposed to receive an exception from an accompanying @After/@AfterReturning method.

从 Spring Framework 5.2.7 开始,在同一 @Aspect 类中定义的需要在同一连接点运行的建议方法将根据其建议类型从高到低优先级排序:@Around, @Before, @After,@AfterReturning, @AfterThrowing。但是,请注意,@After 建议方法将在同一方面中任何 @AfterReturning@AfterThrowing 建议方法之后有效地被调用,这遵循 AspectJ 的 @After 的“after finally 方面”语义。

As of Spring Framework 5.2.7, advice methods defined in the same @Aspect class that need to run at the same join point are assigned precedence based on their advice type in the following order, from highest to lowest precedence: @Around, @Before, @After, @AfterReturning, @AfterThrowing. Note, however, that an @After advice method will effectively be invoked after any @AfterReturning or @AfterThrowing advice methods in the same aspect, following AspectJ’s "after finally advice" semantics for @After.

当在同一 @Aspect 类中定义的两条相同类型建议(例如两条 @After 建议方法)都需要在同一连接点运行时,则顺序是不确定的(因为无法通过反射获取 java 编译类的源代码声明顺序)。考虑将此类建议方法合并为每个连接点一个建议方法,以便每个 @Aspect 类或将建议方法重构成单独的 @Aspect 类,这样你可以在方面级别通过 Ordered@Order 对其进行排列。

When two pieces of the same type of advice (for example, two @After advice methods) defined in the same @Aspect class both need to run at the same join point, the ordering is undefined (since there is no way to retrieve the source code declaration order through reflection for javac-compiled classes). Consider collapsing such advice methods into one advice method per join point in each @Aspect class or refactor the pieces of advice into separate @Aspect classes that you can order at the aspect level via Ordered or @Order.