此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
声明建议
通知与切入点表达式相关联,并在方法之前、之后或周围运行 与切入点匹配的处决。切入点表达式可以是内联 切入点或对命名切入点的引用。
建议前
您可以使用@Before
注解。
以下示例使用内联切入点表达式。
-
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() {
// ...
}
}
如果我们使用命名的切入点,我们可以重写前面的示例 如下:
-
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() {
// ...
}
}
退货后建议
返回通知后,当匹配的方法执行正常返回时运行。
您可以使用@AfterReturning
注解。
-
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() {
// ...
}
}
您可以有多个建议声明(以及其他成员), 都在同一个方面。我们在这些中仅显示一个建议声明 示例来集中每个效果。 |
有时,您需要在通知正文中访问返回的实际值。
您可以使用@AfterReturning
绑定返回值以获取
访问权限,如以下示例所示:
-
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
属性必须对应于参数的名称
在建议方法中。当方法执行返回时,返回值将传递给
advice 方法作为相应的参数值。一个returning
子句
将匹配限制为仅返回
指定的类型(在本例中,Object
,与任何返回值匹配)。
请注意,在以下情况下,不可能返回完全不同的引用 返回建议后使用。
抛出建议后
抛出建议后,当匹配的方法执行退出时,通过抛出
例外。您可以使用@AfterThrowing
注释,作为
以下示例显示:
-
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
否则作为异常类型),并将抛出的异常绑定到 advice 参数。
以下示例显示了如何执行此作:
-
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
,在本例中)。
请注意 |
在(最后)建议之后
After (finally) 通知在匹配的方法执行退出时运行。它是由
使用@After
注解。在建议后必须准备好处理正常和
异常返回条件。它通常用于释放资源等
目的。以下示例显示了如何使用 after finally 建议:
-
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() {
// ...
}
}
请注意 |
周边建议
最后一种建议是围绕建议。围绕建议“围绕”匹配的 方法的执行。它有机会在方法之前和之后都做工作 运行并确定何时、如何运行,甚至该方法是否真的可以运行。 如果您需要在方法之前和之后共享状态,通常会使用 Around 建议 以线程安全的方式执行 - 例如,启动和停止计时器。
始终使用满足您要求的最不强大的建议形式。 例如,如果之前的建议足以满足您的需求,请不要使用周围建议。 |
Around 建议是通过使用@Around
注解。这
方法应声明Object
作为其返回类型,以及方法的第一个参数
必须是ProceedingJoinPoint
.在建议方法的正文中,您必须
调用proceed()
在ProceedingJoinPoint
为了让底层方法
跑。调用proceed()
没有参数将导致调用方的原始
调用基础方法时提供给底层方法的参数。高级使用
情况下,存在proceed()
方法,该方法接受
参数 (Object[]
).数组中的值将用作
调用 underlying 方法。
的行为 Spring 采用的方法更简单,与其基于代理的
仅执行语义。只有在编译 |
周围通知返回的值是调用者看到的返回值
方法。例如,如果简单缓存方面具有
one 或调用proceed()
(并返回该值)。请注意proceed
可以在周围建议的正文中调用一次、多次或根本不调用。都
其中是合法的。
如果将 around 建议方法的返回类型声明为void ,null 将始终返回给调用者,从而有效地忽略任何调用的结果
之proceed() .因此,建议使用 around 通知方法声明返回
类型Object .通知方法通常应返回从
调用proceed() ,即使基础方法具有void 返回类型。
但是,通知可以选择返回缓存值、包装值或其他一些值
值取决于用例。 |
以下示例显示了如何使用 around 建议:
-
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
}
}
通知参数
Spring 提供完全类型的建议,这意味着您可以在
advice 签名(正如我们之前在返回和抛出示例中看到的那样),而不是
与Object[]
数组。我们看到如何进行论证和其他上下文
本节后面的建议机构可用的值。首先,我们来看看如何
写通用建议,了解建议当前建议的方法。
访问当前JoinPoint
任何通知方法都可以声明类型为org.aspectj.lang.JoinPoint
.请注意,声明第一个需要周围建议
type 的参数ProceedingJoinPoint
,它是JoinPoint
.
这JoinPoint
interface 提供了许多有用的方法:
-
getArgs()
:返回方法参数。 -
getThis()
:返回代理对象。 -
getTarget()
:返回目标对象。 -
getSignature()
:返回正在建议的方法的描述。 -
toString()
:打印所建议方法的有用描述。
有关更多详细信息,请参阅 javadoc。
将参数传递给通知
我们已经看到了如何绑定返回值或异常值(使用 after
返回并在抛出建议后)。使参数值可用于通知
body,可以使用args
.如果使用参数名称代替
类型名称args
expression,则相应参数的值传递为
调用通知时的参数值。一个例子应该使这一点更清楚。
假设你想建议执行 DAO作,这些作需要Account
object 作为第一个参数,并且您需要访问通知正文中的帐户。
你可以写以下内容:
-
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
参数。
另一种编写方式是声明一个切入点,该切入点“提供”了Account
对象值,然后引用命名的切入点
从建议。这将如下所示:
-
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 编程指南。
代理对象 (this
)、目标对象 (target
)和注释(@within
,@target
,@annotation
和@args
)都可以以类似的方式绑定。下一个
示例集显示了如何匹配用@Auditable
注释并提取审计代码:
下面显示了@Auditable
注解:
-
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
方法:
-
Java
-
Kotlin
@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 可以处理类声明和方法参数中使用的泛型。假设 您有一个如下所示的泛型类型:
-
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>)
}
您可以通过以下方式将方法类型的拦截限制为某些参数类型 将 advice 参数绑定到要截获方法的参数类型:
-
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
}
此方法不适用于泛型集合。因此,您不能定义 切入点,如下所示:
-
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<?>
和手动
检查元素的类型。
确定参数名称
通知调用中的参数绑定依赖于匹配切入点中使用的名称 表达式添加到通知和切入点方法签名中声明的参数名称。
本节可以互换使用术语参数和参数,因为 AspectJ API 将参数名称称为参数名称。 |
Spring AOP 使用以下内容ParameterNameDiscoverer
要确定的实现
参数名称。每个发现者都有机会发现参数名称,并且
第一个成功的发现者获胜。如果注册的发现者都不具备能力
确定参数名称时,将抛出异常。
AspectJAnnotationParameterNameDiscoverer
-
使用显式的参数名称 由用户通过
argNames
属性或 切入点注释。有关详细信息,请参阅显式参数名称。 KotlinReflectionParameterNameDiscoverer
-
使用 Kotlin 反射 API 确定 参数名称。仅当类路径上存在此类 API 时,才使用此发现器。
StandardReflectionParameterNameDiscoverer
-
使用标准
java.lang.reflect.Parameter
用于确定参数名称的 API。要求使用-parameters
标志javac
.Java 8+ 上的推荐方法。 AspectJAdviceParameterNameDiscoverer
-
从切入点推断参数名称 表达
returning
和throwing
第。有关所用算法的详细信息,请参阅 javadoc。
显式参数名称
@AspectJ建议和切入点注释具有可选的argNames
属性
可用于指定带注释的方法的参数名称。
@AspectJ如果 AspectJ 编译器( 同样,如果@AspectJ方面已使用 |
以下示例演示如何使用argNames
属性:
-
Java
-
Kotlin
@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 | 声明bean 和auditable 作为参数名称。 |
@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 | 声明bean 和auditable 作为参数名称。 |
如果第一个参数的类型为JoinPoint
,ProceedingJoinPoint
或JoinPoint.StaticPart
,您可以从argNames
属性。 例如,如果您修改前面的通知以接收连接point 对象,则argNames
属性不需要包含它:
-
Java
-
Kotlin
@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 | 声明bean 和auditable 作为参数名称。 |
@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 | 声明bean 和auditable 作为参数名称。 |
对类型的第一个参数给予的特殊处理JoinPoint
,ProceedingJoinPoint
或JoinPoint.StaticPart
特别方便用于建议不收集任何其他连接点上下文的方法。在这种情况下,您可以省略argNames
属性。 例如,以下建议不需要声明 这argNames
属性:
继续使用参数
我们之前提到过,我们将描述如何编写一个proceed
调用
在 Spring AOP 和 AspectJ 中一致工作的参数。解决方案是
以确保通知签名按顺序绑定每个方法参数。
以下示例显示了如何执行此作:
-
Java
-
Kotlin
@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 AOP 遵循与 AspectJ 相同的优先级规则来确定通知的顺序 执行。最高优先级建议首先“在进入的路上”运行(因此,给定两块 之前的建议,优先级最高的优先顺序)。“在离开的路上”来自一个 连接点,最高优先级通知最后运行(因此,给定两个 After 建议,优先级最高的将排在第二位)。
当在不同方面定义的两条建议都需要同时运行时
join point,除非另有指定,否则执行顺序是未定义的。您可以
通过指定优先级来控制执行顺序。这是在正常情况下完成的
Spring 方式,通过实现org.springframework.core.Ordered
界面
aspect 类或使用@Order
注解。给定两个方面,
aspect 从Ordered.getOrder()
(或注释值)具有
优先级越高。
特定方面的每种不同建议类型在概念上都是要适用的
直接到连接点。因此,一个 在同一 当两个相同类型的建议(例如,两个 |