注解
# 1、注解概念
# 1.1、注解初识
注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。
简单理解就是:Java 注解(Annotation)用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。
注解为 Java 代码提供元数据。
# 1.2、元数据
元数据(Metadata)是描述数据的数据,它提供了关于数据的信息,包括数据的结构、属性、约束等。它不是数据本身,而是描述数据的特征和属性,使得数据更容易理解、使用和管理。
案例
图片文件a.png
数据的图片内容,我们可以通过图片查看器查看图片,那这个就是图片的数据。图片除了数据之外,还有其他的信息,比如图片大小、格式、创建时间等,这些信息就是描述图片数据的数据,也就是图片a.png
的元数据。
# 1.3、注解的作用
- 生成文档:通过代码里标识的元数据生成javadoc文档。
- 编译检查:通过代码里标识的元数据让编译器在编译期间进行检查验证。
- 编译时动态处理:编译时通过代码里标识的元数据动态处理,例如动态生成代码。
- 运行时动态处理:运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。
# 2、注解分类
- Java自带的标准注解
也称:Java 内置注解。是Java自带的注解,可以直接使用。会随版本不同而有所增加。
- 元注解
元注解是用于定义注解的注解。
- 自定义注解
可以根据自己的需求定义注解,并可用元注解对自定义注解进行注解。
# 3、Java 内置注解
- Java 1.5开始自带的标准注解,包括
@Override
、@Deprecated
和@SuppressWarnings
。 - Java 1.7 后引入
@SafeVarargs
。
# 3.1、@Override
表示当前的方法定义将覆盖父类中的方法。如果重写时,没加这个这个注解 java 编译器会告警,不是报错。
案例
class A{
public void test() {
}
}
class B extends A{
/**
* 重载父类的test方法
*/
@Override
public void test() {
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override 源码(等看完元注解就能看懂了,可以略过先)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
2
3
4
# 3.2、@Deprecated
表示代码被弃用,如果使用了被@Deprecated注解的代码则编译器将发出警告,但仍然可用,只是不建议用。
案例
/**
* 被弃用的方法
*/
@Deprecated
public void oldMethod() {}
2
3
4
5
@Deprecated 源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
2
3
4
5
# 3.3、@SuppressWarnings
表示关闭编译器警告信息。
案例
/**
* 忽略告警
* @return
*/
@SuppressWarnings("rawtypes")
public List processList() {
List list = new ArrayList();
return list;
}
2
3
4
5
6
7
8
9
@SuppressWarnings 源码
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
2
3
4
5
# 3.4、@SafeVarargs
参数安全类型注解,用于提醒开发者不要用参数做不安全的操作 & 阻止编译器产生 unchecked警告
源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
2
3
4
# 4、元注解
元注解是用于定义注解的注解。 在JDK 1.5中提供了4个标准的元注解:@Target
,@Retention
,@Documented
,@Inherited
, 在JDK 1.8中提供了两个元注解 @Repeatable
和@Native
。
@Target
:用于标明注解使用的范围@Retention
:用于标明注解被保留的阶段@Inherited
:用于标明注解可继承@Documented
:用于标明是否生成javadoc文档@Repeatable
:允许一个注解可以被使用一次或者多次@Native
:表示可以被本地代码引用,常常被代码生成工具使用
元注解是用来定义注解的注解。
接下来分点说明这几个元注解的作用及用法:
# 4.1、@Target
@Target
用于标明注解使用的范围。
说明了注解所修饰的对象范围:注解可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数),在定义注解类时使用了@Target 能够更加清晰的知道它能够被用来修饰哪些对象,它的取值范围定义在ElementType 枚举中。
ElementType枚举
public enum ElementType {
TYPE, // 类、接口、枚举类
FIELD, // 成员变量(包括:枚举常量)
METHOD, // 成员方法
PARAMETER, // 方法参数
CONSTRUCTOR, // 构造方法
LOCAL_VARIABLE, // 局部变量
ANNOTATION_TYPE, // 注解类
PACKAGE, // 可用于修饰:包
TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
案例1:@Override 源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
2
3
4
@Target(ElementType.METHOD)
说明注解@Override
只能用于方法。
案例2:多个范围时
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface MyAnnotation {
//注解内容
}
2
3
4
当@Target
定义需要定义多个范围时,则设置为数组就行。
# 4.2、@Retention
@Retention
用于标明注解被保留的阶段。
在反射中,5.3我们有讲到 类加载 的过程,我们知道加载类的流程为:
类(源码) -> 编译为.class字节码文件(编译期) -> 加载运行(运行期)
@Reteniton
注解用来限定那些被它所注解的注解类在注解到其他类上以后,可被保留到何时,一共有三种策略,定义在RetentionPolicy枚举中。
RetentionPolicy枚举
public enum RetentionPolicy {
SOURCE, // 源文件保留
CLASS, // 编译期保留,默认值
RUNTIME // 运行期保留,可通过反射去获取注解信息
}
2
3
4
5
案例1:还是看 @Override
源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
2
3
4
@Retention(RetentionPolicy.SOURCE)
表示只在源码中存在,编译后就不存在了。
案例2
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
//注解内容
}
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
表示注解保留到运行期,可通过反射获取注解信息。
# @Reteniton 保留策略对比
- RetentionPolicy.SOURCE
- 注解仅存在于源代码中,编译后不会包含注解信息。
- 该注解在编译时期只起到辅助作用,不会影响生成的字节码文件。
- 在运行时无法通过反射获取这类注解。
应用场景:比如一些在开发过程中起到辅助作用的注解,不需要在运行时获取和处理。
- RetentionPolicy.CLASS
- @Retention的默认值。
- 注解会保留在编译后的类文件中(字节码文件),但在运行时无法通过反射获取注解信息。
- 类加载器可以读取到这类注解(只要能够加载到类文件)。
应用场景:比如一些框架或工具在运行时期不需要直接操作注解信息,而是通过字节码文件进行处理。
- RetentionPolicy.RUNTIME
- 注解会保留在编译后的字节码文件中,并可以在运行时通过反射获取注解的信息。
- 可以使用Java的反射机制,在运行时获取注解的值,并根据注解信息进行相应的操作。
- 这种保留策略通常用于自定义注解,需要在运行时动态读取和处理注解信息的场景。
应用场景:比如自定义的注解需要在运行时通过反射获取注解的信息,并进行一些动态的处理。例如,Spring框架中的@Autowired注解就是使用了RUNTIME保留策略。
# 4.3、@Inherited
@Inherited
用于标明注解可继承
是一个标记注解阐述了某个被标注的类型是被继承的。使用了@Inherited修饰的注解类型被用于一个class时该class的子类也有了该注解。
验证
- 定义注解
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface TestInheritedAnnotation {
String [] values();
int number();
}
2
3
4
5
6
7
- 使用注解
@TestInheritedAnnotation(values = {"value"}, number = 10)
public class Person {
}
class Student extends Person{
@Test
public void test(){
Class clazz = Student.class;
Annotation[] annotations = clazz.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation.toString());
}
}
}
/**
* 输出结果
* @TestInheritedAnnotation(values=[value], number=10)
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Student
类没有显示地被注解@TestInheritedAnnotation
,但是它的父类Person
被注解,而且@TestInheritedAnnotation
被@Inherited
注解,因此Student
类自动有了该注解。
# 4.4、@Documented
@Documented
用于标明是否生成javadoc文档
用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如 javadoc此类的工具文档化。是一个标记注解,没有成员。
- 定义注解
@Documented
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface TestDocAnnotation {
public String value() default "default";
}
2
3
4
5
6
- 使用注解
@TestDocAnnotation("myMethodDoc")
public void testDoc() {
}
2
3
4
有了解过swagger的理解起来比较容易,就是标注要不要生成文档。
# 4.5、@Repeatable
@Repeatable
允许一个注解可以被使用一次或者多次
JDK8之前也有重复使用注解的解决方案,但可读性不是很好。直接看JDK8重复注解的做法:
@Repeatable(Authorities.class)
public @interface Authority {
String role();
}
public @interface Authorities {
Authority[] value();
}
public class RepeatAnnotationUseNewVersion {
@Authority(role="Admin")
@Authority(role="Manager")
public void doSomeThing(){ }
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 4.6、@Native
@Native
表示可以被本地代码引用,常常被代码生成工具使用
使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可。
# 5、自定义注解
# 5.1、定义注解
定义注解相对来说比较简单,语法:
元注解
public @interface 注解名 {
//注解成员,注解成员可以没有
类型 成员名() default 默认值;
}
2
3
4
5
举了四个例子,这四个注解分别是放在 类(接口、枚举类上)、构造函数、方法级别、成员属性上的
@Documented //定义可以被文档工具文档化
@Retention(RetentionPolicy.RUNTIME)//声明周期为runtime,运行时可以通过反射拿到
@Target(ElementType.TYPE)//注解修饰范围为类、接口、枚举
public @interface ClassAnnotation {
public String name() default "defaultService";
public String version() default "1.1.0";
}
2
3
4
5
6
7
@Documented
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
public @interface ConstructorAnnotatin {
String constructorName() default "";
String remark() default "构造器";
}
2
3
4
5
6
7
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldAnnotation {
public String name() default "defaultName";
public String value() default "defaultValue";
}
2
3
4
5
6
7
8
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MethodAnnotation {
public String name() default "defaultName";
public MethodTypeEnum type() default MethodTypeEnum.TYPE1;
}
public enum MethodTypeEnum {
TYPE1,TYPE2
}
2
3
4
5
6
7
8
9
10
11
# 5.2、使用注解
因为我们在注解中声明了属性,所以在使用注解的时候必须要指明属性值 ,多个属性之间没有顺序,多个属性之间通过逗号分隔。
@ClassAnnotation(name = "personBean", version = "1.2.1")
public class Person {
// 告诉大家是可以用的,但是影响我测试,我就又注释掉了.
// @ConstructorAnnotatin(constructorName="Person()")
// public Person(String description) {
// this.description = description;
// }
@FieldAnnotation(name = "description", value = "This is my personal annotation")
private String description;
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@MethodAnnotation(name = "sayHello", type = MethodTypeEnum.TYPE2)
public void sayHello() {
System.out.println("Hello Annotation!");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 5.3、反射提取注解
在反射中,我们知道可以通过反射的方式获取类相关的所有信息,包括方法、属性等。那同理我们也可以通过反射获取类中的方法、属性上的注解。
可以通过反射获取注解,从而根据注解内容再做操作。
提取注解
public class TestClassAnnotation {
private static Person person = new Person();
public static void main(String[] args) {
Class<?> clazz = person.getClass();
//因为注解是作用于类上面的,所以可以通过isAnnotationPresent来判断是否是一个具有指定注解的类
if (clazz.isAnnotationPresent(ClassAnnotation.class)) {
System.out.println("This is a class with annotation ClassAnnotation!");
//通过getAnnotation可以获取注解对象
ClassAnnotation annotation = clazz.getAnnotation(ClassAnnotation.class);
if (null != annotation) {
System.out.println("BeanName = " + annotation.name());
System.out.println("BeanVersion = " + annotation.version());
} else {
System.out.println("the annotation that we get is null");
}
} else {
System.out.println("This is not the class that with ClassAnnotation");
}
}
}
/**
* 输出结果
* This is a class with annotation ClassAnnotation!
* BeanName = personBean
* BeanVersion = 1.2.1
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
注解就是数据的元数据,可以通过反射获取,前提时@Retention不能是RetentionPolicy.SOURCE。
反射获取注解常用案例
public class AnnotationTest {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("com.nzc.my_annotation.shang.Person");
System.out.println("==============类注解解析==============");
printClassAnno(clazz);
System.out.println("==============成员变量注解解析==============");
printFieldAnno(clazz);
System.out.println("==============成员方法注解解析==============");
printMethodAnno(clazz);
System.out.println("==============构造器注解解析==============");
printConstructorAnno(clazz);
}
/**
* 打印类的注解
*/
private static void printClassAnno(Class<?> clazz) throws ClassNotFoundException {
//判断是否有AuthorAnnotatin注解
if(clazz.isAnnotationPresent(ClassAnnotation.class)) {
//获取AuthorAnnotatin类型的注解
ClassAnnotation annotation = clazz.getAnnotation(ClassAnnotation.class);
System.out.println(annotation.name()+"\t"+annotation.version());
}
}
/**
* 打印成员变量的注解
*/
private static void printFieldAnno(Class<?> clazz) throws ClassNotFoundException {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if(field.isAnnotationPresent(FieldAnnotation.class)) {
FieldAnnotation annotation = field.getAnnotation(FieldAnnotation.class);
System.out.println(annotation.name()+"\t"+annotation.value());
}
}
}
/**
* 打印成员变量的注解
*/
private static void printMethodAnno(Class<?> clazz) throws ClassNotFoundException {
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if(method.isAnnotationPresent(MethodAnnotation.class)) {
MethodAnnotation annotation = method.getAnnotation(MethodAnnotation.class);
System.out.println(annotation.name()+"\t"+annotation.type());
}
}
}
/**
* 打印成员变量的注解
*/
private static void printConstructorAnno(Class<?> clazz) throws ClassNotFoundException {
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
if(constructor.isAnnotationPresent(ConstructorAnnotatin.class)) {
ConstructorAnnotatin annotation = constructor.getAnnotation(ConstructorAnnotatin.class);
System.out.println(annotation.constructorName()+"\t"+annotation.remark());
}
}
System.out.println("无");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72