【译】AOP- Advice Param And Order

名词解释

Advice 通知

Advice Param

Spring 提供完全类型化的Advice,这意味着您可以在Advice签名中声明所需的参数(正如我们之前在返回和抛出示例中看到的那样),而不是一直使用Object[]数组。我们将在本节后面看到如何使参数和其他上下文值可用于Advice主体。首先,我们看一下如何编写通用通知,以了解通知当前通知的方法。

访问当前JoinPoint

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

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

  • getArgs():返回方法参数。
  • getThis():返回代理对象。
  • getTarget():返回目标对象。
  • getSignature():返回对所通知方法的描述。
  • toString():打印所通知方法的有用描述。

有关更多详细信息,请参阅javadoc

将参数传递给 Advice

我们已经看到了如何绑定返回值或异常值(在返回和抛出通知之后使用)。要使参数值可用于通知正文,您可以使用args. 如果在args表达式中使用参数名称代替类型名称,则在调用通知时相应参数的值将作为参数值传递。一个例子应该更清楚地说明这一点。假设您要通知执行以Account 对象为第一个参数的 DAO 操作,并且您需要访问通知正文中的帐户。您可以编写以下内容:

java

@Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
    // ...
}

args(account,..)切入点表达式的部分有两个目的。首先,它将匹配限制为仅那些方法至少接受一个参数的方法执行,并且传递给该参数的参数是Account. 其次,它通过参数使实际Account对象可用于通知account

另一种写法是声明一个切入点,Account 当它匹配一个连接点时“提供”对象值,然后从通知中引用命名的切入点。这将如下所示:

java

@Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}

@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
    // ...
}

有关详细信息,请参阅 AspectJ 编程指南。

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

示例展示了@Auditable注解的定义:

java

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
    AuditCode value();
}

以下示例展示了与@Auditable注解方法执行相匹配的通知:

java

@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
    AuditCode code = auditable.value();
    // ...
}

Advice 参数和泛型

Spring AOP 可以处理类声明和方法参数中使用的泛型。假设你有一个像下面这样的泛型:

java

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

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

java

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

这种方法不适用于泛型集合。所以你不能定义一个切入点如下:

java

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

为了实现这一点,我们必须检查集合的每个元素,这是不合理的,因为我们也无法决定如何处理null值。要实现类似的效果,您必须将参数键入Collection<?>并手动检查元件的类型。

确定参数名称

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

本节交替使用argumentparameter 这两个术语,因为AspectJ API将parameter names 称之为 argument names。

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

AspectJAnnotationParameterNameDiscoverer

使用用户通过相应建议或切入点注释中的argNames属性显式指定的参数名称。有关详细信息,请参见显式参数名称。

KotlinReflectionParameterNameDiscoverer

使用Kotlin反射API来确定参数名称。只有当类路径上存在此类API时,才使用此发现程序。

StandardReflectionParameterNameDiscoverer

使用标准的java.lang.reflect.Parameter API来确定参数名称。要求使用javac的-parameters标志编译代码。Java 8+上的推荐方法。

AspectJAdviceParameterNameDiscoverer

从切入点表达式、返回子句和抛出子句中推导参数名称。有关所使用算法的详细信息,请参阅javadoc。

显式指定参数名称

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

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

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

  • 以下示例显示了如何使用该argNames属性:

java

@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",//1
        argNames="bean,auditable")//2
public void audit(Object bean, Auditable auditable)  {                                                                                                                                   ble) {
    AuditCode code = auditable.value();
    // ... use code and bean
}

1、引用组合切入点表达式中定义的名为切入点的publicMethod。

2、声明bean和auditable作为参数名称。

如果第一个参数的类型为JoinPoint、ProceedingJoinPoint或JoinPoint.StaticPart,您可以从argNames属性的值中省略参数的名称。例如,如果修改前面的建议以接收连接点对象,则argNames属性不需要包括它:

java

@Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && 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作为参数名称。

对JoinPoint、ProceedingJoinPoint或JoinPoint类型的第一个参数进行的特殊处理。StaticPart对于不收集任何其他连接点上下文的建议方法特别方便。在这种情况下,可以省略argNames属性。例如,以下建议不需要声明argNames属性:

java

@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")//1
public void audit(JoinPoint jp) {
    // ... use jp
}

1、引用组合切入点表达式中定义的名为切入点的publicMethod。

参数处理

我们之前说过,我们将描述如何使用在Spring AOP和AspectJ中一致工作的参数编写一个继续调用。解决方案是确保通知签名按顺序绑定每个方法参数。以下示例显示了如何执行此操作:

@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命名切入点。

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

切面顺序

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

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

特定方面的每个不同建议类型在概念上都意味着直接应用于连接点。因此,@AfterThrowing建议方法不应该从附带的@After/@AfterReturning方法接收异常。

从Spring Framework 5.2.7开始,需要在同一连接点运行的同一@Aspect类中定义的建议方法根据其建议类型按以下顺序分配优先级,从最高到最低:

@Around、

@Before、

@After、

@AfterReturning

@AfterThrough。

然而,请注意,@After建议方法将在同一方面的任何@AfterReturning或@AfterThrough建议方法之后有效地调用,遵循AspectJ对@After的“After-finally建议”语义。

当在同一个@Aspect类中定义的两个相同类型的建议(例如,两个@After建议方法)都需要在同一连接点运行时,排序是未定义的(因为无法通过反射javac编译类来检索源代码声明顺序)。考虑将这些建议方法折叠为每个@Aspect类中每个连接点的一个建议方法,或者将这些建议重构为单独的@Aspect类别,您可以通过Ordered或@order在方面级别进行排序。

  • 14
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值