这篇内容是上一篇[动态代理三部曲:上] - 动态代理是如何"坑掉了"我4500块钱的补充,进一步分析篇。 建议两者结合食用,醇香绵软,入口即化。java
好了,不扯淡了,开始...面试
这里为啥是2开头呢?由于上篇文章是1数组
这部份内容不知道各位小伙伴是怎么感受的。最开始学习的时候,我是一头雾水,不知道如何下手。当一步步结合反射、JVM内存模型,类加载机制后。再回过头来就会发现一块儿豁然开朗。编辑器
此篇内容的开始,让咱们根据咱们demo中所用的类:RentHouseProcessorHandler来分析这个问题。
若是咱们用十六进制编辑器(好比:Sublime)打开这个RentHouseProcessorHandler.class文件: 工具
说实话这一行行的文字,最开始我是拒绝的。哦,上帝,为何要让我看这些鬼东西...其实若是咱们静下心来,想当年高中时代学习数学,物理公式那样去认真的对待它。就会发现它不过就是:一堆人为赋予了特别含义的符号而已。学习
在咱们准备读懂这些十六进制文字时,先让咱们看一幅《Java虚拟机规范(Java SE 7)》对class文件的定义: spa
上图的内容,其实很是的通俗易懂,不要由于是不常见的英文就抵触它们。让咱们尝试着去翻译它们: 一、魔数;二、次版本号;三、主版本号;四、常量池数量;五、常量池;六、权限标识;七、此类;八、父类;九、接口数目;十、接口;十一、变量数目;十二、变量;1三、方法数目;1四、方法....翻译
实际上是不是发现了什么,这不是就一个类应该存在的东西么?没错啊,Class文件的结构就是固定了咱们编写的Class类所存放的规则而已。最开始的我,觉得是深奥,没敢去了解他们。当我踌躇满志,鼓足勇气去准备好好大干一场的时候,才发现它太简单了...就是一些规则,仅此而已。设计
虽然只是一些规则,但规则之中,总会有一些特别须要咱们去注意的地方:好比cp_info这个类型。在《深刻理解Java虚拟机》中,做者把以_info结尾的类型称之为“表”。这里让咱们也沿用这种表达方式。说白了,它就是拥有多级关系的类型。3d
cp_info 表示常量池(常量池:首先它和方法区中运行时常量池不是同一个内容。这里的常量池存放了字面量和符号引用)。
符号引用:
这里符号引用的做用,咱们想先一个问题。CPU执行程序的时候,其实是去寻找对应指令的内存地址。可是咱们的Class文件是先被编译出来的,可是此时尚未被JVM加载到内存,因此确定是不可能存在内存地址这一说的。所以咱们的Class文件须要一些标识,让JVM加载内容的时候从常量池中获取到对应的符号引用,而后在映射到具体的内存地址上。
放到常量池的中数据项在《Java虚拟机规范(Java SE 7)》中一共有14个常量,每一种常量都是一个“表”,而且每种常量都用一个公共的tag来表示是哪一种类型的常量。具体内容以下图:
这里让咱们先解读一下这个常量池:让咱们跳过u4的魔数、u2的此版本、u2的主版本。直接来看constant_pool_count。跳过对应的内容,那么咱们的constant_pool_count就对应十六进制的20,对应十进制的32,也就是说常量池中有32个内容?实际不是的,由于设计者将第0个位置空出来另作打算。因此咱们的常量池只有31个内容。
咱们能够经过javap命令证明这个问题。
接下来的一个字节:0a,翻译成十进制就是10,对应咱们表中的CONSTANT_Methodref_info,而接下来的四个字节。分别表明索引3,20。这里的索引表明什么意思呢?注意理解下图中标红的地方:
接下来就不逐个解读这些内容了,由于它就是一个对应的过程。若是小伙伴们有兴趣能够自行去尝试解读一番哦。推荐一个工具JavaClassViewer,能够比较方便的查看这些内容:
常量池结束以后,即是咱们正常的变量,方法的信息。而这里咱们须要了解一个全新的概念:描述符。 对于咱们来讲一个变量、方法在java源码里是什么样子咱们很清楚。可是它们在class文件里是什么样子的呢?这个样子其实就被称之为:描述符。
上文谈动态代理的时候,咱们了解到了ProxyGenerator.generateProxyClass(proxyName,interfaces, accessFlags);
方法中,经过:
dout.writeInt(0xCAFEBABE);
MethodInfo minfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V",ACC_PUBLIC);
复制代码
等方法,构建了咱们的$Proxy0所须要的class结构,是否是和咱们javap出来的内容很相似?接下来让咱们走进描述符。
通过上述内容的铺垫,0xCAFEBABE是什么意思,应该无需多言了。而<init>
是构造方法的意思。再加上(Ljava/lang/reflect/InvocationHandler;)V
以及ACC_PUBLIC就能够表示为InvocationHandler的public的构造方法,其中V表示无返回值。
<init>
:对象构造器方法。<clinit>
:类构造器方法。这里的内容就被称之为方法的描述符,让咱们简单的看一些图,加深这方面内容。
基本类型和void在描述符中都有一个大写字符和他们对应; 那么引用类型的描述符,又是什么样子的呢?
例以下图中的:Ljava/lang/Object;就是表示这是一个Object类型。
而方法描述符的规则也很简单,上图中,总结出来就是一句话:
例如上图中的:int[] m(int i,String s)转换为描述符:(ILjava/lang/String;)[I
不知道截了这么多图,你们对描述符有没有比较明确的认识。说白了咱们咱们ProxyGenerator.generateProxyClass(proxyName,interfaces, accessFlags);
中所write的内容就是具体方法的描述符。
而后经过DataOutputStream转成byte数组,那么就是咱们Class文件所固定的内容了。所以,此时咱们的Class文件就已经构建完毕,接下来所须要的就是将其加载到内存中,供咱们使用。
到这准备结束class文件结构的内容。不知道小伙伴们是否有收获。由于篇幅是在有限,有些内容又不是一句话俩句话能够描述清楚的。因此有些内容一带而过,实在抱歉。具体细节内容,你们能够参考《深刻理解Java虚拟机》。
但愿你们能够谅解。