(二):自定义注解的定义

注解Annotation全解析(一):我不是注释一文中,我们对于注解有了一个基本的认识:注解其实就是给我们代码中的一些关键节点(包、类、方法、参数、属性等)打上一些标记,然后在编译期或运行期通过判断这些标记来执行一些指定操作。那么通过这个流程我们可以分析出注解的使用结构是:

要想完全学会注解,光靠用别人定义的注解是不够的,所以我们要学会自己定义注解。另外,请在学习的下面内容的时候,打开你的Eclipse跟着内容进行编码。

基本定义语法

注解类型的声明部分

注解(Annotation)在Java中是一种与类、接口、枚举等同等地位的标示类型,所以它的基本定义语法与上面这几个都是一致的,只是所使用的关键字有所不同。

1
2
3
//定义一个公共的名为LovoAnnotation的注解
public @interface LovoAnnotation{
}

它使用的定义关键字是:@interface。这无疑表明了注解和接口是有关系的。实际上,注解其本质就是一种特殊的接口。在底层实现上,所有定义的注解(Annotation)都会自动继承java.lang.annotion.Annotation接口。但请注意,注解的声明只能使用@interface,如果自以为是的写成:
1
2
3
import java.lang.annotation.Annotation;
public interface LovoAnnotation extends Annotation{
}

那么不好意思,LovoAnnotation不会被认为是注解类型,只会被当成一个普通的接口。

注解类型的实现部分

根据我们书写类、接口等自定义类型的经验,在这些类型的实现部分无非是书写属性、构造或方法这些内容。但是,注解(Annotation)在实现部分允许书写的内容与之前我们的经验完全不同。
注解(Annotation)在实现部分只能定义一个东西:注解类型元素(annotation type element)。我们来看看它的语法:

1
2
3
4
5
public @interface LovoAnnotation{
public String name();
int age();
int[] array();
}

这个时候有些反应快的同学肯定会想:咦?这不就是接口里面默认定义抽象方法的语法吗?不要着急,我再写一个给你看:
1
2
3
4
5
public @interface LovoAnnotation{
public String name();
int age() default 18;
int[] scores();
}

还是抽象方法吗?打脸了吧!
注解里面定义的是:注解类型元素!注解类型元素!注解类型元素!重要的事情说三遍!

  1. 该元素的访问修饰符只能是public,不写也默认为public;
  2. 该元素的类型只能是基本数据类型、String、Class、注解类型(体现了注解嵌套的效果)、枚举类型以及上面这几种的一维数组;
  3. 该元素的名字应该一般都是名词形式,如果一个注解中只有一个类型元素,请把名字起成value(后面使用会带来便利操作);
  4. ()不是定义方法参数列表的地方,也不能在括号中定义任何参数,就是一个特殊语法;
  5. default代表默认值,值必须和第2点定义的类型保持一致;
  6. 如果没有默认值,代表后面使用时必须给该类型元素赋值;

不得不说,注解元素类型的语法非常奇怪,即有属性的特征(可以赋值),又有方法的特征(打上了一对圆括号)。但是这么设计是有道理的,我们在后面的系列文章中可以看到:注解(Annotation)在定义好了以后,使用的时候操作元素类型像在操作属性,解析的时候操作元素类型像在操作方法。

常用的元注解

一个最最基本的注解定义就只包括了上面的两部分内容:1、注解的名字;2、注解包含的类型元素。但是,我们在使用JDK自带注解的时候发现,有些注解只能写在方法上面(比如@Override);有些却可以写在类的上面(比如@Deprecated)。当然除此以外还有很多细节性的定义,那么这些定义该如何做呢?接下来就该元注解出场了!
元注解:专门修饰注解的注解。它们都是为了更好的设计自定义注解的细节而专门设计的。我们为大家一个个来做介绍。

@Target

@Target注解,是专门用来限定某个自定义注解能够被应用到哪些Java元素上面的。它使用一个枚举类型ElementType定义了如下几个选择:

枚举值 注解能够被应用的地方
ElementType.ANNOTATION_TYPE 注解类型的声明
ElementType.CONSTRUCTOR 构造方法的声明
ElementType.FIELD 属性的声明
ElementType.LOCAL_VARIABLE 局部变量的声明
ElementType.METHOD 方法的声明
ElementType.PACKAGE 包的声明
ElementType.PARAMETER 方法参数的声明
ElementType.TYPE 类、接口以及枚举的声明
1
2
3
4
5
6
7
8
9
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
//LovoAnnotation被限定在只能写在属性的声明上或方法的声明上
@Target({ElementType.FIELD,ElementType.METHOD})
public @interface LovoAnnotation{
public String name();
int age() default 18;
int[] scores();
}

@Retention

@Retention注解,被翻译成保持力或持久力。它是用来限定某个自定义注解的生命力。注解的生命周期有三个阶段:仅存在java源文件是一个阶段;被编译到class文件是一个阶段;运行期被加载到Class对象中是第三个阶段。@Retention同样使用一个枚举类型RetentionPolicy定义了这三个选择:

枚举值 对应的生命力
RetentionPolicy.SOURCE 仅在Java源文件中
RetentionPolicy.CLASS 被编译到class文件中
RetentionPolicy.RUNTIME 运行期放到Class对象中

我们再详解一下:

  1. 如果一个注解被限定在Java源文件中,那么这个注解即不会参与编 译也不会在运行期起任何作用,这个注解就和一个注释是一样的效果,只能被阅读Java文件的人看到;
  2. 如果一个注解被编译到Class文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到;
  3. 如果一个注解被定义在RetentionPolicy.RUNTIME,那么这个注解可以在运行期的加载阶段被加载到Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME
  4. 在默认的情况下,自定义注解是使用的RetentionPolicy.CLASS。
1
2
3
4
5
6
7
8
9
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
//LovoAnnotation的生命力被保持到运行阶段
@Retention(RetentionPolicy.RUNTIME)
public @interface LovoAnnotation{
public String name();
int age() default 18;
int[] scores();
}

@Documented

@Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。

@Inherited

@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。

一个完整的自定义Annotation示例

在学习了上面的各种定义注解的语法后,我们就可以根据我们的实际需要定义我们自己的自定义注解了。下面,我们就来定义一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface LovoAnnotation{
public String name();
int age() default 18;
int[] scores();
}

当我们定义好了这个注解,那么接下来的事情就是如何使用它了。请关注我们关于注解的下一个章节。

系列:
注解Annotation全解析(一):我不是注释
注解Annotation全解析(二):自定义注解的定义
注解Annotation全解析(三):自定义注解的配置使用
注解Annotation全解析(四):自定义注解的运行时解析
注解Annotation全解析(五):注解与XML的比较

坚持原创技术分享,您的支持将鼓励我继续创作!