注解的使用和解析

639 阅读4分钟

眷思量_002_1920X1080.jpg

基础知识

注解 Annotation 是 JDK1.5 引入的一种机制,它使用 @interface 定义,用 @[AnnotaionName] 的格式引用一个注解。

注解可以 传递参数,可作用于 类,变量,方法,参数 等。

元注解

@Target

@Target 标识注解的作用域。具体值是 ElementType 类型的枚举变量。

// 注解的作用域
public enum ElementType { 
    TYPE,            // 作用于类、接口(包括注解类型)和枚举 
    FIELD,           // 作用于变量(包括枚举常量) 
    METHOD,          // 方法 
    PARAMETER,       // 参数 
    CONSTRUCTOR,     // 构造函数 
    LOCAL_VARIABLE,  // 本地变量 
    ANNOTATION_TYPE, // 注解类型
    PACKAGE,         // 包 
    TYPE_PARAMETER,  // 类型参数的声明
    TYPE_USE,        // 包含了TYPE和TYPE_PARAMETER,用在使用到TYPE的任意处
    MODULE           // 模块(JDK9 新增)
}

@Retention

@Retention 表示注解的生命周期。注解的生命周期有三种,定义在 RetentionPolicy 枚举中,分别是:

  • SOURCE:只用在源码中,不会被编译器识别
  • CLASS:在类中生效,会被编译器记录在 class 文件中,但运行时不会被虚拟机保留
  • RUNTIME:运行时有效,被编译器记录在 class 文件中,并在运行时保留

默认值是 CLASS。一般来说,自定义的注解以及框架中使用的注解都设置为 RUNTIME

@Document

表示该注解将会被包含在 Javadoc 中。

@Inherited

表示子类可以继承父类的注解。

@Repeatable

表示注解可以重复使用。使用重复注解需要定义两个注解。

定义两个注解 Pet、Pets ,其中 Pets 注解表示 Pet 的容器,包含一个 Pet[] 类型的参数。

@Retention(RetentionPolicy.RUNTIME)
public @interface Pets {
    Pet[] value();
}

而在我们真正要使用的重复注解 Pet 中,使用 @Repeatable 元注解,并指定参数类型为 Pet.class

@Documented
@Repeatable(Pets.class)
public @interface Pet {
    String value();
}

然后就可以重复使用 Pet 了。

@Pet("中华田园猫")
@Pet("哈士奇")
public class Coder{
    // ...
}

内置注解

@Override@Deprecated@SuppressWarnings 等都是可以直接使用的内置注解。

自定义一个注解

使用元注解

了解了 元注解 后,我们已经了解了如何定义一个简单的注解。下面我们继续学习如何给注解加上更多的功能。

注解参数

注解可以添加参数,参数的格式为:

[参数类型] [参数名]();

而在使用注解时,必须要为参数指定参数值。

@XXX([参数名] = [参数值])

如果参数名定义为 value 并且只有一个参数,那么使用时,可以直接赋值,不需要声明参数名,就像上面我们定义的 Pet 一样。

默认参数

注解参数可以使用 default 指定默认值,格式如下:

[参数类型] [参数名]() default [默认值];

我们修改 Pet 注解,增加一个参数并指定默认值,

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target({ElementType.TYPE})
public @interface Pet {
    String value();
    String name() default "小可爱";
}

使用时注意 value 必须显式地指明参数名,

@Pet(value = "中华田园猫",name = "花花")
public class Coder{
    // ...
}

总结

  • 添加了默认值可以不指定参数值
  • 注解使用时参数顺序无关紧要
  • 只有一个参数时,如果参数名称为 value,使用时直接写参数值

解析注解

解析注解需要使用 反射

反射与注解

反射在运行时借助反射 API 获取类(接口,注解)的任何内部信息,并且可以直接操作 内部属性方法

上述的所有可获取的信息都要通过一个 Class 对象来操作,获取 Class 对象的方式有以下几种:

  • 通过类的 class 属性
Class aClass = String.class;
  • 通过对象的 getClass() 方法
String str = new String();
Class aClass = str.getClass();
  • 通过 Class.forName() 方法
Class aClass = Class.forName("com.zcat.example.annotation.MapValue");

该方法有一个 ClassNotFoundException

如果是基本数据类型的包装类,可以通过 TYPE 属性获得。

Class aClass = Integer.TYPE;

获得 Class 对象后,可以通过反射得到属性和方法等。与注解相关的方法有:

  • getAnnotations():获得注解列表
  • getDeclaredAnnotation(Class aClass):获得指定类型的注解
  • isAnnotationPresent(Class aClass):是否有指定类型的注解

解析自定义注解

我们模仿 SpringBoot 配置文件注入的注解 @Value,自定义一个注解 @MapValue 用来从 properties 配置文件中读取值注入到新建的对象 Coder 属性。

// MapValue.class
@Retention(RetentionPolicy.RUNTIME)
public @interface MapValue {
    String value();
}

Coder 中定义两个属性,并使用注解标注

Coder 是一个 POJO 类,已省略 getter/settertoString 方法。

// Coder.class
public class Coder {
    @MapValue("coder.name")
    private String name;
    @MapValue("coder.lang")
    private String lang;

    public Coder() {}

    // ...
}

配置文件 application.properties 配置属性值。

# application.properties
coder.name=zcats
coder.lang=Java

最后封装一个方法 getCoder() 获得一个 Coder 对象,隐藏主动的 new 操作。

首先要先读取配置文件的内容,

/** 读取配置文件 */
public static Properties loadConfig() {
    FileInputStream is = new FileInputStream(new File("C:\Users\afangor\Coding\Projects\corecoding\src\main\java\com\zcat\example\annotation\application.properties"));
    Properties config = new Properties();
    config.load(is);
    return config;
}

然后利用反射机制 (getDeclaredFields()) 获取 Coder 的所有属性,遍历并判断属性上是否有 MapValue 注解。如果有,就使用 getAnnotation(Class aClass) 方法获取注解,读取注解的参数值,再到配置对象中获取配置的值,通过反射赋值给 Coder 对象。

// Method:getCoder(),获得 Coder 对象,自动注入配置的属性值
public static Coder getCoder() throws Exception {
    Properties config = loadConfig();
    Coder coder = new Coder();
    Field[] fields = coder.getClass().getDeclaredFields();
    for(Field field : fields){
        boolean exists = field.isAnnotationPresent(MapValue.class);
        if(exists){
            MapValue annotation = field.getAnnotation(MapValue.class);
            field.setAccessible(true);
            field.set(coder,config.get(annotation.value()));
        }
    }
    return coder;
}

需要注意的是,由于 Coder 的属性是 private 的,所以设置属性值前,要先调用 field.setAccessible(true) 允许操作私有属性。