Java中的注解
什么是注解
注解也叫元数据,例如我们常见的@Override
和@Deprecated
,注解是JDK1.5版本开始引入的一个特性,用于对代码进行说明,可以对包、类、接口、字段、方法参数、局部变量等进行注解。
它主要的作用有以下四方面:
- 生成文档,通过代码里标识的元数据生成javadoc文档。
- 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
- 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
- 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例。
注解的分类
注解一般可以分为三类:
- 元注解:元注解是用于定义注解的注解,包括@Retention、@Target、@Inherited、@Documented,@Retention用于标明注解被保留的阶段,@Target用于标明注解使用的范围,@Inherited用于标明注解可继承,@Documented用于标明是否生成javadoc文档。
- JDK自带的标准注解:包括@Override、@Deprecated和@SuppressWarnings,分别用于标明重写某个方法、标明某个类或方法过时、标明要忽略的警告,用这些注解标明后编译器就会进行检查。
- 自定义注解:根据自己的需求定义注解,并可用元注解对自定义注解进行注解
注解的声明
可以按照如下形式声明一个自定义的注解
1 |
|
使用@interface语法,声明一个CustomAnnotation,然后使用元注解@Target(ElementType.FIELD)限定该注解只能用于属性值上,注解@Rentention(RetentionPolicy.RUNNTIME)限定,该注解的生存期是运行时。
@Target
还有其他几种取值,分别是:
- TYPE:标明该注解可以用于类、接口(包括注解类型)或enum声明
- FIELD:标明该注解可以用于字段(域)声明,包括enum示例
- METHOD:标明该注解可以用于方法声明
- PARAMETER:标明该注解可以用于参数声明
- CONSTRUCTOR:标明该注解可以用于构造函数声明
- LOCAL_VARIABLE:标明该注解可以用于局部变量声明
- ANNOTATION_TYPE:标明该注解可以用于局部变量声明
- PACKAGE:标明该注解可以用于包声明
- TYPE_PARAMETER:标明该注解可以用于类型参数声明
- TYPE_USE:标明该注解可以用于类型使用声明
注意:当注解未指定Target值时,该注解可以用于任何元素值上,多个值可以使用花括号包围,以逗号分隔。
@Target(value={CONSTRUCTOR,FIELD,...,METHOD})
@Retetion用来约束注解的声明周期,可以有3个取值:source(源码级别)、class(类文件级别)和runtime(运行时级别)。如果注解未定义Rentetion的时候,默认级别是CLASS。
- SOURCE:注解将会被编译器丢弃。此类型的注解信息只会保留在源码里,源码经过编译后,注解的信息会被丢弃,不会保留在编译好的class文件里。
- CLASS:注解在class文件中可用,但会在JVM丢弃。此类型的注解信息会保留在源码里和class文件里,在执行的时候不会加载到虚拟机中。
- RUNTIME:注解细腻系将在运行期(JVM)也保留,因此可以通过反射机制读取注解的信息(源码、class文件和执行的时候都有注解的信息)。
注解的原理
下面我们用这两段程序看看注解的实现原理
CustomAnnotation注解
1 |
|
测试代码
1 | public class AnnotationTest { |
语句System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
的目的是为了将运行过程中的中间类保存到磁盘上,供分析。
首先,在main方法处打个断点,看看
可以发现annotation其实是一个代理对象。
接着,在看看运行main方法之后生成的中间文件
可以看到它生成了两个中间文件,$Proxy0
和$Proxy1
因为CustomAnnotation是$Proxy1对象,我们反编译一下看看。
1 | package com.sun.proxy; |
从代码中得知,$Proxy1类实现了CustomAnnotation接口,说明它就是CustomAnnotation的动态代理类。
然后我们再反编译下CustomAnnotation的字节码
可以看到第二个红框,有一个ACC_ANNOTATION的标记,标明这是一个注解。而第一个红框,表示接口CustomAnnotation扩展了接口Annotation。所以,注解的本质就是一个继承了接口Annotation的接口。
而Annotation接口声明了以下的方法:
这些方法,正是动态代理类$Proxy1实现的方法。