什么是注解
注解也叫元数据, 例如我们常见的 @Override 和 @Deprecated, 注解是 JDK1.5 版本开始引入的一个特性, 用于对代码进行说明, 可以对包, 类, 接口, 字段, 方法参数, 局部变量等进行注解. 它主要的作用有以下四方面:
生成文档, 通过代码里标识的元数据生成 javadoc 文档.
编译检查, 通过代码里标识的元数据让编译器在编译期间进行检查验证.
编译时动态处理, 编译时通过代码里标识的元数据动态处理, 例如动态生成代码.
运行时动态处理, 运行时通过代码里标识的元数据动态处理, 例如使用反射注入实例.
一般可分三类
Java 自带的标准注解
包括 @Override,@Deprecated 和 @SuppressWarnings, 分别用于标明重写某个方法, 标明某个类或方法过时, 标明要忽略的警告, 用这些注解标明后编译器就会进行检查.
元注解
元注解是用于定义注解的注解, 包括 @Retention,@Target,@Inherited,@Documented,@Retention 用于标明注解被保留的阶段
@Target 用于标明注解使用的范围
@Inherited 用于标明注解可继承
@Documented 用于标明是否生成 javadoc 文档
自定义注解, 可以根据自己的需求定义注解, 并可用元注解对自定义注解进行注解.
注解的使用
注解的使用非常简单, 只需在需要注解的地方标明某个注解即可, 例如在方法上注解:
- public class Test {
- @Override
- public String tostring() {return "override it";}
- }
例如在类上注解:
- @Deprecated
- public class Test {
- }
Java 内置的注解直接使用即可, 但很多时候我们需要自己定义一些注解, 例如常见的 spring 就用了大量的注解来管理对象之间的依赖关系. 下面看看如何定义一个自己的注解, 下面实现这样一个注解: 通过 @Test 向某类注入一个字符串, 通过 @TestMethod 向某个方法注入一个字符串.
创建 Test 注解, 声明作用于类并保留到运行时, 默认值为 default.
- @Target({ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Test {
- String value() default "default";
- }
创建 TestMethod 注解, 声明作用于方法并保留到运行时.
- @Target({ElementType.METHOD})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface TestMethod {
- String value();
- }
测试类, 运行后输出 default 和 tomcat-method 两个字符串, 因为 @Test 没有传入值, 所以输出了默认值, 而 @TestMethod 则输出了注入的字符串.
- @Test()
- public class AnnotationTest {
- @TestMethod("tomcat-method")
- public void test(){
- }
- public static void main(String[] args){
- Test t = AnnotationTest.class.getAnnotation(Test.class);
- System.out.println(t.value());
- TestMethod tm = null;
- try {
- tm = AnnotationTest.class.getDeclaredMethod("test",null).getAnnotation(TestMethod.class);
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println(tm.value());
- }
- }
注解的原理
回到上面自定义注解的例子, 对于注解 Test, 如下, 如果对 AnnotationTest 类进行注解, 则运行时可以通过
AnnotationTest.class.getAnnotation(Test.class)
获取注解声明的值, 从上面的句子就可以看出, 它是从 class 结构中获取出 Test 注解的, 所以肯定是在某个时候注解被加入到 class 结构中去了.
- @Test("test")
- public class AnnotationTest {
- public void test(){
- }
- }
从 java 源码到 class 字节码是由编译器完成的, 编译器会对 java 源码进行解析并生成 class 文件, 而注解也是在编译时由编译器进行处理, 编译器会对注解符号处理并附加到 class 结构中, 根据 jvm 规范, class 文件结构是严格有序的格式, 唯一可以附加信息到 class 结构中的方式就是保存到 class 结构的 attributes 属性中. 我们知道对于类, 字段, 方法, 在 class 结构中都有自己特定的表结构, 而且各自都有自己的属性, 而对于注解, 作用的范围也可以不同, 可以作用在类上, 也可以作用在字段或方法上, 这时编译器会对应将注解信息存放到类, 字段, 方法自己的属性上.
在我们的 AnnotationTest 类被编译后, 在对应的 AnnotationTest.class 文件中会包含一个 RuntimeVisibleAnnotations 属性, 由于这个注解是作用在类上, 所以此属性被添加到类的属性集上. 即 Test 注解的键值对 value=test 会被记录起来. 而当 JVM 加载 AnnotationTest.class 文件字节码时, 就会将 RuntimeVisibleAnnotations 属性值保存到 AnnotationTest 的 Class 对象中, 于是就可以通过 AnnotationTest.class.getAnnotation(Test.class) 获取到 Test 注解对象, 进而再通过 Test 注解对象获取到 Test 里面的属性值.
这里可能会有疑问, Test 注解对象是什么? 其实注解被编译后的本质就是一个继承 Annotation 接口的接口, 所以 @Test 其实就是 "public interface Test extends Annotation", 当我们通过 AnnotationTest.class.getAnnotation(Test.class) 调用时, JDK 会通过动态代理生成一个实现了 Test 接口的对象, 并把将 RuntimeVisibleAnnotations 属性值设置进此对象中, 此对象即为 Test 注解对象, 通过它的 value() 方法就可以获取到注解值.
Java 注解实现机制的整个过程如上面所示, 它的实现需要编译器和 JVM 一起配合.
来源: http://www.jianshu.com/p/67611a5b4492