自定义注解的基本操作

young 596 2021-11-16

编写自定义注解需要了解一定的反射知识

元注解

作用在其他注解上的注解

  • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
  • @Documented - 标记这些注解是否包含在用户文档中。
  • @Target - 标记这个注解应该是哪种 Java 成员。
  • @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
  • @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

其中@Retention@Target注解时必须存在

@Retention

@Retention注解声明了注解的作用时期

  • SOURCE 编译器
  • CLASS 编译之后,记录在class中,不会加载到JVM中
  • RUNTIME 运行期,会加载到JVM中,可以通过反射获取到

@Target

@Target注解声明了注解的作用范围

  • TYPE 类、接口、枚举声明
  • FIELD 字段、枚举常量声明
  • METHOD 方法声明
  • PARAMETER 参数声明
  • CONSTRUCTOR 构造函数声明
  • LOCAL_VARIABLE 局部变量声明
  • ANNOTATION_TYPE 注解声明
  • PACKAGE 包声明

创建自定义注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface TestAnnotation {
}

注解类型要写为@interface

由于我们使用自定义注解,都是需要在运行时获取注解进行使用,故@Retention使用RetentionPolicy.RUNTIME

@Target({ElementType.FIELD})表示该注解只能声明在字段上

获取注解

创建一个测试类,在他的属性上声明注解

public class A  {
    @TestAnnotation
    private String name;
    private Integer age;
}

获取注解需要通过反射获取

TestAnnotation testAnnotation = A.class.getDeclaredField("name").getAnnotation(TestAnnotation.class);

如果变量testAnnotation不为null,表示我们获取到了注解

也可以通过先判断,再获取的方式获取注解

Field nameField = A.class.getDeclaredField("name");
if (nameField.isAnnotationPresent(TestAnnotation.class)) {
    TestAnnotation testAnnotation = nameField.getAnnotation(TestAnnotation.class);
}

给注解添加属性

注解的属性目前只支持几种类型

  • 基本类型
  • String
  • Class
  • 枚举
  • 注解
  • 以上任一类型的数组

非这几种类型会在编译期报错

官方文档说明:

https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.6.1

https://docs.oracle.com/javase/1.5.0/docs/guide/language/annotations.html

属性声明格式为: 类型+无参方法

方法名即为属性名

现在我们给我们的自定义注解添加一个名为value的属性

@Retention(value = RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface TestAnnotation {
    String value();
}

此时会发现,我们的测试类中,注解下方出现错误红线,提示是value属性确实必填的值

如果希望value属性可以为空,可以为自定义注解的属性加上默认值,通过default关键字来实现

@Retention(value = RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface TestAnnotation {
    String value() default "";
}

加上default之后,测试类的错误就消失了,由此可见,注解的属性,必须有值,可以通过default关键字来进行默认值的处理

现在,我们可以在我们的自定义注解上,给属性value赋值了

public class A  {
    @TestAnnotation(value = "beijing")
    private String name;
    private Integer age;
    
    // getter、setter、toString...
}

获取自定义注解属性

既然我们增加相关的属性,那么肯定的希望获取属性中的值的,那么我们可以在获取属性之后,调用对应的方法,即可获取到对应的属性值

Field nameField = A.class.getDeclaredField("name");
if (nameField.isAnnotationPresent(TestAnnotation.class)) {
    TestAnnotation testAnnotation = nameField.getAnnotation(TestAnnotation.class);
    String value = testAnnotation.value();
    System.out.println(value); // beijing
}

比如我们想用注解的属性值替换实例的值

Class<A> clazz = A.class;
A a = clazz.newInstance();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
    if (field.isAnnotationPresent(TestAnnotation.class)) {
        TestAnnotation testAnnotation = field.getAnnotation(TestAnnotation.class);
        field.setAccessible(true);
        field.set(a, testAnnotation.value());
    }
}
System.out.println(a); // A(name=beijing, age=null)

是不是就有点类似于Spring的@Value操作

至此基本的自定义注解就开发完了