本篇介绍Aspect文件
1、结构
aspect的结构为
[privileged] [access specification] [abstract] aspect <AspectName> [extends class-or-aspect-name] [implements interface-list] [<association-specifier> Pointcut] | [pertypewithin(TypePattern){ // aspect body }
privileged关键字,可以访问类的私有字段。
access specification,修饰符, public, protected, private等。
abstract关键字,是否是抽象的aspect。
aspect关键字,类似于Java类的class。
aspect name,aspect的名称,通常以Aspect为后缀,例如XXAspect。
extends class-or-aspect-name,继承,通常只能继承抽象的aspect。
implements interface-list,可以实现接口。
association-specifier pointcut,aspect association。之后再介绍。
pertypewithin(TypePattern),是aspect association中的一种,它的含义与within(TypePattern)类似。
aspect body,与普通的Java方法体相同,但是区别在于可以使用关键字,例如thisJoinPoint,proceed()。
1.1 privileged
默认情况下,aspect无法访问私有的字段和方法。若想访问,需要添加privileged。示例如下:
Main对象
public class Main { // 私有字段 private int id; public static void main(String[] args) { // 创建Main对象 Main main = new Main(); main.method1(); } public void method1() { System.out.println("运行method1方法,私有字段id的值为:" + this.id); } }
Aspect对象
/** * * @File Name: PrivilegeTestAspect.aj * @Description: 创建Aspect * @version 1.0 * @since JDK 1.8 */ privileged public aspect PrivilegeTestAspect { // 创建advice,使用匿名方式 before(Main callee) : call(void Main.method1()) && target(callee){ System.out.println("PrivilegeTestAsepct: before objectId="+ callee.id); } }
1.2 association
默认情况下,aspect是单例的,全局的。以在aspect上定义association可以手动配置。
Aspect有四种类型的aspect association
- Singleton(默认值)
- Per Object
- Per Control flow
- Per type
1.2.1 singleton
结构为:
aspect <AspectName> issingleton(){ // aspect body }
它是默认值,issingleton可以省略。
1.2.2 per object
结构为:
aspect <AspectName> perthis(pointcut) || pertarget(pointcut){ // aspect body }
当使用perthis时,此时join point的上下文必须存在this关键字,通常情况下是方法的execution,字段的读取和写入。
当使用pertarget时,此时join point的上下文必须存在target关键字,通常情况下是方法的call。
无论哪种形式,aspect实例与this或target指向的实例一一对应。
引用原著中的内容:
An aspect is created for each object when the join point matching the pointcut is executed the first time for that object
每次匹配join point成功,都会创建aspect实例
示例如下:
// Account对象,只有debit 和 credit两个方法 public class Account { public void credit() { System.out.println("调用Account对象的credit方法"); } public void debit() { System.out.println("调用Account对象的debit方法"); } } // Aspect, 配置Account两个方法的切面 public aspect AssociationDemoAspect perthis(accountOperationExecution(Account)){ // 实现构造器, 用于跟踪aspect实例的个数 public AssociationDemoAspect() { System.out.println("正在创建AssociationDemoAspect"); } /* * 定义pointcut * Account对象的credit方法或者是debit方法 * 由于使用join point 为execution,所以收集实例时使用this关键字,当为调用时,使用target关键字 */ pointcut accountOperationExecution(Account account) :(execution(* Account.credit(..)) || execution(* Account.debit(..))) && this(account); before(Account account) : accountOperationExecution(account){ System.out.println("JoinPoint: "+ thisJoinPointStaticPart + " aspect:" + this + " object :" +account); } } // Main,测试 public static void testAccount() { // 创建Account1对象 Account account1 = new Account(); // 创建Account2对象 Account account2 = new Account(); // 调用account1对象的credit方法 account1.credit(); // 调用account1对象的debit方法 account1.debit(); // 调用account2对象的credit方法 account2.credit(); // 调用account2对象的debit方法 account2.debit(); }
1.2.3 per control flow
结构为:
aspect <AspectName> percflow(pointcut) || percflowbelow(pointcut){ // aspect body }
使用percflow时,等价于cflow,包含方法内部的所有join point及其自身。
使用percflowbelow时,等价于cflowbelow, cflowbelow包含方法内部的所有join point,不包含该方法。
无论哪种形式, aspect实例与方法调用一一对应。
引用原著中的内容:
Each instance is created just before the execution of the credit and debit methods, because a new control flow matching the pointcut specified starts with their execution
每次调用credit和debit方法,都会创建一个aspect实例。
示例同上
1.2.4 per type
结构为
aspect <AspectName> pertypewithin(TypeSignature){ // aspect body }
它与per object类似,区别在于每一个对象实例对应一个aspect实例, per type是每一种类型对应一个aspect实例。
当使用per type时,可以在Aspect中使用getWithinTypeName获取类名。
引用原著中的内容:
It’s typical to advise the static initializer to initialize the aspect state and use that state in other advice
aspect实例的创建时机在类加载的过程, 对于每种类型来说,它的加载过程只有一次。
2、特殊方法
Aspect有两个静态方法,aspectOf,返回关联的aspect实例,若没有,则会创建新的aspect实例,并返回。
因为Aspect对象不能通过new创建。实例的创建,以及内部方法的调用都是由系统自动完成的,如果需要访问aspect实例,可以调用Aspect.aspectOf方法。
当aspect association的类型为per object时,aspectOf方法的参数为对象实例。若为per type时,参数为类对应的Class。
hasAspect, 检查是否有实例与之关联。没有参数,返回值为布尔类型。
3、优先级
优先级的含义有两层,第一层是Aspect层级,即Aspect X与Aspect Y的优先级比较,第二层级是Advice层级,即在Aspect X中存在advice a, b, c, d等等,它们的优先级。
优先级规则如下:
The aspect with higher precedence executes its before advice on a join point before the aspect with lower precedence
Before类型的advice,高优先级在低优先级之前运行
The aspect with higher precedence executes its after advice on a join point after the aspect with lower precedence
After类型的advice,高优先级在低优先级之后运行
The around advice in the higher precedence aspect encloses the around advice in the lower precedence aspect
Around类型的advice,高优先级在低优先级的外层。类似于方法的调用,越外层的方法越处于外层。与分析递归方法时的结构类似。例如f(n)阶层的递归方法,f(3)在f(2)的外层,f(2)在f(1)的外层,相当于f(3)是最大的盒子,f(2)是中等的盒子放入到f(3)的大盒子中,f(1)是最小的盒子放入到f(2)中。Around advice也是类似的,高优先级的advice对应大盒子,低优先级的advice对应小盒子。
定义优先级语法的格式如下:
declare precedence: aspect1, aspect2, aspect3 ….
其中优先级按照aspect从左到右的顺序,越靠左边,前面的aspect具有越高的优先级。
aspect的名称可以使用通配符*,例如auth*,* ;以auth为前缀的aspect比其他的aspect有更高的优先级。
若aspect链出现重复,循环的情况时,定义顺序失败。例如 aspect1, aspect2, aspect1这种情况是错误的。auth*, aspect1, authAspect1这种情况也是错误。
aspect链中的aspect不能是抽象的。即abstract修饰的aspect。
若定义多条aspect链,彼此出现重复时,编译阶段不会发生错误,但是在运行时会出错。例如declare precedence : aspect1, aspect2; 之后又定义declare precedence aspect2, aspect1。编译时不会出错,当切面中的advice运行时,会抛错。
Advice的优先级
当同类型的多个advice对应同一个join point时,默认情况会根据语法顺序,advice定义越靠前, 优先级越高。
当不同类型的advice对应同一个join point时,before类型的总是在join point之前执行,after类型总是在join point之后执行。around类型的advice,会在调用proceed()之前执行before类型,在调用之后执行after类型。proceed()之前的代码是在before之前执行的,proceed()之后的代码是在after之后执行的。