前言
注解是 java 5 引入的一种元数据机制,可以在代码中添加信息,这些信息可以被编译器、开发工具或运行时环境使用。
注解的本质是一个继承了annotation的接口
java 注解(annotation)是一种元数据,它本身不直接影响代码逻辑,但可以被编译器、工具或框架在编译时或运行时读取并处理。
注解实际上是一个接口,它隐式地继承自 java.lang.annotation.annotation 接口。注解中定义的方法对应注解的“属性”。这些方法没有参数,返回值类型受限(基本类型、string、class、枚举、注解以及它们的数组)。
对于一个简单的自定义注解,使用 @interface 关键字进行实现
public @interface myannotation {
string value() default "";
}
用 javap -c myannotation 反编译后,会看到,
public interface myannotation extends java.lang.annotation.annotation {
public abstract java.lang.string value();
}
元注解
java 提供了几个元注解(即注解的注解),用来定义注解的生命周期、使用位置等。
| 元注解 | 作用 |
|---|---|
| @target | 指定注解可以用在哪些地方(类、方法、字段、参数等)。 |
| @retention | 指定注解保留到哪个阶段(源码、字节码、运行时)。 |
| @documented | 是否包含在 javadoc 中。 |
| @inherited | 是否允许子类继承父类的注解。 |
| @repeatable(java 8) | 允许同一位置重复使用同一个注解。 |
@target注解
@target 接收一个 elementtype 数组,表示该注解可以出现在哪些地方。常用的 elementtype 枚举值包括:
| elementtype | 说明 |
|---|---|
| type | 类、接口、枚举、注解类型 |
| field | 成员变量(包括枚举常量) |
| method | 方法 |
| parameter | 方法参数 |
| constructor | 构造方法 |
| local_variable | 局部变量 |
| annotation_type | 注解类型(用于元注解) |
| package | 包 |
| type_parameter(java 8) | 类型参数(如 class myclass 中的 t) |
| type_use(java 8) | 类型使用处(如 new @nonnull string()) |
@retention注解
- source:注解只保留在源代码中,编译时被丢弃(如 @override、@suppresswarnings)。这类注解仅用于编译期检查,不会进入 .class 文件。
- class(默认):注解保留在 .class 文件中,但加载到 jvm 时会被忽略(即运行时无法通过反射获取)。这种级别通常用于字节码操作工具(如 lombok)。
- runtime:注解保留在 .class 文件中,并且在运行时可以被 jvm 读取(通过反射)。这是框架最常用的级别(如 @autowired、@requestmapping)。
注解在字节码中的存储
编译阶段
当编译器处理带有注解的代码时,会根据 @retention 决定是否将注解信息写入 .class 文件。对于 runtime 或 class 级别的注解,编译器会在字节码中添加专门的属性表(attribute)。
以 @myannotation 标注一个类为例:
@myannotation("hello")
public class test {}
用 javap -v test 查看字节码,会看到类似:
runtimevisibleannotations:
0: #10(#11=s#12)
#10 = utf8 "lmyannotation;"
#11 = utf8 "value"
#12 = utf8 "hello"
runtimevisibleannotations 是字节码中的一种属性,表示在运行时可见的注解列表。每个注解被编码为:注解类型 + 属性名 + 属性值。例子中的类型为 lmyannotation;,属性名是value,属性值是hello。
类加载阶段
jvm 在加载类时,会读取 .class 文件中的这些属性,将注解信息解析并存储到类的元数据中(方法区的 annotation 数据结构)。但对于 @retention(class) 的注解,在类加载后这些信息会被丢弃;而对于 runtime 的注解,会保留在运行时。
运行时注解(反射api)
jvm自动生成动态代理对象来实现注解接口,可通过代理对象的 invoke 方法实现对注解中属性对应值的返回(自定义注解中的 value 的对应属性值)
当注解的 @retention 为 runtime 时,才可以通过反射 api 获取注解信息
annotation[] annotations = test.class.getannotations(); myannotation myanno = test.class.getannotation(myannotation.class); string value = myanno.value();
- class.getannotation(class) 最终会调用 jvm 内部的 native 方法,遍历类的 runtimevisibleannotations 表。
- 对于找到的每个注解,jvm 会动态生成一个代理对象(java.lang.reflect.proxy)来实现该注解接口。
- 这个代理对象内部有一个 annotationinvocationhandler(在 sun.reflect.annotation 包下),它维护了一个 map<string, object>,里面存放了解析出的注解属性名和值(例如 {“value”: “hello”})
- 代理对象的 invoke 方法会根据注解属性名返回对应的值(这些值存储在字节码中,由 jvm 解析后保存)。
- 因此,myanno.value() 实际执行的是代理对象的方法调用,而不是某个实现类的实例方法。
@myannotation(“hello”) 本质上是 @myannotation(value = “hello”),相当于给value赋值为 “hello”,调用myanno.value()本质上调用代理对象的 invoke 方法,最终返回属性值。
到此这篇关于【java基础面经】java 注解的底层原理的文章就介绍到这了,更多相关java 注解原理内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论