基础知识
注解 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/setter
和toString
方法。
// 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)
允许操作私有属性。