引言
这篇文章我们来创建一个简单的注解,然后学习注解的两个概念:注解元素、元注解。
定义注解
注解的声明很像接口的声明,只比接口多了一个@符号。下面就是一个最简单的注解:
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 UseCase
extends 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)
@Inherited
public @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的不同取值会给注解带来怎样的影响,由于这两个问题是相互关联的,我们放在下一篇文章来一起讲解。