前文地址:https://www.cnblogs.com/tera/p/13267630.html

本系列文章主要是博主在学习spring aop的过程中了解到其使用了java动态代理,本着究根问底的态度,于是对java动态代理的本质原理做了一些研究,于是便有了这个系列的文章

接上文,我们需要了解class字节码的结构,才能更好地理解后面的代码,这里我直接引用jvm文档中的内容

jvm文档地址:https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html
下面对字节码的结构简单地做了个说明,大部分都是顾名思义

  1. ClassFile {
  2. u4 magic;//固定的开头,值为0xCAFEBABE
  3. u2 minor_version;//版本号,用来标记class的版本
  4. u2 major_version;//版本号,用来标记class的版本
  5. u2 constant_pool_count;//静态池大小,是静态池对象数量+1
  6. cp_info constant_pool[constant_pool_count-1];//静态池对象,有效索引是1 ~ count-1
  7. u2 access_flags;//public、final等描述
  8. u2 this_class;//当前类的信息
  9. u2 super_class;//父类的信息
  10. u2 interfaces_count;//接口数量
  11. u2 interfaces[interfaces_count];//接口对象
  12. u2 fields_count;//字段数量
  13. field_info fields[fields_count];//字段对象
  14. u2 methods_count;//方法数量
  15. method_info methods[methods_count];//方法对象
  16. u2 attributes_count;//属性数量
  17. attribute_info attributes[attributes_count];//属性对象
  18. }

为了不成为一篇枯燥的文档翻译,并且尽快进入Proxy的源码,这里并不会对每一个部分做特别详细的说明,以把握整体为主

回想上篇文章最后,源码我们看到了这里

  1. ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
  2. final byte[] var4 = var3.generateClassFile();

接下去我们就可以进入generateClassFile()方法了

把握整体,我们先跳过一部分细节代码,先看下面这部分(这里我做了一个可读性的变量名修改)

注意对照着Class的字节结构来看

最终输出的字节流

  1. ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
  2. DataOutputStream data = new DataOutputStream(byteStream);

写入固定开头magic,这里-889275714就是对应0xCAFEBABE

  1. data.writeInt(-889275714);

写入版本号

  1. data.writeShort(0);//minor_version
  1. data.writeShort(49);//major_version

写入常量池,这里cp就是指constant pool

  1. this.cp.write(data);

这里我们需要进入cp的write方法看一下,也先不要纠结Entry的细节,我们还是先把握整体

  1. public void write(OutputStream var1) throws IOException {
    DataOutputStream var2 = new DataOutputStream(var1);
    /**
    * 这里写入cp的大小,注意size()+1,可以和之前Class结构中的constant_pool_count对应
    */
    var2.writeShort(this.pool.size() + 1);
    Iterator var3 = this.pool.iterator();
    /**
    * 遍历cp中的对象,写入详细信息,对应Class结构中的cp_info
    */
    while(var3.hasNext()) {
    ProxyGenerator.ConstantPool.Entry var4 = (ProxyGenerator.ConstantPool.Entry)var3.next();
    var4.write(var2);
    }
    }

接着我们回到外层方法,继续往下看

写入access_flag

  1. data.writeShort(this.accessFlags);

写入当前类的信息

  1. data.writeShort.writeShort(this.cp.getClass(dotToSlash(this.className)));

写入父类的信息(回想类的属性第一条,继承了Proxy类)

  1. data.writeShort.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));

写入接口数量

  1. data.writeShort.writeShort(this.interfaces.length);

遍历接口,写入接口信息

  1. Class[] interfaces = this.interfaces;
  2. int interfaceLength = interfaces.length;
  3. for (int i = 0; i < interfaceLength; ++i) {
  4. Class intf = interfaces[i];
  5. data.writeShort(this.cp.getClass(dotToSlash(intf.getName())));
  6. }

写入字段数量

  1. data.writeShort(this.fields.size());

遍历字段,写入字段信息

  1. fieldInerator = this.fields.iterator();
  2. while(fieldInerator.hasNext()) {
  3. ProxyGenerator.FieldInfo fieldInfo = (ProxyGenerator.FieldInfo) fieldInerator.next();
  4. fieldInfo.write(data);
  5. }

写入方法数量

  1. data.writeShort(this.methods.size());

遍历方法,写入方法信息

  1. methodIterator = this.methods.iterator();
  2. while(methodIterator.hasNext()) {
  3. ProxyGenerator.MethodInfo methodInfo = (ProxyGenerator.MethodInfo) methodIterator.next();
  4. methodInfo.write(data);
  5. }

因为该类没有特别的attribute,因此attribute数量直接写0

  1. data.writeShort(0);

正和之前的类结构完全一一对应,此时我们对proxy所做的事情就有了一个整体的把握


了解了整体之后,下面再深入介绍一下字节码中部分对象的具体格式,为后面进一步看Proxy的源码做一些准备
为了更好地理解下面的内容,我们先定义一个简单的类Test.java

  1. public class Test implements TestInt {
  1. private int field = 1;
  2.  
  3. public int add(int a, int b) {
    return a + b;
    }
    }
  4.  
  5. interface TestInt {
    }

生成.class文件

  1. javac Test.java

查看.class文件

  1. javap -v Test.class

得到结果

  1. Classfile /Users/tianjiyuan/Documents/jvm/Test.class
  2. Last modified 2020-7-3; size 292 bytes
  3. MD5 checksum 1afecf9ea44088238bc8aa9804b28208
  4. Compiled from "Test.java"
  5. public class Test implements TestInt
  6. minor version: 0
  7. major version: 52
  8. flags: ACC_PUBLIC, ACC_SUPER
  9. Constant pool:
  10. #1 = Methodref #4.#16 // java/lang/Object."<init>":()V
  11. #2 = Fieldref #3.#17 // Test.field:I
  12. #3 = Class #18 // Test
  13. #4 = Class #19 // java/lang/Object
  14. #5 = Class #20 // TestInt
  15. #6 = Utf8 field
  16. #7 = Utf8 I
  17. #8 = Utf8 <init>
  18. #9 = Utf8 ()V
  19. #10 = Utf8 Code
  20. #11 = Utf8 LineNumberTable
  21. #12 = Utf8 add
  22. #13 = Utf8 (II)I
  23. #14 = Utf8 SourceFile
  24. #15 = Utf8 Test.java
  25. #16 = NameAndType #8:#9 // "<init>":()V
  26. #17 = NameAndType #6:#7 // field:I
  27. #18 = Utf8 Test
  28. #19 = Utf8 java/lang/Object
  29. #20 = Utf8 TestInt
  30. {
  31. public Test();
  32. descriptor: ()V
  33. flags: ACC_PUBLIC
  34. Code:
  35. stack=2, locals=1, args_size=1
  36. 0: aload_0
  37. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  38. 4: aload_0
  39. 5: iconst_1
  40. 6: putfield #2 // Field field:I
  41. 9: return
  42. LineNumberTable:
  43. line 1: 0
  44. line 2: 4
  45.  
  46. public int add(int, int);
  47. descriptor: (II)I
  48. flags: ACC_PUBLIC
  49. Code:
  50. stack=2, locals=3, args_size=3
  51. 0: iload_1
  52. 1: iload_2
  53. 2: iadd
  54. 3: ireturn
  55. LineNumberTable:
  56. line 5: 0
  57. }
  58. SourceFile: "Test.java"

我们先看下面这3个部分正对应minor_version,major_version,access_flags

  1. minor version: 0
  2. major version: 52
  3. flags: ACC_PUBLIC, ACC_SUPER

接着看Constant Pool

  1. Constant pool:
  2. #1 = Methodref #4.#16 // java/lang/Object."<init>":()V
  3. #2 = Fieldref #3.#17 // Test.field:I
  4. #3 = Class #18 // Test
  5. ...
  6. #6 = Utf8 field
    ...
  7. #16 = NameAndType #8:#9 // "<init>":()V

其中有如下几种类型

Methodref :方法的引用
Fieldref:字段的引用
Class :类的引用
Utf8 :字符串的引用
NameAndType 类型的描述

下面一个一个介绍

Class结构

  1. CONSTANT_Class_info {
  2. u1 tag;
  3. u2 name_index;
  4. }

表示一个类的引用
tag:表示自身的编号
name_index:必须是常量池中的有效索引,用来表示类的名字
例如

  1. #3 = Class #18 // Test

tag = 3,表示自身索引为3

name_index = 18,表示名字的索引是18

此时我们查看#18,即这个类的名字是Test

  1. #18 = Utf8 Test

Field、Method、Interface结构

文档中这3者是放在一起的

  1. CONSTANT_Fieldref_info {
  2. u1 tag;
  3. u2 class_index;
  4. u2 name_and_type_index;
  5. }
  6.  
  7. CONSTANT_Methodref_info {
  8. u1 tag;
  9. u2 class_index;
  10. u2 name_and_type_index;
  11. }
  12.  
  13. CONSTANT_InterfaceMethodref_info {
  14. u1 tag;
  15. u2 class_index;
  16. u2 name_and_type_index;
  17. }

表示一个字段、方法、接口方法的引用

tag:表示自身编号
class_index:表示常量池中的一个有效索引
  如果是Methodref_info必须是Class类型的
  如果是InterfaceMethodref_info则必须是一个Interface
  如果是Fieldref_info则可以是Class或者是Interface
name_and_type_index:表示常量池中的一个有效索引(表示方法的名字、返回类型、参数)
  如果是Fieldref_info,则必须是一个对字段的描述,否则必须是一个对方法的描述

例如

  1. #1 = Methodref #4.#16 // java/lang/Object."<init>":()V

tag = 1,表示自身索引为1
class_index = 4,表示类型是索引为4的类
name_and_type_index = 16,表示方法的描述为索引16

查看4和16

  1. #4 = Class #19 // java/lang/Object
  2. #16 = NameAndType #8:#9 // "<init>":()V

即表示这个方法是Object类中的构造函数

NameAndType结构

  1. CONSTANT_NameAndType_info {
  2. u1 tag;
  3. u2 name_index;
  4. u2 descriptor_index;
  5. }

用来表示一个方法或者字段,其中不包括该字段或方法所属的类

tag:表示自身编号
name_index:常量池中的一个有效索引,必须是Utf8类型(表示方法或字段的名字)
descriptor_index:常量池中的一个有效索引,必须是Utf8类型(表示方法的返回类型和参数)

例如

  1. #16 = NameAndType #8:#9 // "<init>":()V

tag = 16
name_index = 8
descriptor_index = 9

查看索引8和9

  1. #8 = Utf8 <init>
  2. #9 = Utf8 ()V

方法名为<init>表示构造函数,参数0个,返回值为void

UTF-8结构

  1. CONSTANT_Utf8_info {
  2. u1 tag;
  3. u2 length;
  4. u1 bytes[length];
  5. }

表示一个字符串常量

tag:表示自身编号
length:表示byte数组的长度
bytes[length]:表示具体数据内容
这个部分其实还有很多细节,不过这里就不展开了,有兴趣的可以自行查看jvm文档,后面会有文章详细分析

常量池的内容就介绍到这里,接下去我们还需要看下类结构的其他成员

this_class,必须是一个有效的常量池索引,需要是CONSTANT_Class_info类型的
super_class,必须是一个有效的常量池索引,需要是CONSTANT_Class_info类型的或者为0,表示没有父类
interfaces_count,接口数量,一个int值
interfaces[],接口数组,数组中的值必须是一个常量池的有效索引,需要是CONSTANT_Class_info类型
fields_count,字段数量

fields[],字段数组,数组中的值都是field_info结构

  1. field_info {
  2. u2 access_flags;//access_flag
  3. u2 name_index;//常量池中的一个有效索引,必须是Utf8类型(表示方法或字段的名字)
  4. u2 descriptor_index;//常量池中的一个有效索引,必须是Utf8类型(表示字段的描述)
  5. u2 attributes_count;//跳过,本文不涉及
  6. attribute_info attributes[attributes_count];//跳过,本文不涉及
  7. }

methods_count,方法数量
methods[],方法数组,结构如下

  1. method_info {
  2. u2 access_flags;//access_flag
  3. u2 name_index;//常量池中的一个有效索引,必须是Utf8类型(表示方法或字段的名字)
  4. u2 descriptor_index;//常量池中的一个有效索引,必须是Utf8类型(表示方法的描述)
  5. u2 attributes_count;//属性数量
  6. attribute_info attributes[attributes_count];//属性的具体内容
  7. }

class文件的一些基本结构就介绍到这里,下一篇文章中会继续结合Proxy的源码,进一步深入了解class的各种结构究竟是怎么被构造的

class文件的基本结构及proxy源码分析二的更多相关文章

  1. JAVA设计模式-动态代理(Proxy)源码分析

    在文章:JAVA设计模式-动态代理(Proxy)示例及说明中,为动态代理设计模式举了一个小小的例子,那么这篇文章就来分析一下源码的实现. 一,Proxy.newProxyInstance方法 @Cal ...

  2. Vue源码分析(二) : Vue实例挂载

    Vue源码分析(二) : Vue实例挂载 author: @TiffanysBear 实例挂载主要是 $mount 方法的实现,在 src/platforms/web/entry-runtime-wi ...

  3. 十、Spring之BeanFactory源码分析(二)

    Spring之BeanFactory源码分析(二) 前言 在前面我们简单的分析了BeanFactory的结构,ListableBeanFactory,HierarchicalBeanFactory,A ...

  4. Tomcat源码分析二:先看看Tomcat的整体架构

    Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...

  5. 多线程之美8一 AbstractQueuedSynchronizer源码分析<二>

    目录 AQS的源码分析 该篇主要分析AQS的ConditionObject,是AQS的内部类,实现等待通知机制. 1.条件队列 条件队列与AQS中的同步队列有所不同,结构图如下: 两者区别: 1.链表 ...

  6. Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题

    4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...

  7. 框架-springmvc源码分析(二)

    框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...

  8. 如何分析SpringBoot源码模块及结构?--SpringBoot源码(二)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 如何搭建自己的SpringBoot源码调试环境?--SpringBoot源码(一). 前面搭建好了自己本地的S ...

  9. 5.2 Spring5源码--Spring AOP源码分析二

    目标: 1. 什么是AOP, 什么是AspectJ 2. 什么是Spring AOP 3. Spring AOP注解版实现原理 4. Spring AOP切面原理解析 一. 认识AOP及其使用 详见博 ...

随机推荐

  1. control+Z的逆 control+Y

    接触过电脑的朋友一定知道control键加Z可以在大多时候撤销我们前一步的操作,相当于计算机系统里最“广谱”的后悔药. 然而,你有没有在编辑文本的时候却因为撤销的操作而后悔?输入文本之后撤销,发现少了 ...

  2. 【Laravel】 常用的artisan命令

    全局篇 查看artisan命令php artisanphp artisan list 查看某个帮助命令php artisan help make:model 查看laravel版本php artisa ...

  3. vc6.0转vs2012的一些错误与解决方法

    1>------ 已启动生成: 项目: NMW210, 配置: Debug Win32 ------ abs_position = fabs((float)posiTemp1 - (float) ...

  4. 安卓开发,Service 服务

    Service 服务 是一种应用组件,可长时间后台运行,不提供用户界面.如音乐播放器/下载程序.不能自己运行. 使用Service的方式: (一)startService(): 调用者和服务之间没有联 ...

  5. Merge,Rebase,Cherry-Pick 一文解惑

    代码合并在日常开发中是较为常见的场景,采用合适的合并方式,可以起到事半功倍的效果.对应在 Git 中合并的方式主要有三个,Merge,Rebase,Cherry-Pick. 开始部分会首先介绍一下这三 ...

  6. JavaScript DOM 注册事件

    一个HTML是一个DOM树,每一个节点都是DOM对象,整个HTML其实也是一个DOM对象,根节点是<html>; 在HTML页面初始化的时候,JavaScript会自动帮DOM对象注册消息 ...

  7. Java开发中的23种设计模式详解(收藏-转)

    设计模式(Design Patterns) ——可复用面向对象软件的基础 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了 ...

  8. skywalking中文文档

    https://github.com/apache/skywalking/blob/v5.0.0-alpha/docs/README_ZH.md 大家可以前往如下地址下载我们的发布包: l  Apac ...

  9. 重学 Java 设计模式:实战迭代器模式「模拟公司组织架构树结构关系,深度迭代遍历人员信息输出场景」

    作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 相信相信的力量! 从懵懂的少年,到拿起键盘,可以写一个Hell ...

  10. 实战技巧,Vue原来还可以这样写

    hookEvent,原来可以这样监听组件生命周期 1. 内部监听生命周期函数 <template> <div class="echarts"></di ...