引言
这篇文章我们来创建一个简单的注解,然后学习注解的两个概念:注解元素、元注解。
定义注解
注解的声明很像接口的声明,只比接口多了一个@符号。下面就是一个最简单的注解:
public @interface Test {}
这个注解没有任何的注解元素,不包含任何元素的注解称为标记注解。
注解元素
什么是注解元素
上面的Test是一个注解,但是它并没有什么作用。当我们需要注解提供一些信息时,就需要为注解提供一些注解元素了。注解元素是什么呢?看下面这个例子:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface UseCase {int id();String description() default "no description";}
id和description就是两个注解元素,description通过default声明了自己的默认值,如果在注解某个方法的时候,没有给出description的值,就会使用默认值。
这两个元素其实都是字段,但是是以方法的形式声明的,为什么要以方法的形式声明而不是直接声明字段呢?我们可以看一下UseCase反编译后的代码:
public interface UseCaseextends Annotation{public abstract int id();public abstract String description();}
可以看到,反编译后的UseCase就是一个继承了Annotation的接口。接口中是不能有成员变量的,所以注解提供了这种特殊的声明字段的方式来给出注解元素。
注解元素的限制
类型限制
注解元素的类型是有限制的,可以作为注解元素的类型有:
- 所有基本数据类型
- String
- Class
- Enum
- Annotation
- 所有以上类型的数组。
如果代码中使用了其他类型,就会出现编译错误。看下面的例子:
包装类型Integer和引用类型SimpleObject都出现了编译错误,UserCaseInnerAnnotation是另外一个注解。
默认值限制
注解元素除了类型有限制外,默认值也有限制。首先,每个注解元素要么有默认值,也就是在定义注解时通过default给定,要么就在使用注解时给定。如果两者都没有,就会出现编译错误。
此外,任何非基本类型的注解元素,无论是在注解声明时给定的默认值,还是在使用注解时给定的值,都不能是null。看下面这个示例:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface UseCase {int id();String description() default "no description";}

aMethod方法在使用注解时给定description为null,编译器给出了错误信息。
元注解
元注解就是用来注解其他注解的注解。上面的UseCase中@Target和@Retention就是两个元注解。java提供了下面五种元注解:
| 注解 | 描述 |
|---|---|
| @Target | 表示注解可以用于哪些地方。可能的 ElementType 参数包括: CONSTRUCTOR:构造器的声明 FIELD:字段声明(包括 enum 实例) LOCAL_VARIABLE:局部变量声明 METHOD:方法声明 PACKAGE:包声明 PARAMETER:参数声明 TYPE:类、接口(包括注解类型)或者 enum 声明 |
| @Retention | 表示注解信息保存的时长。可选的 RetentionPolicy 参数包括: SOURCE:注解将被编译器丢弃 CLASS:注解在 class 文件中可用,但是会被 VM 丢弃。 RUNTIME:VM 将在运行期也保留注解,因此可以通过反射机制读取注解的信息 |
| @Documented | 将此注解保存在 Javadoc 中 |
| @Inherited | 允许子类继承父类的注解 |
| @Repeatable | 允许一个注解可以被使用一次或者多次(Java 8)。 |
@Target
这个注解指定了我们定义的注解用在什么元素上面,这里的元素可以是包、类、字段、方法、成员变量、方法参数等。我们直接看一下@Target的定义:
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Target {/*** Returns an array of the kinds of elements an annotation type* can be applied to.* @return an array of the kinds of elements an annotation type* can be applied to*/ElementType[] value();}
可以看到这个注解有一个注解元素,是ElementType[]的数组,然后它也有自己的元注解,@Target(ElementType.ANNOTATION_TYPE)表明@Target这个注解只能作用于注解上面,我们来看ElementType的声明:
public enum ElementType {/** Class, interface (including annotation type), or enum declaration *///类、接口(包括注解)或者枚举TYPE,/** Field declaration (includes enum constants) *///字段FIELD,/** Method declaration *///方法METHOD,/** Formal parameter declaration *///方法内的参数PARAMETER,/** Constructor declaration *///构造方法CONSTRUCTOR,/** Local variable declaration *///成员变量LOCAL_VARIABLE,/** Annotation type declaration *///注解ANNOTATION_TYPE,/** Package declaration *///包PACKAGE,/** Type parameter declaration*///类型参数TYPE_PARAMETER,/**Use of a type*///类型使用TYPE_USE}
这是一个枚举,里面列举出了注解可以作用的所有元素。
既然是枚举数组,说明@Target括号后面可以有多个ElementType,也就是说这个注解可以同时作用在多种元素(例如同时作用于方法和字段)上面。
关于@Target元注解,我们目前只需要了解这些即可。
@Retention
这个注解用来指定注解保存的时长。它的注解元素是一个RetentionPolicy的数组:
@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Retention {/*** Returns the retention policy.* @return the retention policy*/RetentionPolicy value();}
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.* 注解会被保留在calss文件中但是不会在运行时被虚拟机保留,这是默认的行为,* 也就是没有给定的情况下,默认就是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}
每种RetentionPolicy的解释都比较清楚,代表了注解生效的不同阶段,我们现在只需要记住这三个取值即可,具体每种取值对注解的影响,我们会在后面介绍注解处理器时讲解。
@Documented
@Inherited
我们还是先看一下这个元注解的定义和注释:
/*** Indicates that an annotation type is automatically inherited. If* an Inherited meta-annotation is present on an annotation type* declaration, and the user queries the annotation type on a class* declaration, and the class declaration has no annotation for this type,* then the class's superclass will automatically be queried for the* annotation type. This process will be repeated until an annotation for this* type is found, or the top of the class hierarchy (Object)* is reached. If no superclass has an annotation for this type, then* the query will indicate that the class in question has no such annotation.** <p>Note that this meta-annotation type has no effect if the annotated* type is used to annotate anything other than a class. Note also* that this meta-annotation only causes annotations to be inherited* from superclasses; annotations on implemented interfaces have no* effect.*/@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Inherited {}
首先,这个注解是没有注解元素的。看注释的解释,意思是,如果一个注解(我们叫做注解A)被@Inherited这个元注解修饰了,那么当在一个类(我们叫做C)上查找A这个注解的时候,如果C的声明中没有A,就会在这个类的父类中查找,这个操作一直循环直到找到这个注解或者到达继承关系的顶部,也就是找到Object。如果最后都没有找到,就认为C没有A注解。
注意,因为@Inherited这个注解指定的是继承关系,所以只对作用于类的注解起作用,如果某个注解(我们叫做A)被@Inherited注解修饰,但是A作用于不是类的元素,例如字段,方法上面,就不会起作用。同样需要注意对于接口的继承关系,也不起作用,只对父类起作用。
我们来看一个例子:
@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inheritedpublic @interface InheritedAnnotation {int id() default 1;String name() default "a simple name";}@InheritedAnnotation(id = 3,name = "this is the parent of parent")public class InheritedParentParent {}public class InheritedChild extends InheritedParent {}public class InheritedParent extends InheritedParentParent {}
public static void main(String[] args) throws NoSuchMethodException {Class<InheritedChild> inheritedChildClass = InheritedChild.class;InheritedAnnotation classAnnotation = inheritedChildClass.getAnnotation(InheritedAnnotation.class);if(null != classAnnotation){System.out.println("the id of InheritChild is "+classAnnotation.id() + " and the name is "+classAnnotation.name());}}
三层继承关系中,InheritedParentParent是顶层父类,并且被InheritedAnnotation这个注解修饰,我们在InheritChild上查找这个注解,最后会查找到InheritedParentParent上,输出的结果如下:
the id of InheritChild is 3 and the name is this is the parent of parent
这里通过反射获取注解的方式会在下面讲解注解处理器时描述,这里我们只需要理解@Inherited这个注解的机制即可。
@Repeatable
我们先看定义和注释:
/*** The annotation type {@code java.lang.annotation.Repeatable} is* used to indicate that the annotation type whose declaration it* (meta-)annotates is <em>repeatable</em>. The value of* {@code @Repeatable} indicates the <em>containing annotation* type</em> for the repeatable annotation type.** @since 1.8* @jls 9.6 Annotation Types* @jls 9.7 Annotations*/@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.ANNOTATION_TYPE)public @interface Repeatable {/*** Indicates the <em>containing annotation type</em> for the* repeatable annotation type.* @return the containing annotation type*/Class<? extends Annotation> value();}
这是一个jdk1.8中新增加的元注解,它的注解元素是一个注解的Class。根据注释,如果一个注解(我们叫做A)被这个元注解修饰,那么A就是可重复的,value这个注解元素指定了在哪个注解里面,A是可重复的。这是什么意思呢?看下面的例子:
@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Repeatable(Values.class)public @interface Value {String name() ;}@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD,ElementType.TYPE})public @interface Values {Value[] value();}@Value(name = "name1")@Value(name = "name2")public class ValueAnno {public static void main(String[] args) {Class<ValueAnno> aClass = ValueAnno.class;Annotation[] annotations = aClass.getAnnotations();System.out.println(annotations.length);for (Annotation annotation : annotations) {System.out.println(annotation);}}}
第一个注解Value被@Repeatable修饰,并且注解元素是Values.class,意思是Value这个注解在Values这个注解中是可以重复的,然后Values注解中必须有一个Value注解的数组作为注解元素。
之后,在ValueAnno这个类中,我们使用了两个@Value来修饰,但是输出结果如下:
1@person.andy.concurrency.annotation.Values(value=[@person.andy.concurrency.annotation.Value(name=name1), @person.andy.concurrency.annotation.Value(name=name2)])
只有一个注解 ,并且是Values注解,两个Value注解被作为一个Values注解了。
这样,我们应该就能理解@Repeatable的含义了。
可重复注解和包含注解类型的定义
这里给出《java语言规范》第八版9.6.3中对可重复注解类型的定义:
如果注解类型T的声明是用一个@Repeatable元注解进行注解的,而该注解的value元素表示的是T的包含注解类型,那么T就是可重复的。
如果下面条件都为真,那么注解类型TC就是T的包含注解类型:
- TC声明了返回类型为T[]的value()方法。
- TC声明的任何除value()之外的方法都有缺省值。
- TC存留的时间至少与T一样长,其中其存留期是显式或者隐式地使用@Retention注解表示的。
- T可应用的程序元素的种类至少与TC可应用的程序元素种类相同。
- 如果T的声明有对应于java.lang.annotation.Document的元注解,那么TC的声明必须也有对应于java.lang.annatation.Document的元注解。
- 如果T的声明有对应于java. lang.annatation.Inherited的元注解,那么TC的声明必须也有对应的java.lang.annotation.Inherited的元注解。
可以看出,包含注解类型是有很多限制的,上面我们的Value是可重复注解,Values是Value的包含注解类型。
小结
通过这篇文章,我们应该知道了怎样定义一个注解,怎样提供注解元素,怎样使用元注解。但是到目前为止,我们还不知道通过注解我们能做什么,也不知道RetentionPolicy的不同取值会给注解带来怎样的影响,由于这两个问题是相互关联的,我们放在下一篇文章来一起讲解。
