java 注解(Annotations)的字节码详解

    技术2025-03-27  24

    注解

    一 、描述1.1 methods 方法表2.2 annotations的数据结构2.2.1 RuntimeVisibleAnnotations2.2.1.1 annotation的结构 二、字节码2.1 源码2.2 编译后的bytecode

    注解可以标注在class、field、method上,同时又可以分为编译期的注解,运行期的注解。 这篇文章会结合jvm规范3.1.5说明注解在字节码中的位置。

    一 、描述

    注解可以标注在class、interface、fileds、method、method parameters、type parameters。注解甚至可以作用在包级别package。 类似的所有的组件基本上都有一个Attribute区域,注解是一种Attribute被放置在部分。 使用method的字节码来举例

    1.1 methods 方法表

    方法表在class文件的位置

    ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; // 这里 u2 methods_count; method_info methods[methods_count]; // 结束 u2 attributes_count; attribute_info attributes[attributes_count]; }

    metod_info的的数据结构

    method_info { u2 access_flags; u2 name_index; u2 descriptor_index; // 这里 u2 attributes_count; attribute_info attributes[attributes_count]; // 结束 }

    Attribute的数据结构 属性是变长的,种类有很多。分为jvm规范中预制的,也有各厂家自定义的。但是有一个统一的格式。

    attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; }

    2.2 annotations的数据结构

    除了方法表之外,很多地方都有一个属性表。注解作为一个属性被放在这个位置。 注解属性结构细分有7种

    typedescriptorRuntimeVisibleAnnotations运行时可见RuntimeVisibleAnnotations运行时不可见RuntimeVisibleParameterAnnotations标注在参数上,运行时可见的参数型注解RuntimeInvisibleParameterAnnotations标注在参数上,运行时不可见的参数型注解RuntimeVisibleTypeAnnotations标注在例行上比如泛型,运行时可见的类型注解RuntimeInvisibleTypeAnnotations标注在例行上比如泛型,运行时不可见的类型注解AnnotationDefault注解的默认值

    2.2.1 RuntimeVisibleAnnotations

    顾名思议,就是运行期(runtime) 可见的。延伸一点,运行期可以通过函数等方式获取到的。 注解表结构

    RuntimeVisibleAnnotations_attribute { u2 attribute_name_index; u4 attribute_length; u2 num_annotations; annotation annotations[num_annotations]; }

    2.2.1.1 annotation的结构

    继上一步,理解注解的结构和注解代码定义是分不开的

    annotation 代码定义

    这个是一个完整的定义

    @Retention(RetentionPolicy.RUNTIME) //@Retention(RetentionPolicy.SOURCE) @Target(ElementType.METHOD) public @interface Info { // value int age(); String name(); String descriptor() default "simple annotation"; }

    一个注解可以继承注解(@Retention、@Target) 也需要定义字节的成员(int age();)

    使用时如下,这是一个打印了Info注解的方法

    public class TestInfo { @Info(age = 0, name = "baby") public void born() { System.out.println("human born"); } public static void main(String[] args) throws Exception { TestInfo ti = new TestInfo(); ti.born(); Method born = TestInfo.class.getMethod("born"); if (born.isAnnotationPresent(Info.class)) { Info info = born.getAnnotation(Info.class); System.out.println(info.age()); System.out.println(info.name()); System.out.println(info.descriptor()); } } }

    annotation 的结构定义 对照上面的例子可理解annotation中包含一堆 element_value 元素的

    annotation { u2 type_index; // element_value 的数量 u2 num_element_value_pairs; { u2 element_name_index; element_value value; } element_value_pairs[num_element_value_pairs]; }

    element_value 的结构定义 这里大概解释下成员,后面会有字节码的例子来分析。

    element_value { u1 tag; union { // 基本类型或者 String 的索引,值对应着常量池`constant_pool ` u2 const_value_index; // 枚举类型的键值对 { u2 type_name_index; u2 const_name_index; } enum_const_value; // 类型的信息 u2 class_info_index; //嵌套注解,注解的复合 annotation annotation_value; //数组类型的元数据 { u2 num_values; element_value values[num_values]; } array_value; } value; }

    其他的就不讲了,可以看jvm规范,重点是理解

    二、字节码

    前面讲了注解在字节码位置,和注解的数据结构。这里看一个具体的例子

    2.1 源码

    来描述学校类型的注解

    public @interface School { String type() default "highschool"; }

    描述学生信息的注解 这个注解很复杂

    复合注解(元注解+自定义注解)基本类型的成员 int复杂类型的成员 String带有默认值的成员 default基本类型数组的成员 int[]复杂类型数组的成员 String[] @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @School public @interface StudentInfo { // value int age(); String name(); String descriptor() default "simple student"; String[] course() default {"math", "language"}; int[] scores() default {70, 70}; }

    使用,born方法上使用了注解

    public class TestInfo { @StudentInfo(age = 0, name = "baby") public void born() { System.out.println("human born"); } public static void main(String[] args) throws Exception { TestInfo ti = new TestInfo(); ti.born(); Method born = TestInfo.class.getMethod("born"); if (born.isAnnotationPresent(StudentInfo.class)) { StudentInfo info = born.getAnnotation(StudentInfo.class); System.out.println(info.age()); System.out.println(info.name()); System.out.println(info.descriptor()); } } }

    2.2 编译后的bytecode

    这里仅仅是class文件。但是jvm加载后真实的运行时代码,是有和之前说的数据结构是一致的。 TestInfo.born() 可以看到将born包含注解属性

    // access flags 0x1 public born()V @Lbytecode/annotation/StudentInfo;(age=0, name="baby") L0 LINENUMBER 8 L0 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; LDC "human born" INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V L1 LINENUMBER 9 L1 RETURN L2 LOCALVARIABLE this Lbytecode/annotation/TestInfo; L0 L2 0 MAXSTACK = 2 MAXLOCALS = 1

    StudentInfo注解 这个注解的字节码,可以和上面提到的注解对照着看,发现这个文件就是依据上面的格式,从字节码中解析出来的。

    Classfile /Users/xxxxxxx/Desktop/Work/mycode/Javadetail/BYTECODE/bytecode/target/classes/bytecode/annotation/StudentInfo.class Last modified 2020-7-4; size 711 bytes MD5 checksum 6ccdff3fb70232c700d1d9766c521339 Compiled from "StudentInfo.java" public interface bytecode.annotation.StudentInfo extends java.lang.annotation.Annotation minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION Constant pool: #1 = Class #30 // bytecode/annotation/StudentInfo #2 = Class #31 // java/lang/Object #3 = Class #32 // java/lang/annotation/Annotation #4 = Utf8 age #5 = Utf8 ()I #6 = Utf8 name #7 = Utf8 ()Ljava/lang/String; #8 = Utf8 descriptor #9 = Utf8 AnnotationDefault #10 = Utf8 simple student #11 = Utf8 course #12 = Utf8 ()[Ljava/lang/String; #13 = Utf8 math #14 = Utf8 language #15 = Utf8 scores #16 = Utf8 ()[I #17 = Integer 70 #18 = Utf8 SourceFile #19 = Utf8 StudentInfo.java #20 = Utf8 RuntimeVisibleAnnotations #21 = Utf8 Ljava/lang/annotation/Retention; #22 = Utf8 value #23 = Utf8 Ljava/lang/annotation/RetentionPolicy; #24 = Utf8 RUNTIME #25 = Utf8 Ljava/lang/annotation/Target; #26 = Utf8 Ljava/lang/annotation/ElementType; #27 = Utf8 METHOD #28 = Utf8 RuntimeInvisibleAnnotations #29 = Utf8 Lbytecode/annotation/School; #30 = Utf8 bytecode/annotation/StudentInfo #31 = Utf8 java/lang/Object #32 = Utf8 java/lang/annotation/Annotation { public abstract int age(); descriptor: ()I flags: ACC_PUBLIC, ACC_ABSTRACT public abstract java.lang.String name(); descriptor: ()Ljava/lang/String; flags: ACC_PUBLIC, ACC_ABSTRACT public abstract java.lang.String descriptor(); descriptor: ()Ljava/lang/String; flags: ACC_PUBLIC, ACC_ABSTRACT AnnotationDefault: default_value: s#10 public abstract java.lang.String[] course(); descriptor: ()[Ljava/lang/String; flags: ACC_PUBLIC, ACC_ABSTRACT AnnotationDefault: default_value: [s#13,s#14] public abstract int[] scores(); descriptor: ()[I flags: ACC_PUBLIC, ACC_ABSTRACT AnnotationDefault: default_value: [I#17,I#17]} SourceFile: "StudentInfo.java" RuntimeVisibleAnnotations: 0: #21(#22=e#23.#24) 1: #25(#22=[e#26.#27]) RuntimeInvisibleAnnotations: 0: #29()
    Processed: 0.009, SQL: 9