它的运行结果是这样的:
B.foo()
Call A
和你的想法一致吗?对于( 1 )处 b.foo() 的调用应用了面向对象中的覆盖( override ),它是动态的,是在运行时进行解析。而( 2 )处的 callFoo (b) 则是重载( overload ),它是静态的,是在编译时解析的。因此,对于变量 b ,虽然它是 B 的一个实例,但 b 的静态类型(也就是变量声明的类型)是 A ;由于重载方法的选择是静态的,所以 main 中调用的是 callFoo(A a) ,而不是 callFoo(B b) 。
终于说到了类型声明模式。类型声明模式是基于静态类型信息进行匹配的,而不是动态(或者运行时。下面根据几个典型的例子说明类型声明模式的特性。
还是上面的两个类 A 和 B ,现在我们定义一个方面如下:
pointcut callA():
call( * A. * (..));
before():callA(){
System.out.println( " call A " );
}
}
main 函数内容如下:
A b1 = new B();
b1.foo();
B b2 = new B();
b2.foo();
}
运行结果如下:
call A
B.foo()
call A
B.foo()
可以看到,尽管切入点 callA() 声明的类型为 A ,但实际上,切入点 callA() 可以捕获 A 中的方法及其子类中继承于 A 的方法或重载 A 的方法,而声明的静态类型既可以是 A 也可以是其子类。
但如果在 B 中增加一个新的方法:
System.out.println( " B.doAnotherThing " );
}
main 函数改为:
B b2 = new B();
b2.doAnotherThing();
}
输出结果为: B.doAnotherThing ,如果想对 A 的子类 B 中扩展的方法进行通知,可采用的方法是将切入点 callA() 改为 pointcut callA(): call (* A+.*(..)); 。
让我们再来看另一种情景:如果定义一个切入点如下:
before():callB(){
System.out.println( " call B " );
}
main 函数内容如下:
A b = new B();
b.foo();
}
运行结果为: B.foo() 。 b.foo() 没有匹配切入点 callB() 的原因在于, b 的静态类型是 A ,从静态类型的角度来看,这是对 A 的调用,而不是对 B 的调用。在使用 AspectJ 的类型声明时,很容易在这个地方犯错。
好了,如上便是有关类型声明模式的东西,说得有些凌乱,希望对 AspectJ 初学者有些帮助(我本身也是个初学者)。该文参考了《 Eclipse AspectJ 》和《 AspectJ cookbook 》。