注解是jdk1.5引入的,本质上是继承了Annotation接口的子接口,主要用于标记信息
一、为什么能在运行时获取到注解的信息?
1)程序启动,加载类信息,生成字节码文件,类的注解信息也在字节码文件
2)当第一次通过反射获取注解时,程序会解析字节码中的注解,通过jdk动态代理创建注解的代理对象,并将注解的数据交给代理对象,这个注解代理对象会被类的Class对象记录。每一个Class对象内部都会维护一个注解表,用于缓存类的注解对象。
注解代理对象内部有一个代理执行器AnnotationInvocationHandler,简单看一下内部:
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
// 记录注解的类型
private final Class<? extends Annotation> type;
// 记录注解的属性、对应值
private final Map<String, Object> memberValues;
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
// ... 省略
this.type = type;
this.memberValues = memberValues;
}
}
可以看到代理执行器记录了 注解的类型、各个属性及值
在调用注解对象的方法时,会被执行器的invoke方法拦截
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
public Object invoke(Object proxy, Method method, Object[] args) {
// 获取执行注解的哪个方法
String member = method.getName();
// ... 省略的一些调用Annotation接口中的方法
// 通过注解的方法找到对应的值
Object result = memberValues.get(member);
// ...
return result;
}
}
通过要执行的注解方法,可以从memberValues中找到对应的属性值。
3)以后再获取这个注解的信息就可以直接从类的Class对象的注解表中查询出注解代理对象。
二、注解简单使用
import java.lang.annotation.*;
public class Main {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Component {
String value() ;
}
@Component("stu")
class Student { }
public static void main(String[] args) {
// 第一次获取注解对象,会从字节码文件中解析出注解信息,创建代理对象,记录在Student的Class对象的注解表中
Component c1 = Student.class.getAnnotation(Component.class);
// 第二次获取,直接从Student的Class对象的注解表中获取
Component c2 = Student.class.getAnnotation(Component.class);
// c1与c2本质上是同一个对象,结果为true
System.out.println(c1 == c2);
// 这里调用注解代理对象的value方法,会被AnnotationInvocationHandler的invoke方法拦截,然后从成员memberValues中找到对应的属性值
System.out.println(c1.value());
}
}