引言

这篇文章我们来创建一个简单的注解,然后学习注解的两个概念:注解元素、元注解。

定义注解

注解的声明很像接口的声明,只比接口多了一个@符号。下面就是一个最简单的注解:

  1. public @interface Test {
  2. }

这个注解没有任何的注解元素,不包含任何元素的注解称为标记注解。

注解元素

什么是注解元素

上面的Test是一个注解,但是它并没有什么作用。当我们需要注解提供一些信息时,就需要为注解提供一些注解元素了。注解元素是什么呢?看下面这个例子:

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface UseCase {
  4. int id();
  5. String description() default "no description";
  6. }

id和description就是两个注解元素,description通过default声明了自己的默认值,如果在注解某个方法的时候,没有给出description的值,就会使用默认值。
这两个元素其实都是字段,但是是以方法的形式声明的,为什么要以方法的形式声明而不是直接声明字段呢?我们可以看一下UseCase反编译后的代码:

  1. public interface UseCase
  2. extends Annotation
  3. {
  4. public abstract int id();
  5. public abstract String description();
  6. }

可以看到,反编译后的UseCase就是一个继承了Annotation的接口。接口中是不能有成员变量的,所以注解提供了这种特殊的声明字段的方式来给出注解元素。

注解元素的限制

类型限制

注解元素的类型是有限制的,可以作为注解元素的类型有:

  • 所有基本数据类型
  • String
  • Class
  • Enum
  • Annotation
  • 所有以上类型的数组。

如果代码中使用了其他类型,就会出现编译错误。看下面的例子:
a.png
包装类型Integer和引用类型SimpleObject都出现了编译错误,UserCaseInnerAnnotation是另外一个注解。

默认值限制

注解元素除了类型有限制外,默认值也有限制。首先,每个注解元素要么有默认值,也就是在定义注解时通过default给定,要么就在使用注解时给定。如果两者都没有,就会出现编译错误。
此外,任何非基本类型的注解元素,无论是在注解声明时给定的默认值,还是在使用注解时给定的值,都不能是null。看下面这个示例:

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface UseCase {
  4. int id();
  5. String description() default "no description";
  6. }

null.png
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的定义:

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Target {
  5. /**
  6. * Returns an array of the kinds of elements an annotation type
  7. * can be applied to.
  8. * @return an array of the kinds of elements an annotation type
  9. * can be applied to
  10. */
  11. ElementType[] value();
  12. }

可以看到这个注解有一个注解元素,是ElementType[]的数组,然后它也有自己的元注解,@Target(ElementType.ANNOTATION_TYPE)表明@Target这个注解只能作用于注解上面,我们来看ElementType的声明:

  1. public enum ElementType {
  2. /** Class, interface (including annotation type), or enum declaration */
  3. //类、接口(包括注解)或者枚举
  4. TYPE,
  5. /** Field declaration (includes enum constants) */
  6. //字段
  7. FIELD,
  8. /** Method declaration */
  9. //方法
  10. METHOD,
  11. /** Formal parameter declaration */
  12. //方法内的参数
  13. PARAMETER,
  14. /** Constructor declaration */
  15. //构造方法
  16. CONSTRUCTOR,
  17. /** Local variable declaration */
  18. //成员变量
  19. LOCAL_VARIABLE,
  20. /** Annotation type declaration */
  21. //注解
  22. ANNOTATION_TYPE,
  23. /** Package declaration */
  24. //包
  25. PACKAGE,
  26. /** Type parameter declaration*/
  27. //类型参数
  28. TYPE_PARAMETER,
  29. /**Use of a type*/
  30. //类型使用
  31. TYPE_USE
  32. }

这是一个枚举,里面列举出了注解可以作用的所有元素。
既然是枚举数组,说明@Target括号后面可以有多个ElementType,也就是说这个注解可以同时作用在多种元素(例如同时作用于方法和字段)上面。
关于@Target元注解,我们目前只需要了解这些即可。

@Retention

这个注解用来指定注解保存的时长。它的注解元素是一个RetentionPolicy的数组:

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.ANNOTATION_TYPE)
  4. public @interface Retention {
  5. /**
  6. * Returns the retention policy.
  7. * @return the retention policy
  8. */
  9. RetentionPolicy value();
  10. }

RetentionPolicy定义如下:

  1. public enum RetentionPolicy {
  2. /**
  3. * Annotations are to be discarded by the compiler.
  4. * 注解会被编译器丢弃
  5. */
  6. SOURCE,
  7. /**
  8. * Annotations are to be recorded in the class file by the compiler
  9. * but need not be retained by the VM at run time. This is the default
  10. * behavior.
  11. * 注解会被保留在calss文件中但是不会在运行时被虚拟机保留,这是默认的行为,
  12. * 也就是没有给定的情况下,默认就是CLASS
  13. */
  14. CLASS,
  15. /**
  16. * Annotations are to be recorded in the class file by the compiler and
  17. * retained by the VM at run time, so they may be read reflectively.
  18. * 注解会被保留在class文件中并且会在运行时被虚拟机保留,所以可以通过反射获得。
  19. * @see java.lang.reflect.AnnotatedElement
  20. */
  21. RUNTIME
  22. }

每种RetentionPolicy的解释都比较清楚,代表了注解生效的不同阶段,我们现在只需要记住这三个取值即可,具体每种取值对注解的影响,我们会在后面介绍注解处理器时讲解。

@Documented

这个注解的作用是将注解中的元素包含到Javadoc中去。

@Inherited

我们还是先看一下这个元注解的定义和注释:

  1. /**
  2. * Indicates that an annotation type is automatically inherited. If
  3. * an Inherited meta-annotation is present on an annotation type
  4. * declaration, and the user queries the annotation type on a class
  5. * declaration, and the class declaration has no annotation for this type,
  6. * then the class's superclass will automatically be queried for the
  7. * annotation type. This process will be repeated until an annotation for this
  8. * type is found, or the top of the class hierarchy (Object)
  9. * is reached. If no superclass has an annotation for this type, then
  10. * the query will indicate that the class in question has no such annotation.
  11. *
  12. * <p>Note that this meta-annotation type has no effect if the annotated
  13. * type is used to annotate anything other than a class. Note also
  14. * that this meta-annotation only causes annotations to be inherited
  15. * from superclasses; annotations on implemented interfaces have no
  16. * effect.
  17. */
  18. @Documented
  19. @Retention(RetentionPolicy.RUNTIME)
  20. @Target(ElementType.ANNOTATION_TYPE)
  21. public @interface Inherited {
  22. }

首先,这个注解是没有注解元素的。看注释的解释,意思是,如果一个注解(我们叫做注解A)被@Inherited这个元注解修饰了,那么当在一个类(我们叫做C)上查找A这个注解的时候,如果C的声明中没有A,就会在这个类的父类中查找,这个操作一直循环直到找到这个注解或者到达继承关系的顶部,也就是找到Object。如果最后都没有找到,就认为C没有A注解。
注意,因为@Inherited这个注解指定的是继承关系,所以只对作用于类的注解起作用,如果某个注解(我们叫做A)被@Inherited注解修饰,但是A作用于不是类的元素,例如字段,方法上面,就不会起作用。同样需要注意对于接口的继承关系,也不起作用,只对父类起作用。
我们来看一个例子:

  1. @Target({ElementType.METHOD,ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Inherited
  4. public @interface InheritedAnnotation {
  5. int id() default 1;
  6. String name() default "a simple name";
  7. }
  8. @InheritedAnnotation(id = 3,name = "this is the parent of parent")
  9. public class InheritedParentParent {
  10. }
  11. public class InheritedChild extends InheritedParent {
  12. }
  13. public class InheritedParent extends InheritedParentParent {
  14. }
  1. public static void main(String[] args) throws NoSuchMethodException {
  2. Class<InheritedChild> inheritedChildClass = InheritedChild.class;
  3. InheritedAnnotation classAnnotation = inheritedChildClass.getAnnotation(InheritedAnnotation.class);
  4. if(null != classAnnotation){
  5. System.out.println("the id of InheritChild is "+classAnnotation.id() + " and the name is "+classAnnotation.name());
  6. }
  7. }

三层继承关系中,InheritedParentParent是顶层父类,并且被InheritedAnnotation这个注解修饰,我们在InheritChild上查找这个注解,最后会查找到InheritedParentParent上,输出的结果如下:

  1. the id of InheritChild is 3 and the name is this is the parent of parent

这里通过反射获取注解的方式会在下面讲解注解处理器时描述,这里我们只需要理解@Inherited这个注解的机制即可。

@Repeatable

我们先看定义和注释:

  1. /**
  2. * The annotation type {@code java.lang.annotation.Repeatable} is
  3. * used to indicate that the annotation type whose declaration it
  4. * (meta-)annotates is <em>repeatable</em>. The value of
  5. * {@code @Repeatable} indicates the <em>containing annotation
  6. * type</em> for the repeatable annotation type.
  7. *
  8. * @since 1.8
  9. * @jls 9.6 Annotation Types
  10. * @jls 9.7 Annotations
  11. */
  12. @Documented
  13. @Retention(RetentionPolicy.RUNTIME)
  14. @Target(ElementType.ANNOTATION_TYPE)
  15. public @interface Repeatable {
  16. /**
  17. * Indicates the <em>containing annotation type</em> for the
  18. * repeatable annotation type.
  19. * @return the containing annotation type
  20. */
  21. Class<? extends Annotation> value();
  22. }

这是一个jdk1.8中新增加的元注解,它的注解元素是一个注解的Class。根据注释,如果一个注解(我们叫做A)被这个元注解修饰,那么A就是可重复的,value这个注解元素指定了在哪个注解里面,A是可重复的。这是什么意思呢?看下面的例子:

  1. @Target({ElementType.METHOD,ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Repeatable(Values.class)
  4. public @interface Value {
  5. String name() ;
  6. }
  7. @Retention(RetentionPolicy.RUNTIME)
  8. @Target({ElementType.METHOD,ElementType.TYPE})
  9. public @interface Values {
  10. Value[] value();
  11. }
  12. @Value(name = "name1")
  13. @Value(name = "name2")
  14. public class ValueAnno {
  15. public static void main(String[] args) {
  16. Class<ValueAnno> aClass = ValueAnno.class;
  17. Annotation[] annotations = aClass.getAnnotations();
  18. System.out.println(annotations.length);
  19. for (Annotation annotation : annotations) {
  20. System.out.println(annotation);
  21. }
  22. }
  23. }

第一个注解Value被@Repeatable修饰,并且注解元素是Values.class,意思是Value这个注解在Values这个注解中是可以重复的,然后Values注解中必须有一个Value注解的数组作为注解元素。
之后,在ValueAnno这个类中,我们使用了两个@Value来修饰,但是输出结果如下:

  1. 1
  2. @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的包含注解类型:

  1. TC声明了返回类型为T[]的value()方法。
  2. TC声明的任何除value()之外的方法都有缺省值。
  3. TC存留的时间至少与T一样长,其中其存留期是显式或者隐式地使用@Retention注解表示的。
  4. T可应用的程序元素的种类至少与TC可应用的程序元素种类相同。
  5. 如果T的声明有对应于java.lang.annotation.Document的元注解,那么TC的声明必须也有对应于java.lang.annatation.Document的元注解。
  6. 如果T的声明有对应于java. lang.annatation.Inherited的元注解,那么TC的声明必须也有对应的java.lang.annotation.Inherited的元注解。

可以看出,包含注解类型是有很多限制的,上面我们的Value是可重复注解,Values是Value的包含注解类型。

小结

通过这篇文章,我们应该知道了怎样定义一个注解,怎样提供注解元素,怎样使用元注解。但是到目前为止,我们还不知道通过注解我们能做什么,也不知道RetentionPolicy的不同取值会给注解带来怎样的影响,由于这两个问题是相互关联的,我们放在下一篇文章来一起讲解。