【Java基础面经】Java 注解的底层原理

文章目录

前言

注解的本质是一个继承了Annotation的接口

Java 注解(Annotation)是一种元数据,它本身不直接影响代码逻辑,但可以被编译器、工具或框架在编译时或运行时读取并处理。

注解实际上是一个接口,它隐式地继承自 java.lang.annotation.Annotation 接口。注解中定义的方法对应注解的"属性"。这些方法没有参数,返回值类型受限(基本类型、String、Class、枚举、注解以及它们的数组)。

对于一个简单的自定义注解,使用 @interface 关键字进行实现

java 复制代码
public @interface MyAnnotation {
    String value() default "";
}

用 javap -c MyAnnotation 反编译后,会看到,

java 复制代码
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 标注一个类为例:

java 复制代码
@MyAnnotation("hello")
public class Test {}

用 javap -v Test 查看字节码,会看到类似:

java 复制代码
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 获取注解信息

java 复制代码
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 方法,最终返回属性值。

相关推荐
HAPPY酷2 小时前
PyCharm 终极避坑指南:环境选择、镜像加速与包管理
ide·python·pycharm
妙蛙种子3112 小时前
【Java设计模式 | 创建者模式】 抽象工厂模式
java·开发语言·后端·设计模式·抽象工厂模式
雄哥0072 小时前
spring 升级记录
java·后端·spring·spring升级
卓怡学长2 小时前
m320基于Java的网络音乐系统的设计与实现
java·数据库·spring·tomcat·maven
yaaakaaang2 小时前
五、原型模式
java·原型模式
如竟没有火炬2 小时前
搜索二维矩阵
数据结构·python·算法·leetcode·矩阵
chh5632 小时前
从零开始学C++--类和对象
java·开发语言·c++·学习·算法
郝学胜-神的一滴2 小时前
自动微分实战:梯度下降的迭代实现与梯度清零核心解析
人工智能·pytorch·python·深度学习·算法·机器学习
一只废狗狗狗狗狗狗狗狗狗2 小时前
c语言速通复习
c语言·开发语言