aop——Pointcut表达式

  本篇将介绍Pointcut表达式,它的语法格式如下:

修饰符(1)  pointcut(2)  name(3)  piontcut_type(signature)(4)

第一部分,修饰符,类似于Java,有public,private。它是可选的,默认值是public。它是pointcut的修饰符

第二部分,pointcut关键字,是最简单的部分

第三部分,pointcut的名称,类似于类命名,变量命名,有含义的名称即可。它是可选的,它是可选部分,name不存在时,为匿名pointcut。

  第四部分,type(signature),其中type描述的是pointcut的类型,它分为两种,kinded和non-kinded。其中kinded与Join point的类型是一一对应的。signature表示签名,它有三种,类签名,方法&构造器签名,字段签名。签名受到type的约束,当type为方法时,例如call,execution时,签名只能是方法和构造器签名。

最复杂的就是第四部分。首先介绍一些公共的内容。

1、公共部分

 公共部分包含三个部分,

第一个部分是逻辑表达式,与,或,非。这太基础了,略。

第二个部分特殊字符。

第三个部分是签名。

1.1 特殊字符

  * :可以是任何字符,当为类签名(Type signature)时,可以代表类全名的中的任何字符串。当为方法签名时,出现在参数中,表示任意类型的单个参数。当为其他签名时,表示名称中的任何字符串。例如方法签名的方法名,参数类型名。

.. :层次结构中的任何子层次,当为类签名时,可以代表包下的所有子包。当为方法签名时,出现在参数中,可以表示任何参数,不论类型,数量,顺序。

+:类层次结构,只适用于类签名。以当前类为树的根,所有该类的子类,子孙类,(树的所有节点和叶子节点)。

1.2  签名

  签名有三种类型,类签名,方法和构造器签名,属性签名。

1.2.1 类签名

  类签名的格式为:

(修饰符) (注解) (类全名) (+ || *)

  修饰符通常不会写,默认值为public。

  之后演示示例,遵循一般到特殊的顺序。

  1.2.1.1   普通类

无论它是接口,注解,枚举,抽象类等等,它的名称都遵循类全名的格式

  1. 包名:
    • 不限制,*  Account或者Account表示任何包下面的Account类,
    • 限制包名:com.bean*.Account,任何com包下,包名前缀为bean的子包,此时限制包层次结构为com包的第一层子包。
    • 限制顶级包,不限制包层次结构,com..Account,任何com包下的Account类,
    • 限制更具体的包,com.bean.*.Account,任何com.bean包下面的Account类。

  2. 类名:

    • 不限制 *,com.bean.*,任何com.bean包下的类。
    • 限制类名,com.bean.Account*,任何com.bean包下,类名前缀为Account的类。
    • 限制类层次结构,com.bean.Account+,任何com.bean包下Account类的子类,子类不一定与Account处于同一包下。

  1.2.1.2  类上有注解

此时可以拆分为注解,普通类。普通类遵循上述的规则,注解本身也是普通类的一种。所以它也基本遵循普通类。关键在于二者的结合。

  1. 单个注解
    • 不限制类名:@Test *,表示任何标注有Test的注解,此时需要注意@Test没有指名包名。
    • 只限制类名:@Test Account+,表示有Test 注解的Account子类
    • 同时限制: @Test* Account+,表示有Test为前缀的注解的Account子类。
    • 逻辑非: !@Test  *,表示任何没有Test注解的类。它的范围很广。

  2. 多个注解:假定两个注解,多个注解也是同理。

    • 逻辑与:@(Test && Test1)  *,表示同时有Test和Test1注解的任何类。
    • 逻辑或:@(Test || Test1)  *,表示有Test或Test1注解的任何类。

  1.2.1.3   类上有泛型

类上有泛型可以拆分为类,泛型类约束。类上可以有注解,泛型。泛型类上也可以有注解,泛型,它类似于无限层次的嵌套。层次深的话,会非常复杂,不过大部分泛型只有一层或两层,例如List<String>或List<Map<String,String>>。

  1. 泛型类
    • 具体的类和泛型类名:Map<String,String>,表示Map类型。Key和value都是String
    • 具体的泛型名:*<String>,表示类的泛型为String。例如List<String>,Set<String>,此时它限定了泛型的名称,数量
    • 都不是具体的:@Test *<@Test1 *>,表示有Test注解的类,同时拥有泛型,泛型的类型拥有Test1注解。

  2. 泛型上下限

    • 上限:Collection+<? Extends Account>,表示类型为Collection的子类,泛型类是Account的子类
    • 下限:Collection+<? Super Account>,表示类型为Collection的子类,泛型类是Account的父类。

三个综合起来会非常的复杂,遇到复杂的语法时最好拆分为泛型类,注解类,普通类三部分。无限重复这个拆分过程,直到泛型类,注解类也为普通类。

使用逻辑非时需要注意,否则会造成巨量的类被选中。这与SQL语句select *一样。应该尽量避免

1.2.2      方法&构造器签名

在介绍方法签名时,先介绍方法的组成要素,方法的要素有以下五个

  1. 方法上添加的注解,0个或多个
  2. 方法的修饰符,public,final,static,private,protected,abstract等。
  3. 方法的返回值,void表示无返回值,
  4. 方法的签名,名称和参数
  5. 方法抛出的异常。

方法的Pointcut的格式为Join point(method signature)

其中Join point只有两种:call(方法的调用),execution(方法的执行)。

Method signature由上述的五个部分组成。其中只有方法的返回值和方法的签名是必须的,其他都是可选的

1.2.2.1   注解

与类签名类似,分为两种情况。

  1. 单个注解:此时没有逻辑与,或关系,
    • 限制注解的名称,@Test*:表示以Test注解为前缀的方法
    • 限制注解的存在与否,@Test:表示方法上有@Test。 !@Test:表示没有@Test注解的所有方法,一般不使用,因为这个范围太广。

  2. 多个注解:逻辑与,逻辑或

    • @(Test || Test1):存在@Test或@Test1注解的方法。
    • @(Test && Test1):存在@Test和@Test1注解的方法。

  注:它是可选的

  1.2.2.2   修饰符

修饰符也分为两种情况,

  1. 单个修饰符:,
    • 限制修饰符的存在与否:public:表示public修饰的方法。!public表示没有public修饰的所有方法
    • 修饰符的逻辑关系:(public || private):表示有public或private修饰的方法。
  2. 修饰符组合:此时修饰符之间存在一些互斥关系

Public static:表示同时有public,static修饰的方法。需要注意组合之间存在一些互斥关系,可以存在public final static方法,不存在public final abstract方法。互斥规则遵循Java的语法规则。

注:它是可选的

1.2.2.3   方法的返回值

方法的返回值分为两种,第一种无返回值,使用void关键字,第二种返回值为任意对象。

  • 当无返回值,public static void:表示public static 修饰的无返回值方法。当不使用void关键字时自动转换为第二种,返回值为对象。
  • 返回值为对象时,类签名的语法都适用于这部分,意味着方法的返回值可以存在注解,可以带有泛型,也可以只是一个普通类。

注:它是必须的

1.2.2.4   方法的签名

方法的签名由两部分组成,名称和参数。

名称的格式为:类全名 + 方法的名称,其中类签名用于限制类全名部分。方法的名称比较简单,只是一个普通的字符串。

  • 前缀:test*,方法的名称前缀为test
  • 后缀:*test,方法的名称后缀为test
  • 全名:test,方法的名称为test。
  • *:表示任何方法名

参数的格式为:参数的类型 + 空格 + 参数的名称。其中对参数的名称是不做限定的,参数的类型适用于类签名。

  • .. :任何参数
  • 参数顺序和类型:String,List,表示该方法拥有两个参数,第一个参数为String类型,第二个参数为List类型。类签名适用于参数类型。

  1.2.2.5  方法抛出的异常

方法异常部分的格式为: throws关键字  + 空格 + 异常类型。异常类型必须继承Throwable。方法上抛出的异常大部分是CheckException。

1.2.2.6   示例

注解部分:@Test

修饰符:public

返回值:void

方法签名:com.bean.Account. set*(..)

方法异常:不指定

execution(@Test public void com.bean.Account.set*(..))

它会选出com.bean.Account类下面的所有setXXX方法,这些方法必须有@Test注解,返回值为void。

例如Spring的@Transcational注解

execution (@Transcational * *(..))

它会选出所有标注@Transcational注解的方法,

  • 它忽略了方法的修饰符,方法的异常,只有注解,返回值,方法签名三部分。
  • 第一个*号代表返回值,表示任意的对象,
  • 第二个*表示任何类下面的任何方法,
  • ..表示方法的任何参数。

复杂的地方在于方法中有很多局部可以使用类签名,例如返回值是类,方法参数是类,方法抛出的异常也是类。本质上方法注解也是类,它也可以使用类签名。

1.2.3      属性签名

Field Singature由四个部分组成

  1. 注解:字段上标注的注解
  2. 修饰符:除abstract之外,所有的修饰符基本与方法修饰符相同
  3. 类型:字段的类型
  4. 属性名:结构为类全名 + 属性名称。

  1.2.3.1   注解

与方法,类上的注解基本都相似。注:它是可选的

1.2.3.2   修饰符

与方法的修饰符相同。注:它是可选的

1.2.3.3   属性类型

本质上就是类签名。注:它是必须的

1.2.3.4   属性名称

本质上与方法名称基本相同,结构为类全名+属性名称。类签名适用于类全名,属性名本质上是一个字符串。注:它是必须的

从上述的三种签名可以看到,将Java元素作为粒度进行分类时,第一层包含类,类中的方法,类中的属性,在第二级分类时,由注解,修饰符,类,名称,异常等组成。这些基本相同。其中第二级中,类可以无限层次的嵌套,类似于JSON字符串的对象。

2、Pointcut type

它分为两类,kinded与non-kinded,kinded类型与join point是一一对应的。

2.1 kinded

  Kinded Pointcut的格式为join point(Signature),其中Join point与签名类型有关。

当为类签名时,join point为staticinitialization(初始化,静态代码块),signature对应Type Signature(类签名)

当为方法签名时,join point为call(调用)execution(执行),signature对应Method Signature。

当为构造器签名时,join point为call(调用)execution(执行),initialization(实例化),preInitialization(实例化之前)signature对应Constructor Signature。

当为属性签名时,join point为get(读取)和set(修改),signature对应Field Signature(属性签名)

当为Aspect时,join point为adviceexecution(advice方法体执行),无需指定signature。

2.2 non-kinded

  Non-Kinded Pointcut有以下几种类型。

  2.2.1   control-Flow

引用原著对Control flow的定义:

The control flow of a join point defines the flow of the program instructions that occur as a result of the invocation of the join point

方法的执行流程。

基于Control flow,有两种类型的non kinded pointcut。

cflow():selects the join points in the control flow of the specified pointcut,including the join points matching the pointcut

当前方法内部的所有区域涉及到的point类型,以及该方法本身。

cflowbelow:selects the same join points as cflow(),except for the join point that initiated the control flow

  当前方法内部的所有区域涉及到的point类型,不包含该方法。

  2.2.2 lexical-structure based

  引用原著中对lexical的定义:

A lexical scope is a segment of source code. Lexical-structure based pointcuts select join points occurring inside the lexical scope of specified classes, aspects and methods

本质就是一段作用域,类,方法,代码块。

基于lexical scope,它有两种类型的non kinded pointcut

within(classSignature):selects any join point within the body of the specified classes and aspects,as well as any nested classes

这段话就是类作用域,而classSignature指类签名。通常用法是排除aspect类,例如!within(*..aspect.TestAspect),排除aspect包下的TestAspect。

Withincode(ConstructorSignature) or withincode(MethodSignature)Selects any join point inside a lexical structure of a constructor or a method including any local classes in them

这段话就是方法作用域或构造器作用域。

示例:

within(Account):Account类的作用域。

within(Account+):Account类作用域,和它所有子类的作用域。

withinCode(* Account.debit(..)):Account类下的debt方法作用域。

2.2.3   execution object

引用原著中的定义:

  These pointcuts select join points based on the types of the objects at execution time. The pointcut select join points that match either the type of this. which is the current execution object, or the target object, which is the object on which the method is being called

基于运行方法的this关键字和方法调用者的类型决定。

  基于execution object,它有两种类型this()和target()

This():takes the form this(Type or ObjectIdentifier),It selects join point that have a this object associated with them that is of the specified type or the specified ObjectIdentifier’s type

满足This关键字指向的对象类型与this(obj)中的obj类型相等的条件。

  Target():similar to this() pointcut but uses the target of the join point instead of this,It takes the target(<Type or ObjectIdentifier>) form,the target() pointcut is normally used with a method-call join point。And the target object is the one on which the method is invoked

满足方法调用者的对象类型与target(obj)中的obj类型相等的条件。它们通常是同一概念,只有当方法被代理或者通过反射API调用时才会不同。

  1. this(obj):
  • 收集this关键字指向的对象,当this关键字不存在时,返回null。例如在静态代码块,静态方法中,它返回null。

  2. target(obj):

  • 当为method join point时,若是方法调用(call),target收集方法调用者,例如user.setName(),此时target为user对象。若是方法执行(execution),它收集的是this关键字,与this(obj)相同。
  • 当为get & set join point时,它收集的对象的实例,与this(obj)收集的上下文信息相同

在使用this,target时,需要注意以下两点:

  • 它们都不适用于static方法
  • Obj参数必须是具体的对象类型,不能存在适配符(例如*号)

  2.2.4  argument

引用原著中的定义:

These pointcuts select join points based on the argument object’s runtime type of a join point

根据kinded pointcut上下文中的参数类型,参数数量,参数顺序。它有以下三种情况

For method and constructor join points,the argument objects are the method and constructor arguments

对于方法,构造器的join point,此时参数为方法,构造器的参数。

For exception-handler join points,the argument object is the handled exception.

对于异常类型的join point,此时参数为catch()中的参数。

For field write access join points,the argument object is the new value to be set

对于字段写操作类型的join point,此时参数为字段的新值。

  2.2.5   annotation-based

引用原著的定义:

  AspectJ allows selection based on annotations carried by types,methods,and fields like the execution object and argument pointcuts,annotation-based pointcuts come in two form:selection based on matching annotation types and collection of the matching annotation

  它是基于其他Non-kinded pointcut,其他non-kinded pointcut收集的是对象的类型,而annotation-based收集的这些对象上的注解类型。例如this(Account)收集的是方法运行时,this关键字指向Account对象。@this(Test)的含义是指this关键字指向对象上有Test注解。它与其他non-kinded 基本都是对应的。

@this对应this, @target对应target,@args对应args, @within对应within,

@withincode对应withincode。

@annotation是直接收集kinded pointcut上的注解。

2.2.6   conditional Check

引用原著中的定义

This pointcut selects join points based on some conditional check at the join point. It takes the form of if(Boolean expression)

  它是基于收集的上下文对象,同样它也是基于其他non-kinded pointcut。例如this(Account),它收集Account对象的实例,此时可以使用if(account.getAge() > 18),含义是当前实例的age属性大于18。前提是建立在收集的Account对象实例上。

至此本篇内容结束,个人认为这是最复杂的一部分,但是它也是最基础,是必备的一部分。

posted @ 2021-04-27 14:08  蜗牛旅行1899  阅读(631)  评论(0编辑  收藏  举报