1,注解的概念
1.1 什么是注解
Annotation(注解)就是 Java 提供了一种元程序中的元素关联任何信息和任何元数据(metadata)的途径和方法。Annotation(注解)是一个接口,程序可以通过反射来获取指定程序元素的 Annotation 对象,然后通过 Annotation 对象来获取注解里面的元数据。
Annotation(注解)是 JDK1.5 及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。Annotation 就像修饰符一样被使用,并应用于包,类型,构造方法,方法,成员变量,参数,本地变量的声明中。
1.2 什么是元数据
元数据就是“关于数据的数据”。元数据的功能有很多,比如:Javadoc的注释自动生成文档。元数据可以用来创建文档,跟踪代码的依赖性,执行编译时格式检查,代替已有的配置文件。
根据元数据的作用可以分为以下几类:
1,编写文档:通过代码里标识的元数据生成文档【生成文档doc文档】
2,代码分析:通过代码里标识的元数据对代码进行分析【使用反射】
3,编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查【Override】
元数据标签的存在并不影响程序代码的编译和执行,它只是生成其它的外键或针对在运行代码的描述信息。综上所述:
1,元数据以标签的形式存在于 Java 代码中
2,元数据描述的信息是类型安全的,即元数据内部的字段都是有明确类型的
3,元数据需要编译器之外的工具额外的处理用来生成其它的程序部件
4,元数据可以只存在于 Java 源代码级别,也可以存在于编译之后的Class文件内容
2,注解的分类
2.1 根据注解的参数个数
1. **标记注解**:一个没有成员定义的 Annotation 类型称为标记注解。这种 Annotation 类型仅使用自身的存在与否来为我们提供信息
2. **单值注解**:在标记注解的基础上提供一段数据
3. **完整注解**:可以包括多个数据成员,每个数据成员由名称和值构成
2.2 根据注解使用方法和用途
1. **JDK内置标准注解**:注解的语法比较简单,除了@符号的使用外,基本与Java固有的语法一致,JavaSE中内置三个标准注解,定义在`java.lang`中
2. **元注解**:元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation 类型作说明
3. **自定义注解**:使用@interface 的语法来说明一个注解
3,系统内置标准注解
JavaSE中内置3个标准注解,定义在java.lang
中:
-
@Override
:用于修饰此方法覆盖了父类的方法 -
@Deprecated
:用于修饰已经过时的方法 -
@SuppressWarnnings
:用于通知java编译器禁止特定的编译警告
3.1 @Override
表示限定重写父类方法
@Override 是一个标记注解类型,它被用作标记方法。说明了被标注的方法重载了父类的方法,起到了断言的作用
3.2 @Deprecated
表示标记已过时
@Deprecated 是一个标记注解。
3.3 @SuppressWarnnings
表示抑制编译器警告
SuppressWarnnings
注解的常见参数值得简单说明:
deprecation
:使用了不赞成使用的类或方法时的警告
-
unchecked
:执行了为检查的转换时的警告,例如当使用集合时没有用泛型(Generics)来指定集合保存的类型 -
fallthrough
:当switch程序块直接通往下一种情况而没有 Break 时的警告 -
path
:在类路径,源文件等中有不存在的路径时的警告 -
serial
:当在可序列化的类上缺少 定义时的警告 -
finally
:任何 finally 子句不能正常完成时的警告 -
all
:关于以上所有情况的警告
3.4 @FunctionalInterface
JDK8出现,主要⽤来指示⼀个接⼝是⼀个函数接⼝,以提示编译器编译时检查接⼝是否符合函数接⼝的规范.
4,元注解
元注解的作用就是负责注解其他注解。JavaEE5 定义了4个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明。JavaEE5定义的元注解:
@Target
@Retention
@Documented
@Inherited
这些类型和它们所支持的类在java.lang.annotation
包中可以找到。
4.1 @Target
@Target 说明了 Annotation 所修饰的对象范围:Annotation可被用于packages,types(类,接口,枚举,Annotation类型),类型成员(方法,构造方法,成员变量,枚举值),方法参数和本地变量(如循环变量,catch参数)
作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
取值(ElementType
)有:
1.CONSTRUCTOR
:用于描述构造器
2.FIELD
:用于描述域
3.LOCAL_VARIABLE
:用于描述局部变量
4.METHOD
:用于描述方法
5.PACKAGE
:用于描述包
6.PARAMTER
:用于描述参数
7.TYPE
:用于描述类,接口(包括注解类型)或enum声明
@Target(ElementType.TYPE)
public @interface MyAnno {
String value() default "abc";
}
@Target(ElementType.FIELD)
public @interface MyAnno2 {
}
4.2 @Retention
@Retention 定义了该 Annotation 被保留的时间长短
作用:表示需要在什么级别保存该注解信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)
取值(RetentionPoicy
)有:
SOURCE
:在源文件中有效(即源文件保留)CLASS
:在class文件中有效(即class保留)RUNTIME
:在运行时有效(即运行时保留)
Retention 元注解类型有唯一的value作为成员,它的取值来自java.lang.annotation.RetentionPolicy
的枚举类型值
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
String value();
String m1();
int m2() default 1;
}
4.3 @Documented
@Documented 用于描述其它的 annotation 类型应该被作为被标注的程序成员的公共API,因此可以被例如 javadoc 此类的工具文档化。Documented 是一个标记注解,没有成员
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnno {
String value();
String m1();
int m2() default 1;
}
4.4 @Inherited
@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是继承的。如果一个使用了@Inherited 修饰的 annotation 类型被用于一个class,则这个 annotation 将被用于该class的子类。
注意:@Inherited annotation 类型会被标注过的class的子类所继承。类并不从它所实现的接口继承 annotation,方法并不从它所重载的方法继承annotation
@Inherited
public @interface MyAnno {
String value();
String m1();
int m2() default 1;
}
作用在类上的自定义注解可以被继承下来。作用在接口上自定义注解不能被实现它的类继承下来。类和接口中方法上的自定义注解不能被重写/实现了其它方法的子类继承(就是说,接口因为被子类实现的时候,方法必被实现,所以其方法上的自定义注解必定不能被继承。而子类如果没有重写父类的方法的话,子类就可以继承父类方法上的自定义注解)
4.5 @Repeatable
此注解是JDK8引⼊的,⽤以表明注解在修饰某⼀个程序单元时,被@Repeatable修饰的注解可以多次出现
在成员单元上.⽐如下⾯的@SomeAnno注解就多次修饰了SomeClass这个程序单元.
5,自定义注解
使用@interface 自定义注解时,自动继承了 java.lang.annotation.Annotation
接口,由编译程序自动完成其他细节。在自定义注解时,不能继承其他的注解或接口。@interface 用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的类型,返回值类型就是参数的类型(返回值类型只能是基本类型,Class,String,enum)。可以通过 default 来声明参数的默认值
5.1 定义注解格式
public @interface 注解名 {定义体}
5.2 注解参数的可支持数据类型
1,所有基本数据类型(int,float,Boolean,byte,double,char,long,short)
2,String类型
3,Class类型
4,enum类型
5,Annotation类型
6,以上所有类型的数组
5.3 Annotation 类型里面的参数改怎么设定
1,只能用 public 或默认(default)这两个访问修饰符
2,参数成员只能用基本数据类型,byte,short,char,int,long,float,double,boolean 8种基本数据类型和String,Enum,Class,annotation等数据类型,以及这一些类型的数组
3,如果只有一个参数成员,最好把参数名设为“value”,后加小括号,
5.4 注解元素的默认值
注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本数据类型的注解元素的值不可为null。因此,使用空字符串或0作为默认值是一种常用的做法。这个约束使得处理器很难表现一个元素的存在或缺失的状态,因为每个注解的声明中,所有元素都存在,并且都具有相应的值,为了绕开这个约束,我们只能定义一些特殊的值,例如空字符串或者负数,以表示某个元素不存在。
5.5 解析注解
使⽤注解修饰了程序单元之后,如果没有对其进⾏解析,这些注解不会对程序的运⾏有任何的影响.必须对这些注解进⾏解析让其真正的发挥作⽤.注解的解析其核⼼思路是
- 通过反射找出被注解修饰的程序单元的反射类型表示,⽐如Class,Method,Field
- 依据这些被修饰单元的反射类型表示,通过相关的⽅法获取注解信息.
5.5.1 AnnotatedElement
在JDK中⽤⼀个接⼝类型 AnnotatedElement
来代表被注解修饰的程序单元,class对象,Method对象,Field对象等都实现了此接⼝.此接⼝的作⽤就是⽤来获取被注解修饰的程序单元上的注解.此接⼝提供了如下成员
-
getAnnotation(Class annotationClass)
:返回修饰改程序单元的指定类型的注解,不存在则返回null -
getDeclaredAnnotation(Class annotationClass)
:返回直接修饰程序单元的指定类型的注解,不包含继承过来你的注解,不存在则返回null -
getDeclaredAnnotations
:返回直接修饰在单元上的所有注解 -
isAnnotationPresent(Class annotationClass)
:指定的注解是否修饰在了程序单元上,是就返回true,否则返回false. -
getAnnotationsByType(Class annotationClass)
:返回修饰程序单元的指定类型的多个注解,返回类型是个数组,不存在则返回null,这是jdk8新加的,因为jdk8推出了可重复(Repeatable)注解. -
getDeclaredAnnotationsByType(Class annotationClass)
:返回直接修饰该程序单元的指定类型的多个注解,不存在则返回 null。
⽐如下⾯的代码就是找出修饰在类上的所有注解.
Class<UserInfoEntity> clazz = UserInfoEntity.class;
Annotation[] annos = clazz.getDeclaredAnnotations();
所有的注解都是 java.lang.annotation.Annotation
类型的⼦类.所以上⾯的getDeclaredAnnotations
⽅法的返回值⽤Annotation数组接收.
如果要得到某个⽅法上⾯的所有注解,可以⽤下⾯的代码
Method m = ...;
Annotation[] methodAnnos = m.getDeclaredAnnotations();
如果想得到某⼀个特定的注解,可以采⽤ getDeclaredAnnotation(class)
⽅法来实现,如果没有这
个特定的注解,getDeclaredAnnotation
⽅法就返回null.⽐如下⾯的代码
Method m = null;
Table talbeAnno = m.getDeclaredAnnotation(Table.class);
下⾯的代码就获取到了UserInfoEntity类上修饰的Table注解的schema的值.
Class<UserInfoEntity> clazz = UserInfoEntity.class;
boolean haveTableAnno = clazz.isAnnotationPresent(Table.class);
if(haveTableAnno) {
Table tableAnno = clazz.getDeclaredAnnotation(Table.class);
System.out.println(tableAnno.schema());
}
6,可重复注解
在JDK 8 之前,注解不⽀持重复修饰同⼀个程序单元,所以必须采取技巧性的代码来处理这种需求.其做法是先创建⼀个普通的注解,⽐如下⾯
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
String value() default "";
}
接着再创建⼀个容器注解,其某⼀个成员变量的类型是上⾯注解的数组类型,⽐如:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnoContainer {
MyAnno[] value();
}
之后就把容器注解修饰在某个程序单元上,value成员变量就指定多个想重复出现的注解,⽐如
@MyAnnoContainer({@MyAnno("a"),@MyAnno("b")})
public class UserInfoEntity {
}
上⾯的实现⽅式语义不明确,因为⽬的是想多次出现MyAnno注解,但实际修饰在类上的却是另⼀个注解MyAnnoContainer.
在JDK8之后推出了⼀个@Repeatable元注解,此注解直接添加到MyAnno注解上,然后给@Repeatable元注解的value属性指定容器注解即可.最后直接把MyAnno注解修饰在程序单元上.最后的代码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(value = MyAnnoContainer.class )
public @interface MyAnno {
String value() default "";
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnoContainer {
MyAnno[] value();
}
@MyAnno("a")
@MyAnno("b")
public class UserInfoEntity {
}
在UserInfoEntity上修饰了多个@MyAnno之后就不能修饰容器注解MyAnnoContainer了.
想获取这个可重复的注解可以利⽤JDK先推出的⽅法getAnnotationsByType
⽅法,如果找不到指定的注解就返回⼀个⻓度为0的数组.⽐如下⾯的代码
Class<UserInfoEntity> clazz = UserInfoEntity.class;
MyAnno[] myAnnos = clazz.getAnnotationsByType(MyAnno.class);
for(MyAnno anno:myAnnos) {
System.out.println(anno.value());
}
这种可重复的注解利⽤getDeclaredAnnotation
⽅法是取不到的.返回的是null
7,类型注解
在JDK 8 之前依据@Target设定注解可以修饰的地⽅,主要可以修饰的地⽅有类型,⽅法,字段,参数等.从JDK8开始,注解可以⽤在任何使⽤类型的地⽅,⽐如初始化(new)⼀个对象,使⽤implements表达式时.⽐如下⾯的代码
//实例化对象时
new @Interned MyObject();
//类型转换时
myString = (@NonNull String) str;
//实现语句时
class UnmodifiableList<T> implements
@Readonly List<@Readonly T> { ... }
// 异常声明时.
void monitorTemperature() throws
@Critical TemperatureException { ... }
// 泛型声明时
List<@NotNull String> list
定义⼀个这样的类型注解与定义普通的注解类似,只需要指定 Target 为ElementType.TYPE_PARAMETER
或者 ElementType.TYPE_USE
,或者同时指定这两个 Target。⽐如:
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface MyAnnotation {
}
ElementType.TYPE_PARAMETER
表示这个 Annotation 可以⽤在 Type 的声明式前,⽽ElementType.TYPE_USE
表示这个 Annotation 可以⽤在所有使⽤ Type 的地⽅(如:泛型,类型转换等)
Java 8 通过引⼊ Type Annotation,使得开发者可以在更多的地⽅使⽤ Annotation,从⽽能够更全⾯地对代码进⾏分析以及进⾏更强的类型检查.
想让这些类型其作⽤,还需要这个注解的处理器,以便在编译的时候利⽤这些检查的注解处理器解析这些注解.在https://checkerframework.org/⽹址有⼀个这样的注解解析器的使⽤说明.
8,注解处理器