本篇介绍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之后执行的。