简介
Java注解(Annotation)又称 Java 标注,是JDK1.5 引入的一种注释机制。
和 Javadoc(注释)不同,Java 注解可以通过反射获取标注内容。在编译期生成类文件时,标注可以被嵌入到字节码中。 JVM 可以保留注释内容,在运行时可以获取到注释内容。当然它也支持自定义Java注释。
理解 Annotation 的关键,是理解 Annotation 的语法和用法;理解 Annotation 的语法和用法之后,再看 Annotation 的框架图,可能有更深刻的体会。
注解的分类
大致分为三类:JDK 内置注解、自定义注解、第三方框架提供的注解。
使用的位置
实际开发中,注解常常出现在类、方法、成员变量、形参位置。当然还有其他位置,这里不阐述。
级别
注解和类、接口、枚举是同一级别的。
注解的作用
如果说注释是写给人看的,那么注解就是写给程序看的。它就像一个标签,贴在一个类、方法或字段上。
它的目的是为当前读取该注解的程序提供判断依据。
- 比如程序只要读到加了@Test 注解的方法,就知道该方法是待测试方法
- 比如@Before注解,程序看到这个注解就知道该方法要放在@Test方法之前执行
JDK内置注解
Java 定义了一套注解,共有7个,3个在java.lang
中,剩下4个在java.lang.annotation
中。
- @Override 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
- @Deprecated 不推荐使用,标记过时方法。如果使用该方法,会报编译警告。
- @SuppressWarnings 警告镇压,指示编译器去忽略注解中声明的警告。
元注解
元注解的作用就是负责注解其他的注解,Java定义了4个标准的meta-annotation类型,它们被用来提供对其他annotation类型作说明。
- @Retention 标识这个注解怎么保存,是只在代码(SOURCES)中,还是编入类文件(CLASS)中,或者是在运行时(RUNTIME)可以通过反射访问。
- @Documented 标记这些注解是否包含在用户文档中。
- @Target 标记这个注解应该是一名Java成员。
- @Inherited 标记这个注解是继承于该注解类(替代注解并没有继承于任何子类)
从Java1.7开始,额外添加了3个注解:
- @SafeVarargs Java1.7开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
- @FunctionalInterface Java1.8开始支持,标识一个匿名函数或函数式接口。
- @Repeatable Java1.8开始支持,标识某注解可以在同一个声明上使用多次。
通过4个元注解我们可以自定义一个注解:
public class Test01{
}
// 自定义一个注解
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
@Inherited
@interface MyAnnotation{}
- @Target 表示我们的注解可以用在哪些地方
- @Retention 表示注解的生命周期,RUNTIME > CLASS > SOURCES
- @Retention(RetentionPolicy.SOURCE) 注解仅存在于源码中,在class字节码文件中不包含;
- @Retention(RetentionPolicy.CLASS) 默认的保留策略,注解会在class字节码文件中存在,它在磁盘内,而不是内存中,虚拟机将字节码文件加载进内存后注解会消失,所以运行时无法获得;
- @Retention(RetentionPolicy.RUNTIME) 注解会在class字节码文件中存在,在运行时可以通过反射获取到
- @Documented 表示是否将我们的注解生成在JAVAdoc中
- @Inherited 表示子类可以继承父类的注解
自定义注解
使用@interface
自定义注解时,自动继承了java.lang.annotation.Annotation
接口。
@interface 用来声明一个注解,格式:@interface注解名{定义内容}
public class Test02{
@MyAnnotation(name = "hory", schools = {"QH","BD"}) // 如果参数没有默认值,就必须传入值
public void test(){}
}
// 自定义一个注解
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation{
String name(); // name()是注解的参数;String是注解的类型
int age() default 23; // 有默认值的参数,可以不在上面添加参数值
String[] schools();
}
其中属性的数据类型可以为:
- 八大基本类型
- String
- 枚举
- Class
- 注解类型
- 以上类型的一维数组
value属性
如果注解的属性只有一个,且名字为value(),那么在使用该注解时,可以不用指定属性名,因为默认就是给value赋值。
但是注解的属性如果有多个,无论名字是否为value(),都必须写明属性名及其值的对应关系。
数组属性
如果数组的元素只有一个,可以省略{}
注解的本质
首先说结论:
-
注解的本质是一个接口。
-
使用
@interface
自定义注解时,自动继承了java.lang.annotation.Annotation
接口。
为了验证这个结论,我们先来自定义一个注解,然后编译成字节码文件,再然后将字节码文件进行反编译。
- 定义一个注解
public @interface MyAnnotation {}
- 编译后得到字节码文件 MyAnnotation.class
- 通过XJad工具反编译 MyAnnotation.class 得到
import java.lang.annotation.Annotation;
public interface MyAnnotation extends Annotation {}
可以发现,@interface 变成了 interface,而且自动继承了Annotation。
既然确实是个接口,那么自然可以在里面写方法。
public @interface MyAnnotation {
public abstract String getValue();
}
编译成 class 文件后再反编译:
import java.lang.annotation.Annotation;
public interface MyAnnotation extends Annotation {
public abstract String getValue();
}
虽说注解的本质是接口,但是仍然有很多怪异的地方,比如使用注解时,竟然可以给getValue赋值:
@MyAnnotation(getValue="annotation on class")
public class Student {
public String name;
public void study(){}
}
你见过给方法赋值的操作吗?别闹了,你脑中想到的是给方法传参。
所以在注解里,类似于 String getValue() 这种,被称为“属性”,给属性赋值显然听起来好接受多了。
基于以上差异,以后还是把注解单独归为一类,不要当成接口使用。
注解的三角关系
注解就像一个标签,是贴在程序代码上供另一个程序读取的。
所以想要使用注解,那么必须有三个步骤:① 定义注解;② 在一个类/方法等上面标注刚才定义的注解;③ 在其他程序中读取类/方法等上标注的注解。
如果只定义注解,那么没有什么意义,定义完了标注也没什么意义,只有① ② ③ 全部有的时候,才是一个完整的注解使用过程。
反射操作注解
接下来写一个程序读取注解,读取注解的思路是:
-
直接读取:IO 读取 .java 文件 --> 处理字符串 --> 得到注解信息 (太麻烦,而且注解中如果传了很复杂的值,解析起来很麻烦)
-
反射读取:因为注解一般标识在类、方法、成员变量上,所以 反射得到Class、Method、Field —> 得到注解
所以看一下用反射是怎么操作注解的。。。
- 定义一个注解
- 注意:想要用反射操作注解,就要定义注解的保留策略@Retention(value = RetentionPolicy.RUNTIME)
- 如果没有设置保留策略,则其默认值为 RetentionPolicy.CLASS,会报 java.lang.NullPointerException,用鼠标点击下面的 MyAnnotation.class时,IDE会提示 Annotation ‘MyAnnotation.class’ is not retained for reflective,直译的话就是:注解MyAnnotation并没有为反射保留。
@Target(value={ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name();
int age() default 23;
}
- 将注解表示在 Dog 类及其 sleep()方法上
@MyAnnotation(name = "hory")
public class Dog {
@MyAnnotation(name = "my")
public void sleep(){ }
}
- 反射获取注解信息
import java.lang.reflect.Method;
public class TestAnnotation {
public static void main(String[] args) throws Exception {
// 获取类上的注解
Class<Dog> clazz = Dog.class;
MyAnnotation annotationOnClass = clazz.getAnnotation(MyAnnotation.class);
System.out.println(annotationOnClass.name());
System.out.println(annotationOnClass.age());
// 获取sleep方法s上的注解
Method helloMethod = clazz.getMethod("sleep",null);
MyAnnotation annotationOnMethod = helloMethod.getAnnotation(MyAnnotation.class);
System.out.println(annotationOnMethod.name());
System.out.println(annotationOnMethod.age());
}
}
结果:
hory
23
my
23
总结
- 注解就像标签,是程序判断执行的依据。比如,程序读到@Test就知道这个方法是待测试方法,而@Before的方法要在测试方法之前执行。
- 注解需要三要素:定义、使用、读取并执行。
- 注解分为自定义注解、JDK内置注解和第三方注解(框架)。自定义注解一般要我们自己定义、使用、并写程序读取,而JDK内置注解和第三方注解我们只要使用,定义和读取都交给它们。
- 大多数情况下,三角关系中我们只负责使用注解,无需定义和执行,框架会将注解类和读取注解的程序隐藏起来,除非阅读源码,否则根本看不到。平时看不到定义和读取的过程,光顾着使用注解,久而久之很多人就忘了注解如何起作用了!