参考: https://blog.csdn.net/keda8997110/article/details/50747923 https://cloud.tencent.com/developer/article/1455539 https://www.cnblogs.com/zhangxufeng/p/9160869.html

以下为Spring支持的全部AOP表达式类型,虽然execution最常用,但也应当理解其他类型的含义和使用场景
msyx478w7c.jpeg

前提

一个应当注意的点是,aop表达式的关键部分是类型名(方法所在类和参数类型),而有些方法可以区分相对路径,有些方法不行,而滥用通配符必然会影响效率,所以,在AOP表达式中应该尽可能地使用完整类型名,即包路径+类型名。

execution

其表达式的全部语法为:
execution([modifiersP] ret-typeP [declaring-typeP]nameP(paramP) [throwsP])
其中P代表pattern。方括号表示非彼徐翔,各项语义如下:

  • modifiers-pattern:方法的可见性,如public,protected;
  • ret-type-pattern:方法的返回值类型,如int,void等;
  • declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
  • name-pattern:方法名类型,如buisinessService();
  • param-pattern:方法的参数类型,如java.lang.String;应当注意空的参数格式也是一种valid的参数格式
  • throws-pattern:方法抛出的异常类型,如java.lang.Exception;

其中,必要参数可以使用通配符做部分替换,共有两种通配符:

  1. *通配符:用于匹配单个单词,或者用于前缀或后缀的匹配。如表达式execution(* com.spring.service.Prefix*.*())中,*号就用于匹配返回值、以Prefix为前缀的类名,上述类名内所有的方法名
  2. ..通配符:用于匹配0或多个项,常用于配合匹配包路径,以及用于指定返回值参数类型。例如表达式`execution( com.spring...businessService(java.lang.String,..))`,其中..指的是com.spring的当前目录及其所有子目录,而匹配了一个类;后面的..则匹配了除第一个String类参数后的所有参数,所以整体上,这个表达式匹配的目标是“在com.spring包及其子包下的所有类中的至少接受一个String参数的businessService方法”。

在2.中,..指代的是值列表,*则匹配单个值,所以指定某路径下的所有类,必须要用..*,指定当前目录下的所有类则是.*

使用上述指定的模式和通配符搭配,就可以制定execution语境下所有的切面场景。

within

within的粒度较粗,指定的是类层面,为类内所有方法的执行创建切点(但只能是对象发方法而不能是static方法)。因此它只有一个declaring-type-pattern参数:
within(declaring-type-pattern)
!需要注意的是,within的参数必须是显式的期望方法所在的实体实现类,对接口的声明并没有作用

args

相当于是对execution中把参数部分拿出来,故其形式为args(param-pattern)

this和target

this和target表达式的作用大抵相同,但是需要甄别,所以将二者放在一起讨论。

部分细节参考自:https://blog.csdn.net/yangshangwei/article/details/77861658

文中说道:

  • target()切点函数通过判断目标类是否按类型匹配指定类来决定连接点是否匹配,即按类型匹配. 用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;

  • this()切点函数则通过判断代理类是否按类型匹配指定类来决定是否和切点匹配,即按对象本身匹配。 用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配。 this中使用的表达式必须是类型全限定名,不支持通配符

这里的解释相当绕口,首先从二者共同的功能谈起:

二者相同且与execution/within的不同之处

execution/within的局限之处(或者说整洁之处)在于,如果二者有效地修饰了一些类/接口(再次强调within不能用于接口)对应的方法,那么他们的实现/继承者们不属于这些类和接口的方法,execution/within是无法监测到的,而this和target就是搞定这些方法。
从这个角度,我们可以开始理解到,this和target中提到的调用层级一般是指继承/实现的OO层级。它能够实现前述的功能:当A是父类或接口,定义了methodA,而B实现或继承了A,在实现methodA的基础上有自己的methodB,我们使用this(A)target(A)作为切面后,这个切面就不仅作用在methodA,还会作用在methodB上。
其实这个功能看起来有些鸡肋,甚至是有点Anti-OOP的,但是它确实可以方便地一个切点,全局省心。

二者的差异

AOP的一个重要用途是对类的“动态增强”,即在不增加代码的基础上,为对象添加新功能(以方法的形式)。这听起来很神奇,但它可以通过Java特有的Mixin方法实现。

这个实现的方法见:https://www.yuque.com/riseuplzy/gl3gpl/zu2ea3#DgBK9

明白了这个神奇的实现,我们知道,被@DeclareParents“附魔”后的对象,其实变成了一个动态代理对象,它instanceof所有mixin的类型,包括原先的类;此时,如果我们对一个mixin类型作this/target切片,两个方法就会有不同的表现:

  • this切片能过作用于mixin类型的方法和原类型的其他方法
  • target切片只能作用于mixin类型方法

而有了这个实验结果,我们在返回去看描述,就能找到原因:this基于对象本身判别,它拿到了JDK代理对象,因此这个对象所有的方法都会被this切面作用;而target根据类型判别,它不知道代理对象的存在,在他看来这个被附魔的对象跟mixin类型并没有关系,因此它的切面加不到除mixin类型声明外的其他方法上。

@within

格式为@within(anno-declaring-type-pattern),用于匹配所有持有注解的目标类。它同样只对实体类生效,而对接口不生效。

@annotation

格式为@annotation(anno-declaring-type-pattern),用于匹配所有持有注解的方法。

@args

格式与args相同,均为@args(param-pattern),但要注意,这里的param-pattern必须全为注解,这代表指定位置的参数持有对应类型的注解,如@args(com.example.Anno1,com.example.Anno2,..)
因为args和@args的匹配机制极易添加额外匹配,所以一般都是用于AOP表达式中的额外限定。

@target

格式为@target(anno-declaring-type-pattern)其匹配机制与target完全相同,只是目标是持有指定注解的类/接口。

bean

是springaop下扩展的表达式,格式为bean(beanNameString),其中可以使用*通配符来部分匹配beanName,它作用于所有name能被给出的表达式匹配的Bean。

表达式的运算

表达式可以被视为token,进而可以用一些逻辑连词符进行简单运算
可用的逻辑连词符主要有&& 、||和!,用这些连词符可以指定一些相对复杂的切片场景