摘要
本文详细介绍java注解是什么,如何声明java注解,如何解析java注解。最后介绍JDK提供的几大基本注解,使用这些基本注解可自定义用户注解。
一、注解是什么
1)是元数据
元数据被定义为:描述数据的数据,对数据及信息资源的描述性信息。
具体到Java语言,类型,方法,属性,参数等程序元素是java编程中必不可少的数据。 JDK5开始java增加了对元数据的支持——注解(Annotation),用以描述以上的程序元素。
2)只是元数据
java注解一点也不神秘,它仅仅是修饰程序元素(类,方法,成员变量,包,构造器)的特殊标记。
正如我们肉眼所见那样,作为元数据,这个特殊标记仅仅对程序元素进行进一步说明;注解本身并不会影响程序逻辑,增加,删除,修改程序元素上的java注解,代码都始终如一的执行。
3)解析元数据
注解已经被Spring大量应用,大有化腐朽为神奇的韵味,通过配置一些简单的注解,实现神奇的复杂功能。既然注解只是元数据,对程序不产生影响,那么Spring注解是如何影响程序的呢?
事实上,所有的java注解,都配套的解析工具。这些解析工具通过读取程序元素上的注解,按照一定的规则对程序施加影响,这类工具统称APT
(Annotatoin Processing Tool)。不管是JDK原生注解,第三方如spring注解,每一个注解必定可以找到解析它的代码。
二、声明java注解
关于此部分内容,JDK官方提供了详细介绍:Annotation Types
1)声明
注解声明产生新的注解类型,注解是一个特殊的接口类型。正常的接口声明与注解声明的区别在于,接口声明的关键字是interface
,而注解声明使用@interface
关键字。另外,注解成员只有返回类型有限的方法,不存在属性。
声明语法:
AnnotationTypeDeclaration:
{InterfaceModifier} @ interface Identifier AnnotationTypeBody
以下是一个简单的注解声明:
public @interface Description {
}
2)成员
注解的成员只有方法,每个方法为注解定义一个元素。注解有且仅有通过方法定义的元素。
定义成员的语法如下
AnnotationTypeElementDeclaration:
{AnnotationTypeElementModifier} UnannType Identifier ( ) [Dims] [DefaultValue] ;
注解中的方法成员返回类型有限,仅允许以下类型,否则会发生编译时错误:
- 基本类型
- java.lang.String
- 具体的Class类型,或者带类型通配符的Class(
Class<?>
) - 枚举类型
- 注解类型
- 以上类型的数组
以下自定义注解类型枚举了注解方法可能的返回类型:
public @interface Description {
// 基本类型
int age() default 18;
// java.lang.String
String name();
// 具体class
Class<Long> specificClass() default Long.class;
// 通配
Class<?> classes() default String.class;
// 枚举
CEnum cEnum();
// 其他注解类型
Override otherAnno();
enum CEnum {}
}
三、JDK对注解的支持
jdk提供接口java.lang.annotation.Annotation
支持注解声明,在反射包下,提供接口java.lang.reflect.AnnotatedElement
支持注解解析。
1)Annotation
首先看下Annotation接口的声明:
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
关于Annotation 接口,有以下说明:
- Annotation 接口是所有注解类型都会隐式继承的接口;
- 手动显式继承Annotation 接口的接口不是注解;
- Annotation 接口本身也不是注解。
由于继承性,所有的注解也拥有了Annotation 的方法。
2)AnnotatedElement
AnnotatedElement 接口表示当前JVM中一个“被注解的程序元素”。在Java中,所有实现AnnotatedElement 接口的类,都是可被注解修饰的。
该接口提供了一系列方法,获取程序元素上的注解:
AnnotatedElement方法Java中,实现这个接口的类有:
AnnotatedElement实现类因此,java程序中,注解几乎可修饰所有常见的元素,如:
- AccessibleObject(可访问对象,如:方法、构造器、属性等)
- Class(类,就是你用Java语言编程时每天都要写的那个东西)
- Constructor(构造器,类的构造方法的类型)
- Executable(可执行的,如构造器和方法)
- Field(属性,类中属性的类型)
- Method(方法,类中方法的类型)
- Package(包,你每天都在声明的包的类型)
- Parameter(参数,主要指方法或函数的参数,其实是这些参数的类型)
得益于此,可以通过反射,获取程序元素上的注解,对程序施加影响。
四、注解类型
按照注解是否具有成员,以及使用方式,注解可分为:
- 标记型注解
- 数据型注解
- 元注解
标记型注解
标记型注解不具备方法,仅仅利用自身的存在与否反映信息,如@Override
。
数据型注解
数据型注解具备方法,能够提供更加丰富的信息,如@Target
。
元注解
根据注解的使用方式,如果注解是用于修饰其他注解的,那么这个注解就是一个元注解。如@Target
五、基础元注解
jdk在java.lang.annotation包下提供了几个基本的元注解,这些注解是自定义注解的基石。
这些注解包括:
- @Documented
- @Target
- @Retention
- @Inherited
- @Repeatable(jdk8+)
下面分别对这些重要的注解进行详细介绍。
1)@Documented
Documented是标记型注解,只能修饰其他注解。在使用javadoc等工具生成java文档时,若使用具有@Document的注解修饰程序元素,在java文档中,这些注解会作为文档的一部分说明程序元素。
如自定义注解被@Document修饰
@Documented
@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Description {
String value() default "";
}
@Description 修饰的类:
@Description(value = "描述")
public class AnnotationTest {
那么类AnnotationTest 产生的javadoc文档中,注解会作为说明的一部分:
@document效果2)@Target
Target是数据型注解,只能修饰其他注解,指示注解可修饰的程序元素。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
包含一个ElementType
数组类型返回值的方法,指定注解可修饰的多个程序元素类型。通过观察枚举ElementType可知注解可修饰的程序元素集合。
默认:注解未使用@Target修饰时,默认修饰除TYPE_PARAMETER(类型参数)之外的所有程序元素。
4)@Retention
Retention是数据型注解,只能修饰其他注解,指示被修饰的注解能够保留多久;只有直接用于修饰注解时保留策略有效,作为注解的成员时保留策略无效。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
包含一个RetentionPolicy
类型返回值的方法,通过观察其源码,保留策略包括:
- SOURCE--注解会被编译器丢弃,不可反射读;
- CLASS--编译器保留,VM和运行时丢弃,不可反射读;
- RUNTIME--编译器记录入class文件,VM和运行时都会保留;因此这种类型是可以使用反射读取的。
默认:注解未使用@Retention修饰时,保留策略默认为RetentionPolicy.CLASS
4)@Inherited
Inherited是标记型注解,只能修饰其他注解,指示注解类型的自动继承;注解类型的继承与我们熟知的类继承,接口继承有很大的区别。
当用户在一个程序元素类上,使用AnnotatedElement的相关注解查询方法,查询元注解Inherited修饰的其他注解类型A
时,如果这个类本身并没有被注解A修饰,那么会自动查询这个类的父类是否被注解A修饰。查询过程会沿着类继承链一直向上查找,直到注解A被找到,或者到达继承链顶层(Object)。
如果元注解Inherited修饰的其他注解,修饰了除类之外的其他程序元素,那么继承性将会失效。
下面的Demo演示了注解的这种继承性:
public class InheritedTest {
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@Inherited // 声明注解具有继承性
@interface AInherited {
String value() default "";
}
@AInherited("父类")
class SuperClass {}
class ChildClass extends SuperClass {
}
public static void main(String[] args) {
AInherited annotation = ChildClass.class.getAnnotation(AInherited.class);
System.out.println(annotation);
// output: @annotations.InheritedTest$AInherited(value=父类)
}
}
若自定义注解 AInherited
没有被Inherited 修饰,子类ChildClass.class.getAnnotation(AInherited.class)
将会返回null
若子类ChildClass在类上自行指定了与父类相同类型的注解AInherited,那么ChildClass.class.getAnnotation(AInherited.class)
将会返回子类声明的注解
属性和方法注解的继承
属性和方法注解的继承,与类注解的继承完全不同,与元注解Inherited毫无关系,忠实于方法/属性本身的继承。
以下示例说明属性/方法注解的继承:
public class InheritedTest {
@Target(value = {ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
@Retention(value = RetentionPolicy.RUNTIME)
@interface AInherited {
String value() default "";
}
@AInherited("父类")
class SuperClass {
@AInherited("父类方法foo")
public void foo() {}
@AInherited("父类方法bar")
public void bar(){}
@AInherited("父类的属性")
public String field;
}
class ChildClass extends SuperClass {
@Override
public void foo() {
super.foo();
}
}
public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
Method foo = ChildClass.class.getMethod("foo");
System.out.println(Arrays.toString(foo.getAnnotations()));
// 子类ChildClass重写了父类方法foo,并且Source注解只在源码阶段保留,所以没有任何注解
Method bar = ChildClass.class.getMethod("bar");
System.out.println(Arrays.toString(bar.getAnnotations()));
// bar方法未被子类重写,从父类继承到了原本注解
Field field = ChildClass.class.getField("field");
System.out.println(Arrays.toString(field.getAnnotations()));
// 同上
}
}
5)@Repeatable
元注解Repeatable是JDK8开始引入的一个特别有意思的注解。Repeatable指示它修饰的注解是可重复的。Repeatable的值是指示这个可重复注解的容器注解类型,容器类型强制包含一个名为value()
,返回类型为可重复注解的数组。
通常情况下,一个程序元素,只能使用同一个注解修饰一次,否者会发生编译时错误。Repeatable允许其修饰的注解,可多次作用于同一个程序元素。
Repeatable较为抽象,下面通过示例讲解:
public class RepeatableTest {
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(DescList.class)
@interface Desc {
String value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface DescList {
Desc[] value(); // 必须包含返回类型为可重复注解类型的数组,名为value的方法
Class<?> c() default String.class;
}
@Desc("foo")
@Desc("foo1")
public static void foo() {
}
@DescList(value = {@Desc("bar"), @Desc("bar1")})
public static void bar() {
}
public static void main(String[] args) throws NoSuchMethodException {
Method foo = RepeatableTest.class.getMethod("foo");
System.out.println("get annotations:" + Arrays.toString(foo.getAnnotations()));
System.out.println("by annotation type: " + Arrays.toString(foo.getAnnotationsByType(Desc.class)));
System.out.println("by annotation type: " + Arrays.toString(foo.getAnnotationsByType(DescList.class)));
Method bar = RepeatableTest.class.getMethod("bar");
System.out.println("get annotations:" + Arrays.toString(bar.getAnnotations()));
System.out.println("by annotation type: " + Arrays.toString(bar.getAnnotationsByType(Desc.class)));
System.out.println("by annotation type: " + Arrays.toString(bar.getAnnotationsByType(DescList.class)));
}
}
示例中,foo和bar方法使用了不同的方式,达到了重复使用@Desc
修饰的效果。在此也可以一窥AnnotatedElement的getAnnotation
系列方法,与getAnnotationsByType
系列方法的区别。
在getAnnotation下,得到的注解类型都是容器注解类型;
在getAnnotationsByType下,得到的注解类型是指定的注解类型。
六、总结
总结如下:
- java注解是JDK对元数据的支持,注解本身不会对程序产生影响,使用APT工具解析注解,可以对程序施加影响。
- 接口
java.lang.annotation.Annotation
与接口java.lang.reflect.AnnotatedElement
是JDK注解的两大基石,前者是所有注解的隐式父接口,后者是APT工具反射读取注解的关键所在。 - JDK提供几个基本元注解为自定义注解提供支持。
网友评论