注解
什么是注解?
注解(Annotation)是从JDK1.5开始引入的一种特性,与 Class、Interface、Enum 相同层次,可以像创建 Class 一样创建自己的 Annotation
注解(Annotation)是一种代码级别的元数据,可以标记在包、类型、成员变量、方法、参数的声明中
注解有什么用?
注解可以理解为一个标记,打在包、类型、成员变量、方法、参数的声明上的一个标记,在后续适合的时机对该标记进行解析,根据标记的内容执行相应的逻辑处理。
JDK 提供了 @Override、@Deprecated 等注解,这些注解都是用来规范代码,@Override 标记方法是一个重写方法,@Deprecated 标记方法/类是过时的,不推荐使用。
项目中经常会集成 Lombok
组件,该组件提供了 @Data、@Getter、@Setter 等一系列注解,在一个实体类上标记 @Data 注解,就可以不用为成员变量写 Getter&Setter 方法,但却可以使用 Getter&Setter 方法,就是因为在编译过程中,发现 @Data 注解,编译器自动为实体类的成员变量生成了 Getter&Setter 方法。
项目中经常会集成 HibernateValidator
组件用于参数校验,该组件提供了 @NotNull、@NotEmpty 等一系列注解,方法调用时检测到入参前有 @Valid 注解,组件就会对入参的成员变量进行值校验,如果不符合注解提供的规则,就会抛出异常。
元注解
什么是元注解?
元注解就是标记在注解上的注解。JDK 提供了以下几个元注解,在自定义注解时会用到。
- @Target
- @Retention
- @Documented
- @Inherited
- @Repeatable
@Target
声明注解可以修饰的对象范围
- TYPE:类、接口、枚举、注解
- FIELD:成员变量
- METHOD:方法
- PARAMETER:方法参数
- CONSTRUCTOR:构造方法
- LOCAL_VARIABLE:局部变量
- ANNOTATION_TYPE:注解
- PACKAGE:包
- TYPE_PARAMETER:类型参数(即泛型)
- TYPE_USE:任何类型
如果声明为 ANNOTATION_TYPE 类型,说明当前注解是一个元注解,可以看一下 @Target 元注解本身的定义。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.ANNOTATION_TYPE})
public @interface Target {
ElementType[] value();
}
@Retention
可以理解为注解的生命周期,可以分为三个阶段,源代码(.java文件) -> 字节码(.class文件) -> 运行期,有的注解只作用于源代码,编译后就会被丢弃,有的注解可以正常编译到 class 文件,但程序运行时会被虚拟机忽略。
- SOURCE:源代阶段,只出现在源代码中,在编译阶段会被编译器忽略
- CLASS:编译阶段,会被编译进 .class 文件中,类加载过程中会被虚拟机忽略
- RUNTIME:运行期阶段,会被虚拟机加载到 Class 对象中,可以通过反射得到这个注解。
@Documented
一个标记注解,没有成员,是否需要出现在 JavaDoc 文档中
@Inherited
注解是否可以被继承,在父类声明的注解,解析子类时,是否可以获取到该注解。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Anno1 {}
@Anno1
public class Base {}
public class Child extends Base {
public static void main(String[] args) throws Exception {
Annotation[] annotations = Child.class.getAnnotations();
Arrays.stream(annotations).forEach(System.out::println);
}
}
如果父类与子类同时声明了同一个注解,相当于方法重写的意思,在解析子类时,只会解析到子类的注解,而不会解析到父类的注解
也不是所有标记了 @Inherited 的注解就可以被继承,只有标记在 class 上,才可以继承,也就是说标记在接口、成员变量、方法上,通过子类也无法解析到。
@Repeatable
正常一个元素上,相同的注解只能标记一个,如果注解上加了 @Repeatable 元注解,就可以标记多个
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(value = AnnoList.class)
public @interface Anno1 {
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnoList {
Anno1[] value();
}
@Anno1
@Anno1
public class Base {}
可以理解为实际标记的是一个 AnnoList 注解,在反射时,如果发现有多个,就会解析成 AnnoList,如果只有一个,就会解析成 AnnoList,这会直接影响到注解的继承(Inherited)。
自定义注解
注解在声明时,不支持继承其它的接口或者注解。 注解可以声明配置项,通过 default 设置默认值,格式如下。
如果只有一个配置属性,建议使用 value,注解在使用时如果没有设置配置项,默认使用的配置属性就是 value
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Anno1 {
String value() default "";
}
反射解析注解
对于 @Retention 为 RUNTIME 的注解,可以在运行期通过反射的方式得到注解,JDK 的反射模块为注解提供了两个重要的接口:Annotation 及 AnnotatedElement。
所有注解自动实现 Annotation 接口,由编译器自动完成继承细节。
注解标记的元素必须实现 AnnotatedElement 接口,如 Class、Field、Method 等类都实现了 AnnotatedElement 接口,AnnotatedElement 接口提供了获取注解的方法。
//方法一:返回元素上存在的所有注解
Annotation[] getAnnotations()
// 方法二:返回元素上指定类型的注解
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
// 方法三:针对 @Repeatable 注解,支持通过子注解获取
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass)
// 方法四:判断元素上是否存在指定类型的注解
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
return this.getAnnotation(annotationClass) != null;
}
// 以上方法支持注解的继承
// 以下方法为 Declared 版本,只能解析当前元素的注解,不支持注解的继承。
// 方法五:返回元素上存在的所有注解
Annotation[] getDeclaredAnnotations()
// 方法六:返回元素上指定类型的注解
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)
// 方法七:针对 @Repeatable 注解,支持通过子注解获取
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass)