传统语法(aspect类)--->注解语法(@Aspect)之间的兼容性,以及映射关系。在实际应用场景中,传统语法基本不使用。
1、aspect
1.1 语法
传统语法:
【access specification】 【abstract】 aspect <AspectName> 【extends class-or-aspect-name】 【implements interface-list】 【<association-specifier> Pointcut】| 【pertypewithin(TypePattern){ // aspect body }
注解语法:
@Aspect(["<perthis | pertarget | percflow | percflowbelow>(Pointcut) | [pertypewithin(TypePattern)]"]) [access specificiation] [abstract] [static] class <AspectName> [extends class-or-aspect-name] [implements interface-list]{ // aspect body }
二者的映射:
- Aspect传统语法的修饰符映射为Java的修饰符,此时不支持Privileged修饰符。
- Aspect关键字映射为@Aspect注解
- Aspect-name映射为Java的类名
- Extends | implements:没有变化,但是规则上需要Aspect传统语法的约束。
- association specifier: 映射为@Aspect注解的value属性
- pertypewithin:映射为@Aspect注解的value属性,与association specifier只能出现一种。
注意事项:
- 有@Aspect注解的Java类,被视为Aspect,此时Java类不能是接口,枚举类,注解类等。
- Java类必须有无参构造器,一般也是通过系统去调用,不要尝试自己去new
- Aspect只能继承抽象的Aspect,与Java的继承规则不同
- Aspect类上不能拥有泛型参数,除非是抽象的Aspect
1.2 方法
传统语法:
XXAspect.aspectOf, XXAspect.hasAspect()
注解语法:
Aspects.aspectOf(XXAspect.class),Aspects.hasAspect().
1.3 优先级
传统语法:
declare precedence : aspect1, aspect2..,可以有多条语句
注解语法:
@DeclarePredence(“aspect1, aspect2”),且只能有单个注解。
2、pointcut
2.1 abstract
传统语法:
[access specifier] abstract pointcut <pointcutName>(args)
注解语法:
@Pointcut
[access specifier] abstract void <methodName >(args)
二者的映射
- pointcut关键字转换为@Pointcut注解,它是标注在方法上的
- pointcut的名称转换为Java方法的名称。
注意事项:Java方法的修饰符不能是private
2.2 concrete
传统语法:
[access specifier] pointcut <pointcutName> : (kinded pointcut) && (non-kinded pointcut)
注解语法:
@Pointcut(“<pointcut-definition>”)
[access specifier] pointcut <methodName>(args){}
在abstract的基础上,还包含:
- Kinded pointcut映射为@Pointcut的value属性,即<pointcut-definition>部分
- Non-kinded pointcut收集的上下文映射为方法的参数,顺序由@Pointcut的argNames属性指定。
@Pointcut(value = "execution(* ch6.Account(..)) && this(account) && args(amount)", argNames = "account,amount") public void test(Account account, String amount) { }
注意事项:在编写类签名时,需要使用全类名, import语法无用。
2.3 If
If有三种形式,是if(true),if(false),if(),
if(true)等价于没有。
if(false)等价于否定所有的pointcut。
If()形式下,方法的返回值类型为布尔,它类似于Java语法中if(expression),方法的修饰符必须是public static。
3、advice
3.1 common
3.1.1 注意事项
无论是注解方式,传统方式,都需要遵循advice自身的规则,
- advice自身的约束,例如before, after没有返回值。不能手动调用,由系统自动调用等。
- Advice映射的方法虽然是Java的方法,在选择Join point时,需要将其视为Advice方法对待。例如选择@Before标注的方法时,使用adviceexecution(),而不是call 或 execution。
3.1.2 上下文
传统语法:使用this(), target()收集上下文并传递。
注解语法:相同,需要指定参数顺序。
3.1.3 特殊关键字
传统语法:直接在advice中可以使用thisJoinPoint, thisJoinPointStaticPart。
注解语法:使用thisJoinPoint需添加JoinPoint参数,使用thisJoinPointStaticPart需添加JoinPoint.staticPart参数,使用thisEnclosingJoinPointStaticPart时,添加JoinPoint.EnclosingStaticPart参数。
3.2 before
传统语法:
before({arguments}) :pointcut({arguments}) { // advice body }
注解语法:
@Before(“<pointcut>”) public void <advice-name> ({arguments})
二者的映射关系为:
- before映射为@Before注解
- pointcut映射为@Before注解的value属性
- arguments代表收集到的上下文信息,传统方式下,上下文信息首先收集到pointcut,之后映射为before()中的参数。注解方式下,上下文信息直接映射为方法的参数。
- 由于before advice没有返回值,映射为void。
注意事项: 必须使用public,void修饰。
3.3 after
After有三种类型,after finally, after returning, after throwing。
After finally与before类型的唯一区别是前者使用@After注解,后者使用@Before注解。
After returning与before类型的advice区别有两点,
- 注解不同,前者使用@AfterReturning注解,后者使用@Before注解
- After returning可以收集方法的返回值,若方法的返回值类型为特定类型时,相当于添加了返回值类型的条件,只会选择返回值类型匹配的方法。具体的方式为设置注解的returning属性,它的值为方法的返回值,不指定类型的情况下,默认为Object。
After throwing与After returning的区别有以下两点
- 注解不同,前者使用@AfterThrowing,后者使用@AfterReturning。
- After throwing收集的是异常类型,after returning收集的是返回值。此时设置注解的throwing属性,它的值为抛出的异常实例,不指定类型的情况下,默认为Exception
3.4 around
around类型的advice与before, after类型的advice复杂一些,主要体现在两个方面。
第一个方面proceed关键字的使用。
第二个方面上下文收集时,需要收集的信息比before和after复杂。
对于before, after类型的advice,它们无法使用proceed关键字,该关键字代表调用原始的方法,在传统方式下,可以直接在advice body中调用proceed。在注解方式下,需要在方法上添加ProceedingJoinPoint参数,通过该对象的实例调用proceed方法。
@Around(value ="noArgPointcut()") public Object testAround(ProceedingJoinPoint pjp) { // 无参方式调用 pjp.proceed(); // 有参数方式调用, 参数为Object[]数组 Object proceed = pjp.proceed(new Object[5]); return proceed; }
在收集上下文时,
- 当为execution时,使用this收集当前方法中this关键字指向的对象
- 当为call时,使用target收集当前方法的调用者
- 默认情况下,join point中方法的参数会被收集为Object[],并作为around方法的参数。
4、ITD
4.1 类层次结构
传统语法:
declare parents : (TypePattern) implements (interfaceList) declare parents : (TypePattern) extends (Class or InterfaceList)
注解语法:
@DeclareMixin(“typePattern”) public XXInterface methodName(){ return XXInterfaceImpl; }
映射关系为:
- 注解方式下无extends类型的对应语法。
- Declare parents关键字映射为DeclareMixin注解或者是DeclareParents注解。
- Type Pattern映射为注解的value属性。
- interfaceList映射为XXInterface。若XXInterface继承了其他Interface,例如A, B, C,可以使用@DeclareMixin的interfaces属性指定接口的范围,例如interfaces属性值为A时,只需要实现XXInterface,A接口。B,C接口被忽略。
示例如下:
/** * @Title: testDeclareParent * @Description:与declare parents : ch5.*Customer implements BeanSupport等价 * 1、ch5.*Customer对应value属性 * 2、BeanSupport对应方法的返回值 * 3、BeanSupport中默认的static aspect Impl代码块(默认接口实现)对应BeanSupportImpl中的默认实现代码块 * 4、declare parents对应@DeclareMixin注解 */ @DeclareMixin(value = "ch5.*Customer") public BeanSupport testDeclareParent() { return new BeanSupportImpl(); }
4.2 error && warning
传统方式下,它的语法结构为:
declare error : <pointcut> : <message> declare warning : <pointcut> : <message>
注解方式下,它的语法结构为:
@DeclareError("pointcut”) static final String varName = message
二者的映射关系为:
- declare error转换为@DeclareError注解
- pointcut转换为注解的value属性
- message转换为变量的值。
注意事项:
- 变量名称可以随意,但是变量的值无法改变,所以需要使用final修饰。
- message的值必须是字面量,不允许是方法的返回值,或者是字符串拼接中存在方法的返回值。
- 变量必须使用static修饰,确保不执行任何方法或构造器的情况下便可以获取变量的值。
4.3 不支持
给类,方法,构造器等添加注解不支持。
给类添加新成员不支持。
忽略必检异常不支持。
5、对比
- 传统方式需要学习Aspect语法,而注解方式下兼容Java语法。在编程习惯方面注解方式更具有优势,但是传统方式是注解方式的基础,所以从学习难度上看,二者是等价的。
- 在传统方式下,在类签名时可以只使用类名,而在注解方式下,类签名中必须使用类全名。Java中的import无法在pointcut中使用
- 若只使用Advice,二者的功能是相同的。需要实现ITD时,传统方式比注解方式的功能更全面。
- 当使用Spring框架时,注解方式的兼容性更强。
Privileged关键字不支持。