简介
本文介绍Java中注解的用法,需要有反射相关的知识内容,不熟悉反射的建议先看反射相关内容后再看本篇文章会更容易理解。
1. 什么是注解?
注解(Annotation),也叫元数据。一种代码级别的说明。简单理解就是注解就是给机器看的注释,那给机器看到这些注释当然就可以再编译时期和运行时期去干些事情。
所以,接下来我们从以下几个步骤来介绍
- java 注释用 // 来表示,那注解怎么写?有什么规范?
- 注解既然是机器看的,那机器怎么看到注解?
2. 定义注解
注解的定义和类的定义有点相似,类的定义用class
关键字,注解使用@interface
来定义,所有的注解都是java.lang.annotation.Annotation
接口的子类,JDK中注解相关的类和接口都定义java.lang.annotation
包中,注解和类一样,也可以定义属性,不过语法上有差异,具体规则如下:
- 访问修饰符必须为public,不写默认为public
- 该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组
- 该元素的名称一般定义为名词,如果注解中只有一个元素,通常把名字起为value
- 属性名称后面的()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法
- default代表默认值,值必须和第2点定义的类型一致
- 如果没有默认值,代表后续使用注解时必须给该类型元素赋值
如下定义一个注解
public @interface MyAnnotation {
//定义一个属性
String value();
//定义属性指定默认值
String name() default "";
}
3. Java 中内置的元注解
java 中的元注解用在注解上边,用来定义注解的使用规则。
3.1. @Target
指定注解使用位置
标注注解使用位置,比如注解可用在类上、方法上等,不指定默认可用在任何地方,源码如下
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
package java.lang.annotation;
/*注解的使用范围*/
public enum ElementType {
/*类、接口、枚举、注解上面*/
TYPE,
/*字段上*/
FIELD,
/*方法上*/
METHOD,
/*方法的参数上*/
PARAMETER,
/*构造函数上*/
CONSTRUCTOR,
/*本地变量上*/
LOCAL_VARIABLE,
/*注解上*/
ANNOTATION_TYPE,
/*包上*/
PACKAGE,
/*类型参数上*/
TYPE_PARAMETER,
/*类型名称上*/
TYPE_USE
}
3.2. @Retention
指定注解生命周期
先来看一下java程序的3个过程
- 源码阶段
- 源码被编译为字节码之后变成class文件
- 字节码被虚拟机加载然后运行
@Retention
的作用就是指定注解会在以上哪些时期存在,对应源码如下
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
public enum RetentionPolicy {
/*注解只保留在源码中,编译为字节码之后就丢失了,也就是class文件中就不存在了*/
SOURCE,
/*注解只保留在源码和字节码中,运行阶段会丢失*/
CLASS,
/*源码、字节码、运行期间都存在*/
RUNTIME
}
3.3. @Inherited
允许子类继承父类上的注解
注解继承只针对class 级别注解有效
示例代码:类UseAnn
可以继承UseAnnParent
上的@MyAnnotation
注解,因为@MyAnnotation
注解上使用@Inherited
标注
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
@MyAnnotation(value = "用在类上")
public class UseAnnParent{
}
public class UseAnn extends UseAnnParent{
}
3.4. @Repeatable
表示注解可以在一个位置重复使用
@Repeatable
使用时需要指定注解容器,如下,定义一个@MyAnnotations
注解,里面有一个MyAnnotation[]
属性,并且@MyAnnotations
生命周期要大于等于@MyAnnotation
生命周期。
示例代码:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
@interface MyAnnotations {
MyAnnotation[] value();
}
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
String value();
}
4.使用注解
前面我们一直在说注解的定义,接下来终于介绍到该怎么获取使用了
4.1. AnnotatedElement
获取注解信息
java.lang.reflect.AnnotatedElement
接口提供的方法可以获取注解信息,常用API如下:
- boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 判断是否有注解
- T getAnnotation(Class annotationClass) 获取指定类型注解
- Annotation[] getAnnotations(); 获取所有注解,包括从父类继承的。
- T[] getAnnotationsByType(Class annotationClass)
- Annotation[] getDeclaredAnnotations(); 返回此元素上的注解,不包括父类上的注解
以下类都实现了java.lang.reflect.AnnotatedElement
接口 ,所以以下类型都可以直接获取到注解信息
- Package:用来表示包的信息
- Class:用来表示类的信息
- Constructor:用来表示构造方法信息
- Field:用来表示类中属性信息
- Method:用来表示方法信息
- Parameter:用来表示方法参数信息
- TypeVariable:用来表示类型变量信息,如:类上定义的泛型类型变量,方法上面定义的泛型类型变量
来个简单的例子
@Test
public void test(){
Class<UseAnn> clazz = UseAnn.class;
for (Annotation annotation: clazz.getAnnotations()){
// 执行逻辑,比如判断该类上有注解,然后获取对应注解信息,进行下一步操作
System.out.println(annotation);
}
}
4.2. Spring 对注解的支持
我们发现子类可以继承父类上的注解,JDK提供的java.lang.reflect.AnnotatedElement
工具也只能获取父类上的注解,就是解决了类的继承关系,那注解上的注解该怎么获取使用?接下来看Spring中对这个问题的解决
-
@AliasFor
注解
这个注解可以和@Inherited
对照理解,@Inherited
是java子类可以继承获取java父类上的注解,@AliasFor
注解是设置注解上的注解属性 -
AnnotatedElementUtils
是Spring 提供的一个工具,用来查找注解,并且可以查找注解上的注解
来个代码例子:
1.先定义一个注解
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation01 {
String value01() default "";
}
2.再定义一个注解,用上边定义的注解MyAnnotation01
标注,使用@AliasFor
注解 对value01
标注,当我们使用注解设置MyAnnotation.value01
属性的同时会设置@MyAnnotation01.value01
的属性。
@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@MyAnnotation01
public @interface MyAnnotation {
String value();
@AliasFor(annotation = MyAnnotation01.class,value = "value01")
String value01();
}
接下来测试
@MyAnnotation(value = "用在类上", value01 = "用在类上02")
public class UseAnn{
}
@Test
public void test(){
for (Annotation annotation: UseAnn.class.getAnnotations()){
System.out.println(annotation);
}
MyAnnotation01 myAnnotation01= AnnotatedElementUtils.getMergedAnnotation(UseAnn.class,MyAnnotation01.class);
System.out.println(myAnnotation01);
}
输出结果
@com.wangjunxin.simple.annotations.MyAnnotation(value=用在类上, value01=用在类上02)
@com.wangjunxin.simple.annotations.MyAnnotation01(value01="用在类上02")
看结果发现我们并没有设置MyAnnotation01属性,但是MyAnnotation01的属性有值, @AliasFor
用法一目了然。
5. 最后
AnnotatedElementUtils
和 java.lang.reflect.AnnotatedElement
工具只能获取到运行时期的注解,我们平常自定义的注解都使用 @Retention(RetentionPolicy.RUNTIME)
,如果想了解其他两个生命周期的,可以看下 mapstruct
框架(用来复制属性的框架)、lombok
(帮值我们自动注入set/get方法)
以上就是本文全部内容啦,自己写代码练习理解会更深。