作者 | cherry-peng
来源 | https://blog.csdn.net/xsp_happyboy/article/details/80987484
首先看看官方对注解的描述:
An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.复制代码
翻译:
注解是一种能被添加到java代码中的元数据,类、方法、变量、参数和包都可以用注解来修饰。注解对于它所修饰的代码并没有直接的影响。复制代码
通过官方描述得出以下结论:
继续看看官方对它的使用范围的描述:
Annotations have a number of uses, among them:Information for the complier - Annotations can be used by the compiler to detect errors or suppress warnings.Compiler-time and deployment-time processing - Software tools can process annotation information to generate code, XML files, and so forth.Runtime processing - Some annotations are available to be examined at runtime.复制代码
翻译:
注解又许多用法,其中有:为编译器提供信息 - 注解能被编译器检测到错误或抑制警告。编译时和部署时的处理 - 软件工具能处理注解信息从而生成代码,XML文件等等。运行时的处理 - 有些注解在运行时能被检测到。复制代码
基于上一节,已对注解有了一个基本的认识: 注解其实就是一种标记,可以在程序代码中的关键节点(类、方法、变量、参数、包)上打上这些标记,然后程序在编译时或运行时可以检测到这些标记从而执行一些特殊操作。 因此可以得出自定义注解使用的基本流程:
注解类型的声明部分:
注解在Java中,与类、接口、枚举类似,因此其声明语法基本一致,只是所使用的关键字有所不同@interface。 在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。
public @interface CherryAnnotation { }复制代码
注解类型的实现部分:
根据我们在自定义类的经验,在类的实现部分无非就是书写构造、属性或方法。但是,在自定义注解中,其实现部分 只能定义一个东西:注解类型元素(annotation type element)。 咱们来看看其语法:
public @interface CherryAnnotation { public String name(); int age(); int[] array(); }复制代码
也许你会认为这不就是接口中定义抽象方法的语法嘛?别着急,咱们看看下面这个:
public @interface CherryAnnotation { public String name(); int age() default 18; int[] array(); }复制代码
看到关键字 default 了吗?还觉得是抽象方法吗?
注解里面定义的是: 注解类型元素!
定义注解类型元素时需要注意如下几点:
可以看出,注解类型元素的语法非常奇怪,即又有属性的特征(可以赋值),又有方法的特征(打上了一对括号)。但是这么设计是有道理的,我们在后面的章节中可以看到:注解在定义好了以后, 使用的时候操作元素类型像在操作属性,解析的时候操作元素类型像在操作方法。
一个最最基本的注解定义就只包括了上面的两部分内容:1、注解的名字;2、注解包含的类型元素。但是,我们在使用JDK自带注解的时候发现,有些注解只能写在方法上面(比如@Override);有些却可以写在类的上面(比如@Deprecated)。当然除此以外还有很多细节性的定义,那么这些定义该如何做呢?接下来就该元注解出场了! 元注解:专门修饰注解的注解。 它们都是为了更好的设计自定义注解的细节而专门设计的。我们为大家一个个来做介绍。
@Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。它使用一个枚举类型定义如下:
public enum ElementType { /** 类,接口(包括注解类型)或枚举的声明 */ TYPE, /** 属性的声明 */ FIELD, /** 方法的声明 */ METHOD, /** 方法形式参数声明 */ PARAMETER, /** 构造方法的声明 */ CONSTRUCTOR, /** 局部变量声明 */ LOCAL_VARIABLE, /** 注解类型声明 */ ANNOTATION_TYPE, /** 包的声明 */ PACKAGE }复制代码
//@CherryAnnotation被限定只能使用在类、接口或方法上面 @Target(value = {ElementType.TYPE,ElementType.METHOD}) public @interface CherryAnnotation { String name(); int age() default 18; int[] array(); }复制代码
@Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。注解的生命周期有三个阶段:1、Java源文件阶段;2、编译到class文件阶段;3、运行期阶段。同样使用了RetentionPolicy枚举类型定义了三个阶段:
public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler. * (注解将被编译器忽略掉) */ SOURCE, /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. * (注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为) */ CLASS, /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * (注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到) * @see java.lang.reflect.AnnotatedElement */ RUNTIME }复制代码
我们再详解一下:
@Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。
@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。
回顾一下注解的使用流程:
首先,定义一个注解、和一个供注解修饰的简单Java类
@Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) @Documented public @interface CherryAnnotation { String name(); int age() default 18; int[] score(); }复制代码
public class Student{ public void study(int times){ for(int i = 0; i < times; i++){ System.out.println("Good Good Study, Day Day Up!"); } } }复制代码
简单分析下:
所以最终书写形式如下:
public class Student { @CherryAnnotation(name = "cherry-peng",age = 23,score = {99,66,77}) public void study(int times){ for(int i = 0; i < times; i++){ System.out.println("Good Good Study, Day Day Up!"); } } }复制代码
特殊语法一:
@Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.TYPE}) @Documented public @interface FirstAnnotation { }复制代码
//等效于@FirstAnnotation() @FirstAnnotation public class JavaBean{ //省略实现部分 }复制代码
特殊语法二:
@Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.TYPE}) @Documented public @interface SecondAnnotation { String value(); }复制代码
//等效于@ SecondAnnotation(value = "this is second annotation") @SecondAnnotation("this is annotation") public class JavaBean{ //省略实现部分 }复制代码
特殊用法三:
@Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.TYPE}) @Documented public @interface ThirdAnnotation { String[] name(); }复制代码
//等效于@ ThirdAnnotation(name = {"this is third annotation"}) @ ThirdAnnotation(name = "this is third annotation") public class JavaBean{ //省略实现部分 }复制代码
特殊用法四:
这一章是使用注解的核心,读完此章即可明白, 如何在程序运行时检测到注解,并进行一系列特殊操作!
首先回顾一下,之前自定义的注解@CherryAnnotation,并把它配置在了类Student上,代码如下:
@Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD}) @Documented public @interface CherryAnnotation { String name(); int age() default 18; int[] score(); }复制代码
package pojos; public class Student { @CherryAnnotation(name = "cherry-peng",age = 23,score = {99,66,77}) public void study(int times){ for(int i = 0; i < times; i++){ System.out.println("Good Good Study, Day Day Up!"); } } }复制代码
注解保持力的三个阶段:
只有当注解的保持力处于运行阶段,即使用@Retention(RetentionPolicy.RUNTIME)修饰注解时,才能在JVM运行时,检测到注解,并进行一系列特殊操作。
因此,明确我们的目标: 在运行期探究和使用编译期的内容(编译期配置的注解),要用到Java中的灵魂技术——反射!
public class TestAnnotation { public static void main(String[] args){ try { //获取Student的Class对象 Class stuClass = Class.forName("pojos.Student"); //说明一下,这里形参不能写成Integer.class,应写为int.class Method stuMethod = stuClass.getMethod("study",int.class); if(stuMethod.isAnnotationPresent(CherryAnnotation.class)){ System.out.println("Student类上配置了CherryAnnotation注解!"); //获取该元素上指定类型的注解 CherryAnnotation cherryAnnotation = stuMethod.getAnnotation(CherryAnnotation.class); System.out.println("name: " + cherryAnnotation.name() + ", age: " + cherryAnnotation.age() + ", score: " + cherryAnnotation.score()[0]); }else{ System.out.println("Student类上没有配置CherryAnnotation注解!"); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } } }复制代码
解释一下:
如果文章对您有帮助,请记得点赞关注哟~
欢迎大家关注我的公众号:情系IT,每日推送技术文章供大家学习参考。