上次讲了注解的定义和自定义注解,Java-注解入门指南,
但是留了个问题没有进一步说明,就是注解所设定的数据是存在什么地方的?明白这个问题需要引入一个新东西,类的常量池。
class的结构
对于Java新手来说这部分可能不是很友好,class文件是java文件编译后的字节码,对于一个class文件来说规定的结构可以理解为一张表,下面是class文件结构的规定,
如果第一次接触的话可以先忽略具体的各个项目,总的说就是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_SUPERConstant 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开发的话可以仔细弄清楚这一块的知识哦。
==== 今日沙雕 ====
