这篇博客的两个主题:
- spring的AnnotatedElementUtils
- 个人源码阅读方法论分享
为什么要分享AnnotatedElementUtils这个类呢,这个类看起来就是一个工具类,听起来很像apache的StringUtils,CollectionUtils。
原因是,它包含着spring对java注解的另类理解,和运用。
java的是怎样支撑注解的?
Class<TestAnnotation> clazz = TestAnnotation.class;
// 获取类注解
MyClassAnnotation myClassAnnotation = clazz.getAnnotation(MyClassAnnotation.class);
// 获得构造方法注解
Constructor<TestAnnotation> cons = clazz.getConstructor(new Class[] {
});
MyConstructorAnnotation Constructor = cons.getAnnotation(MyConstructorAnnotation.class);
// 获得方法注解
Method method = clazz.getMethod("setId", new Class[] {
String.class });
MyMethodAnnotation myMethodAnnotation = method.getAnnotation(MyMethodAnnotation.class);
// 获得字段注解
Field field = clazz.getDeclaredField("id");
MyFieldAnnotation myFieldAnnotation = field.getAnnotation(MyFieldAnnotation.class);
以及@Inherited,它可以将父类的注解,带到继承体系上的子类中去。
这套注解体系有什么问题?
面向对象语言之所以被冠以“面向对象”这样的名字,是因为它具有多态的能力。有了多态的能力,我们才有了面向接口编程的能力,有了这个能力,依赖反转才有立足点;所有的设计模式才有立足点(工厂模式,装饰器模式,策略模式…)。可以说多态是java这样的强类型,面向对象语言的灵魂。
那么多态这种能力是怎么来的?
父类与接口。弱类型的语言其实天然就支持多态,但强类型的语言则不是。而java在语言层面支持了"父类与接口",体现在java程序可以自动的向上转型,并且可以安全的向下转型。向上,向下转型这两件事,就实现了所谓的“多态”语义。
我们再向问题的本质进一步,看看java是怎么实现上下转型的?
当把class文件加载进内存(方法区)时,方法在真正运行之前就有一个确定的调用版本,且该版本在运行期不可变的一类,将会被解析,符号引用将被替换为实在的内存地址,成为该方法的入口地址。静态方法,私有方法,构造器,父类方法符合这个要求。这类方法也被称为非虚方法。
public class Test {
public void test() {
// 实例和方法都是确定的(Human的静态方法run)
Human.run();
}
}
而虚方法和静态分派则是:
// 实例不确定,方法也不确定。
// 此处唯一能确定的是,方法的重载版本。可见这个方法的版本是无参数的,它确定了执行器在调用run时,
// 一定不会去调用一个带任何参数的版本的run方法。这就是静态分派。
public class Test {
public void test(Human human) {
human.run();
}
}
上面提到了重载,而静态分派就是用以确定重载版本的,下面我要说的是覆写。覆写会导致不同实例的覆写版本,方法体不一样,所以虚拟机只能在运行期通过对象的实际类型来决定调用哪个版本的覆写方法。这被称为动态分派。
public class Test {
public void test() {
System.out.