美文网首页
注解的原理-类的常量池

注解的原理-类的常量池

作者: Phoenix的学习历程 | 来源:发表于2018-08-02 10:18 被阅读0次

    上次讲了注解的定义和自定义注解,
    但是留了个问题没有进一步说明,就是注解所设定的数据是存在什么地方的?
    这里需要引入一个新东西,类的常量池。

    class的结构

    对于Java新手来说这部分可能不是很友好,
    class文件是java文件编译后的字节码,对于一个class文件来说规定的结构可以理解为一张表,
    下面是class文件结构的规定,


    image

    如果第一次接触的话可以先忽略具体的各个项目,
    总的说就是Java编译后的字节码按照表的规定非常严格的以表的结构构成。

    对于我们要关注的问题"注解的数据存储在哪里"来说,
    只需要关注表里面的 constant_pool 这个部分,
    这个称作常量池的东西,保存了一系列的数据,分为四种

    • Literal,字面量
    • Symbolic References,符号引用
    • Others,其他
    • constant pool,常量
      注解的数据就存在 constant pool这里。

    常量池

    用比较直观的方式来理解常量池的话,最简单便捷的方式就是看字节码,
    javap 是一个查看字节码的命令,之前多次用过它来理解Java的字节码,
    这里我们用 javap来看常量池的话可以执行

    javap -p Student.Class

    输出

    Classfile /Users/zhenghui/StudioProjects/MyProject/AnnotationDemo/Student.class
      Last modified 2018-7-28; size 925 bytes
      MD5 checksum 6ec1e13999388ff134142418179a88d8
      Compiled from "Student.java"
    public class Student
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
    #1 = Methodref          #15.#34        // java/lang/Object."<init>":()V
       #2 = Fieldref           #4.#35         // Student.name:Ljava/lang/String;
       #3 = Fieldref           #4.#36         // Student.age:I
       #4 = Class              #37            // Student
       #5 = Methodref          #4.#38         // Student."<init>":(Ljava/lang/String;I)V
       #6 = Fieldref           #39.#40        // java/lang/System.out:Ljava/io/PrintStream;
       #7 = Class              #41            // java/lang/StringBuilder
       #8 = Methodref          #7.#34         // java/lang/StringBuilder."<init>":()V
       #9 = String             #42            // name:
      #10 = Methodref          #7.#43         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      #11 = String             #44            //  age:
      #12 = Methodref          #7.#45         // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      #13 = Methodref          #7.#46         // java/lang/StringBuilder.toString:()Ljava/lang/String;
      #14 = Methodref          #47.#48        // java/io/PrintStream.println:(Ljava/lang/String;)V
      #15 = Class              #49            // java/lang/Object
      #16 = Utf8               name
      #17 = Utf8               Ljava/lang/String;
      #18 = Utf8               age
      #19 = Utf8               I
      #20 = Utf8               <init>
      #21 = Utf8               (Ljava/lang/String;I)V
      #22 = Utf8               Code
      #23 = Utf8               LineNumberTable
      #24 = Utf8               createStudent
      #25 = Utf8               (Ljava/lang/String;I)LStudent;
      #26 = Utf8               RuntimeVisibleAnnotations
      #27 = Utf8               LSexual;
      #28 = Utf8               value
      #29 = Utf8               male
      #30 = Utf8               displayInfo
      .... //省略部分内容
      {
      java.lang.String name;
        descriptor: Ljava/lang/String;
        flags:
    
      int age;
        descriptor: I
        flags:
        .... //省略部分内容
        public void displayInfo();
          descriptor: ()V
          flags: ACC_PUBLIC
          Code:
            stack=3, locals=1, args_size=1
               0: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
               3: new           #7                  // class java/lang/StringBuilder
               6: dup
               7: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
              10: ldc           #9                  // String name:
              12: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
              15: aload_0
              16: getfield      #2                  // Field name:Ljava/lang/String;
              19: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
              22: ldc           #11                 // String  age:
              24: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
              27: aload_0
              28: getfield      #3                  // Field age:I
              31: invokevirtual #12                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
              34: invokevirtual #13                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
              37: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
              40: return
            LineNumberTable:
              line 16: 0
              line 17: 40
      }
    

    内容比较长,就只截取其中一部分。
    感兴趣的话可以自己写个简单的类编译一下,然后查看完整的字节码,跟上面的大同小异。
    上面的字节码是从上一个文章中的例子里编译来的,
    在 Constant pool 这部分保存了我们注解的内容,关注
    #24 - #29 的内容,
    这里就是注解所携带的信息存放的地方了。
    这里用了一个RuntimeVisibleAnnotations作为标注,对应注解中的RUNTIME标记。
    可能跟你一开始理解的不同,现在应该明白,注解的信息并不保存在方法的执行栈中,而是在一个叫常量池的地方独立保存起来。

    关于class的文件结构可以说很长的篇幅,
    比如魔数,比如最大最小版本,
    可能做过gradle插件的同学会遇到"major.minor version 52.0"这么个问题,
    原因是在低版本的java上使用了高版本的插件导致的,这个 version 52就定义在class文件的 major version 字段。
    如果打算进阶资深Java开发的话可以仔细弄清楚这一块的知识哦。


    公众号.jpg

    相关文章

      网友评论

          本文标题:注解的原理-类的常量池

          本文链接:https://www.haomeiwen.com/subject/yuljvftx.html