注解

2023/11/11 Java基础

# 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() {
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

@Override 源码(等看完元注解就能看懂了,可以略过先)

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
1
2
3
4

# 3.2、@Deprecated

表示代码被弃用,如果使用了被@Deprecated注解的代码则编译器将发出警告,但仍然可用,只是不建议用。

案例

/**
* 被弃用的方法
*/
@Deprecated
public void oldMethod() {}
1
2
3
4
5

@Deprecated 源码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
1
2
3
4
5

# 3.3、@SuppressWarnings

表示关闭编译器警告信息。

案例

/**
* 忽略告警
* @return
*/
@SuppressWarnings("rawtypes")
public List processList() {
    List list = new ArrayList();
    return list;
}
1
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();
}
1
2
3
4
5

# 3.4、@SafeVarargs

参数安全类型注解,用于提醒开发者不要用参数做不安全的操作 & 阻止编译器产生 unchecked警告

源码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
1
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 新增
 
}
1
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 {
}
1
2
3
4

@Target(ElementType.METHOD) 说明注解@Override只能用于方法。

案例2:多个范围时

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
public @interface MyAnnotation {
//注解内容
}
1
2
3
4

@Target定义需要定义多个范围时,则设置为数组就行。

# 4.2、@Retention

@Retention 用于标明注解被保留的阶段。

在反射中,5.3我们有讲到 类加载 的过程,我们知道加载类的流程为:

类(源码) -> 编译为.class字节码文件(编译期) -> 加载运行(运行期)

@Reteniton注解用来限定那些被它所注解的注解类在注解到其他类上以后,可被保留到何时,一共有三种策略,定义在RetentionPolicy枚举中。

RetentionPolicy枚举

public enum RetentionPolicy {
    SOURCE,    // 源文件保留
    CLASS,     // 编译期保留,默认值
    RUNTIME    // 运行期保留,可通过反射去获取注解信息
}
1
2
3
4
5

案例1:还是看 @Override 源码

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
1
2
3
4

@Retention(RetentionPolicy.SOURCE)表示只在源码中存在,编译后就不存在了。

案例2

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
//注解内容
}
1
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();
}
1
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)
 */
1
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";
}
1
2
3
4
5
6
  • 使用注解
@TestDocAnnotation("myMethodDoc")
public void testDoc() {

}
1
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(){ }
}
1
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 默认值;
}
1
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";
}
1
2
3
4
5
6
7
@Documented
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
public @interface ConstructorAnnotatin {
    String constructorName() default "";
    String remark() default "构造器";
}
1
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";
}
1
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
}
1
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!");
    }
}
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

# 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
 */
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("无");
  }
  
}
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
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

# 6、深入理解注解

# 7、注解实战

参考:Java注解详解和自定义注解实战,用代码讲解 (opens new window)