Java 注解
编译时注解、运行时注解
在定义注解时,在每个注解接口上面需要定义注解的生命周期
:
- @Retention(RetentionPolicy.RUNTIME) 表示
运行时注解
,在运行时可以通过反射获取相关信息。 - @Retention(RetentionPolicy.CLASS) 表示
编译时注解
,会由编译器记录在class 文件
中,但是在运行时不会保留。 - @Retention(RetentionPolicy.SOURCE) 表示注解的信息会
被编译器抛弃
,不会留在class文件中
运行时注解(@Autowired)与编译时注解(@Override、@Deprecated)的区别?
运行时注解保留到运行时
,可在运行时访问。而编译时注解保留到编译时
,运行时无法访问
。- 运行时注解是Java
反射
机制实现的,需要定义注解处理器,所以会对性能产生一点影响。而编译时注解对性能没有任何影响。
注解的底层实现
注解的接口信息
我们可以通过反射获取到注解的相关信息,因为 Class 反射类实现了AnnotatedElement
接口,而AnnotatedElement 接口的getAnnotation方法可以获取到注解的相关信息。即所有的类都会实现这个接口,这个接口有获取注解相关信息的方法。
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
// 静态内部类,保存了注解相关的信息
private static class AnnotationData {
final Map<Class<? extends Annotation>, Annotation> annotations;
final Map<Class<? extends Annotation>, Annotation> declaredAnnotations;
// Value of classRedefinedCount when we created this AnnotationData instance
final int redefinedCount;
AnnotationData(Map<Class<? extends Annotation>, Annotation> annotations,
Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
int redefinedCount) {
this.annotations = annotations;
this.declaredAnnotations = declaredAnnotations;
this.redefinedCount = redefinedCount;
}
}
}
public interface AnnotatedElement {
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return getAnnotation(annotationClass) != null;
}
<T extends Annotation> T getAnnotation(Class<T> annotationClass);
}
通过反射判断注解信息
Class<> aClass = Object.getClass();
aClass.isAnnotationPresent(Retention.class);// 查看是否具体实现了某一个注解
Annotation[] declaredAnnotations = class.getAnnotation();// 这个是获取类上面的注解信息
Field field = aClass.getDeclaredField("author");
field.getAnnotation();// 这个是获取属性上面的注解
Class<?>.getAnnotation() 是获取类上面的注解
Field.getAnnotation() 是获取字段上面的注解
那么是如何通过加上注解,我们就可以获取到注解里面的属性值的呢?
JVM在运行时是通过动态代理来获取注解的值
,会创建一个代理类,这个代理类继承了Proxy类并且实现了我们自定义的注解接口
,会为注解每一个方法,实现一个代理的方法。 在这个代理方面里面会调用super.h.invoke方法,所以是使用的动态代理,代理这个注解接口,使用的是AnnotationInvocationHandler 类的invoke方法进行增强。在这个invoke方法里面可以通过this.memberValues.get获取设置的具体的value,其中 memberValues是一个LinkedHashMap 保存了key 和V。PS:在实例化AnnotationInvocationHandler类的时候会传入memberValues的值。
这也是为什么注解中的属性要
以方法的形式定义
,而且不能是private
的,因为要用到反射和动态代理。
// 定义注解
@Retention(RetentionPolicy.RUNTIME) // 元注解:注解的注解
@interface PersonInformation{
public String name();
public int age();
public int money();
}
//使用注解
@PersonInformation(name="类注解",age=16,money=100)
class UseAnnotation{
@PersonInformation(name="字段注解",age=16,money=100)
private String author;
int x=1;
}
// 测试注解
public class TestAnnotation {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
// 查看生成的代理类信息,会生成一个代理类,来代理这个注解接口 System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
UseAnnotation useAnnotation = new UseAnnotation();
Class<?> aClass = Class.forName("annotation.UseAnnotation");
PersonInformation annotation1 = aClass.getAnnotation(PersonInformation.class);// 注解写在类上
System.out.println("name: "+ annotation1.name()+", age: "+annotation1.age()+", money: "+annotation1.money());
Field field = aClass.getDeclaredField("author");
boolean flag = field.isAnnotationPresent(PersonInformation.class);
if(flag){
// 注解写在属性上面
PersonInformation annotation = field.getAnnotation(PersonInformation.class);// 这个是获取字段上面的注解
System.out.println("name: "+ annotation.name()+", age: "+annotation.age()+", money: "+annotation.money());
}
}
}