一、认识注解
1.1、介绍注解
注解(Annotation):从JDK5.0开始,Java增加了对元数据(MetaData)的支持,及注解。
Annotation就是代码中的特殊标记,这些标记可以通过设置在编译时、类加载时、运行时读取来执行相应的处理。通过注解可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。并且代码分析工具、开发工具和部署工具都可以通过这些补充信息进行验证部署。
Annoation可以像修饰符一样使用,可用于修饰包、类、构造器、方法…之前,并且能够设置指定的值保存在Annoation中的属性里,例如name=value。
目的:在JavaSE中,注解的使用目的例如标记过时的功能(@Deprecated), 忽略警告(@SuppressWarnings;在JavaEE/Android开发中注解占据了更重要的角色,例如Spring框架中的AOP通过注解来配置任意方法的切面,使用注解来代替JavaEE旧版中的繁冗代码与XML配置。
未来的开发模式都是基于注解的,例如:Spring2.5以上都是基于注解,JPA,Hibernate3.X,Struts2。
框架=注解+反射+设计模式
1.2、开发中常见注解使用
使用注解前需要在前面加上@符号,可以将其当做一个修饰符来使用。
1、用于生成文档的相关注解
一般使用于注释中,用于在使用javadoc工具来生成文档
类注释中:
@author:标明开发该类模块的作者,多个作者之间可使用,分割。
@version:标明该类模块的版本。
@since:从哪个版本开始增加的,我们常看到源代码注释中会标注。
@see:参考转向,也就是相关主题。
一般用于方法中:
@param:用于在方法注释中,对方法中某参数的说明,如果没有参数就不能写。
@return:用于在方法注释中,对方法返回值的说明,如果方法的返回值类型是void就不能写。
@exception:对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写 其中
2、在编译时进行格式检查(JDK内置的三个基本注解)
一般修饰在方法:
@Override: 限定重写父类方法, 该注解只能用于方法,可以来检查是否为重写的方法。
@Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择。
@SuppressWarnings: 抑制编译器警告。
3、跟踪代码依赖性,实现替代配置文件功能
①Servlet3.0中提供的注解,使得不再需要在web.xml中进行Servlet的部署访问。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YlCwnrSA-1613984978679)(https://gitee.com/changluJava/picture-bed/raw/master/mouse/20210221231848.png)]
通过使用@WebServlet("/login")来代替掉下面的配置文件(减少xml配置)。
②Spring中事务的配置,只用一个注解来替换掉了配置文件
二、自定义注解
2.1、自定义注解说明
对于自定义注解的说明:
定义Annotation时需要使用@interface关键字。
自定义注解自动继承了java.lang.annotation.Annotation接口,包含四个方法equals()、hashCode()、toString()、annotationType()。
Annoation中的成员变量以无参数方法的形式来声明。
其方法名和返回值定义了该成员的名字和类型,称之为配置参数。其类型只能是8种基本数据类型、String类型、Class类型、enum类型、Annotation类型以及上面所有类型的数组。
可以在定义成员变量时指定参数值,可在成员变量后使用default 默认值来进行初始化。
若是Annoation中只有一个参数成员,建议使用参数名为value。
使用说明:若是使用的注解中含有配置参数(即成员变量),那么使用时需要在()中指定参数值(对于有默认值的可以不写因为会自动赋值),例如:@注解(参数=值);若是Annotation中只有一个成员那么我们在使用时可以省略参数=,例如:@注解(值)。
注意:没有成员定义的Annotation称为标记;包含成员变量的Annotation称为元数据。
自定义注解
public @interface MyAnnotation { String value() default "changlu"; } @MyAnnotation(value = "changlu") //或 @MyAnnotation("liner") 或 @MyAnnotation() class Person{ }
@MyAnnotation(value = "changlu")与@MyAnnotation("liner")都相当于对其中的value重新赋值了,由于注解中只有一个成员所以可以省略参数直接填值。
@MyAnnotation() :会默认使用注解成员中的值。
2.2、JDK1.5提供的四个元注解
四个标准的元注解:Retention、Target、Documented、Inherited
@Retention
@Retention:用来指定该Annotation的生命周期。
@Documented @Retention(RetentionPolicy.RUNTIME) //生命周期为运行时 @Target(ElementType.ANNOTATION_TYPE) //只可标注在注解类型上 public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); } //@Retention的成员返回值RetentionPolicy,一个枚举类 public enum RetentionPolicy { SOURCE,//在源文件中有效(仅在源文件中保留),编译器编译时会忽略该注解 CLASS,//仅在class文件中有效(即class保留),当运行Java程序时,JVM不会保留注解。 RUNTIME//:在运行时有效(即运行时保留),当运行 Java 程序时, JVM 会保留注释。程序可以通过反射获取该注释。 }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zmdpvsuI-1613984978684)(https://gitee.com/changluJava/picture-bed/raw/master/mouse/20210222151009.png)]
该注解使用不同RetentionPolicy的枚举实例表示的不同生命周期
@Target
@Target:用于指定被修饰的Annotation能用于修饰哪些程序元素(如方法、类、包、参数…)。
@Documented @Retention(RetentionPolicy.RUNTIME) //生命周期为运行时 @Target(ElementType.ANNOTATION_TYPE)//只可标注在注解类型上 public @interface Target { //是一个数组枚举类 ElementType[] value(); } //@Target的成员返回值是一个注解 public enum ElementType { TYPE,//使用于描述类、接口(包括注解类型)或enum声明 FIELD,//使用于属性(包含注解内容) METHOD,//使用于方法 PARAMETER,//参数 CONSTRUCTOR,//用于描述构造器 LOCAL_VARIABLE,//描述局部变量 ANNOTATION_TYPE,//描述注解类型声明 PACKAGE,//包 TYPE_PARAMETER,//jdk1.8新增,表示该注解能写在类型变量的声明语句中(如:泛型声明) TYPE_USE//jdk1.8新增,表示该注解能写在使用类型的任何语句中 }
@Tatget({ElementType.TYPE_USE}):即可在任何语句中添加注解。
@Documented
@Documented: 用于指定被 修饰的 Annotation 类能够被 javadoc工具提取成文档。默认情况下,javadoc是不包括注解的。
若定义了@Documented的注解务必设置@Retention的值为Runtime(生命周期为运行时)。 @Documented @Retention(RetentionPolicy.RUNTIME) //生命周期是运行时 @Target(ElementType.ANNOTATION_TYPE) //只可标注在注解类型上 public @interface Documented { }
@Inherited
@Inherited:被其修饰的注解具有继承性,若某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解。
实际使用较少。
@Documented @Retention(RetentionPolicy.RUNTIME)//生命周期是运行时 @Target(ElementType.ANNOTATION_TYPE)//只可标注在注解类型上 public @interface Inherited { }
三、利用反射获取注解信息
自定义注解:
@Documented @Target({ElementType.ANNOTATION_TYPE,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME)//运行时 @Inherited public @interface MyAnnotation { String value() default "changlu"; }
通过反射获取到类的注解以及注解值:
@MyAnnotation("clle") class Person{ } class Student extends Person{ } public class Test { public static void main(String[] args) { Class<Person> clazz = Person.class; //获取该类上指定的注解类型 MyAnnotation anno = clazz.getAnnotation(MyAnnotation.class); System.out.println(anno.value());//clle //查看Student是否继承了Person的注解 Annotation[] annotations = Student.class.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation);//@xyz.changlu.MyAnnotation(value=clle) } } }
认识一下AnnotatedElement接口
Class实现了该接口,才会有获取注解的相关方法:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h8XjEarH-1613984978686)(https://gitee.com/changluJava/picture-bed/raw/master/mouse/20210222155559.png)]
isAnnotationPresent():此元素上是否存在指定注解类型,若存在返回true,不存在返回false。
getAnnotation():获取该元素上指定注解类型的实例。
getAnnotations():获取该元素上的所有注解实例。
getAnnotationsByType:获取该元素上指定注解类的所有实例。
Class类的继承图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ZaYFZCA-1613984978690)(https://gitee.com/changluJava/picture-bed/raw/master/mouse/20210222160004.png)]
四、JDK8中注解的新特性
4.1、可重复注解(两种方式)
两种方式实现
什么是可重复注解呢?就是在同一个部分上定义两个或多个相同的注解,其中的值可不一样。
①jdk1.8之前,需要通过再定义一个注解,其中包含我们想要使用的注解数组
@Target({ElementType.ANNOTATION_TYPE,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME)//运行时 @Inherited public @interface MyAnnotation { String value() default "changlu"; } //该注解中我们定义@MyAnnotation数组 @Target({ElementType.ANNOTATION_TYPE,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME)//运行时 @Inherited @interface MyAnnotations{ //定义注解数组 MyAnnotation[] value(); }
@MyAnnotations({@MyAnnotation("changlu"),@MyAnnotation("liner")}) class Person{ }
我们通过定义@MyAnnotations注解的成员为注解数组形式来实现可重复注解。
②jdk1.8新增@Repeatable注解
@Target({ElementType.ANNOTATION_TYPE,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Repeatable(MyAnnotations.class)//注意:这里新增使用@Repeatable()其中值为一个Class类就是下面定义的MyAnnotations public @interface MyAnnotation { String value() default "changlu"; } @Target({ElementType.ANNOTATION_TYPE,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME)//运行时 @Inherited @interface MyAnnotations{ //定义注解数组 MyAnnotation[] value(); }
在@MyAnnotation中使用了@Repeatable(MyAnnotations.class)注解,其中定义了指定一个容器注解。
@MyAnnotation("changlu") @MyAnnotation("liner") class Person{ }
这样我们就能够直接在一个类上定义多个相同注解了,相较于第一种方式需要先声明@MyAnnotations,其中再包裹两个注解。
我们再看一下@Repeatable注解:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Repeatable { Class<? extends Annotation> value();//其中成员的返回值为一个Class类型 }
获取可重复注解的值(两种方式)
获取上面使用@Repeatable注解方法的设置的重复注解:
@MyAnnotation("changlu") @MyAnnotation("liner") class Person{ }
public class AnnotationTest { public static void main(String[] args) { //方式一:通过getAnnotation()获取MyAnnotations容器注解的实例,接着遍历 MyAnnotations annotation = Person.class.getAnnotation(MyAnnotations.class); MyAnnotation[] value = annotation.value(); for (MyAnnotation myAnnotation : value) { System.out.println(myAnnotation.value()); } //changlu //liner } //方式二:通过使用getAnnotationsByType()获取MyAnnotation注解数组,接着重复遍历即可 @Test public void test(){ MyAnnotation[] annos = Person.class.getAnnotationsByType(MyAnnotation.class); for (MyAnnotation anno : annos) { System.out.println(anno.value()); } //changlu //liner } }
通过查阅博客得知使用新增的@Repeatable,在编译器编译时实际上也是转换为使用的MyAnnotations注解表示。
@MyAnnotation("changlu") @MyAnnotation("liner") 转换后 @MyAnnotations(value=[@MyAnnotation("changlu"),@MyAnnotation("liner")]) //并且在字节码中数组使用[]来表示
对于获取单个注解值使用getAnnotation()方法,获取重复注解使用getAnnotationsByType()。
详细内容可看我下面的参考文章[1]。
4.2、新增类型注解(2个)
也就是在@Target注解中的成员参数ElementType枚举类中添加了两个实例:
ElementType.TYPE_PARAMETER :表示该注解能写在类型变量的声明语 句中(如:泛型声明)。
ElementType.TYPE_USE:表示该注解能写在使用类型的任何语句中。
区别说明:在Java 8之前,注解只能是在声明的地方所使用,Java8开始,注解可以应用在任何地方(可直接使用ElementType.TYPE_USE)。