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

张开发
2026/5/4 22:40:49 15 分钟阅读
【Java基础面经】Java 注解的底层原理
文章目录前言元注解Target注解Retention注解注解在字节码中的存储编译阶段类加载阶段运行时注解反射API前言注解的本质是一个继承了Annotation的接口Java 注解Annotation是一种元数据它本身不直接影响代码逻辑但可以被编译器、工具或框架在编译时或运行时读取并处理。注解实际上是一个接口它隐式地继承自 java.lang.annotation.Annotation 接口。注解中定义的方法对应注解的“属性”。这些方法没有参数返回值类型受限基本类型、String、Class、枚举、注解以及它们的数组。对于一个简单的自定义注解使用 interface 关键字进行实现publicinterfaceMyAnnotation{Stringvalue()default;}用 javap -c MyAnnotation 反编译后会看到publicinterfaceMyAnnotationextendsjava.lang.annotation.Annotation{publicabstractjava.lang.Stringvalue();}元注解Java 提供了几个元注解即注解的注解用来定义注解的生命周期、使用位置等。元注解作用Target指定注解可以用在哪些地方类、方法、字段、参数等。Retention指定注解保留到哪个阶段源码、字节码、运行时。Documented是否包含在 Javadoc 中。Inherited是否允许子类继承父类的注解。RepeatableJava 8允许同一位置重复使用同一个注解。Target注解Target 接收一个 ElementType 数组表示该注解可以出现在哪些地方。常用的 ElementType 枚举值包括ElementType说明TYPE类、接口、枚举、注解类型FIELD成员变量包括枚举常量METHOD方法PARAMETER方法参数CONSTRUCTOR构造方法LOCAL_VARIABLE局部变量ANNOTATION_TYPE注解类型用于元注解PACKAGE包TYPE_PARAMETERJava 8类型参数如 class MyClass 中的 TTYPE_USEJava 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)publicclassTest{}用 javap -v Test 查看字节码会看到类似RuntimeVisibleAnnotations:0:#10(#11s#12)#10Utf8LMyAnnotation;#11Utf8value#12Utf8helloRuntimeVisibleAnnotations 是字节码中的一种属性表示在运行时可见的注解列表。每个注解被编码为注解类型 属性名 属性值。例子中的类型为 LMyAnnotation属性名是value属性值是hello。类加载阶段JVM 在加载类时会读取 .class 文件中的这些属性将注解信息解析并存储到类的元数据中方法区的 Annotation 数据结构。但对于 Retention(CLASS) 的注解在类加载后这些信息会被丢弃而对于 RUNTIME 的注解会保留在运行时。运行时注解反射APIJVM自动生成动态代理对象来实现注解接口可通过代理对象的 invoke 方法实现对注解中属性对应值的返回自定义注解中的 value 的对应属性值当注解的 Retention 为 RUNTIME 时才可以通过反射 API 获取注解信息Annotation[]annotationsTest.class.getAnnotations();MyAnnotationmyAnnoTest.class.getAnnotation(MyAnnotation.class);StringvaluemyAnno.value();Class.getAnnotation(Class) 最终会调用 JVM 内部的 native 方法遍历类的 RuntimeVisibleAnnotations 表。对于找到的每个注解JVM 会动态生成一个代理对象java.lang.reflect.Proxy来实现该注解接口。这个代理对象内部有一个 AnnotationInvocationHandler在 sun.reflect.annotation 包下它维护了一个 MapString, Object里面存放了解析出的注解属性名和值例如 {“value”: “hello”}代理对象的 invoke 方法会根据注解属性名返回对应的值这些值存储在字节码中由 JVM 解析后保存。因此myAnno.value() 实际执行的是代理对象的方法调用而不是某个实现类的实例方法。MyAnnotation(“hello”) 本质上是 MyAnnotation(value “hello”)相当于给value赋值为 “hello”调用myAnno.value()本质上调用代理对象的 invoke 方法最终返回属性值。

更多文章