前文地址: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
下面对字节码的结构简单地做了个说明,大部分都是顾名思义

ClassFile {
u4 magic;//固定的开头,值为0xCAFEBABE
u2 minor_version;//版本号,用来标记class的版本
u2 major_version;//版本号,用来标记class的版本
u2 constant_pool_count;//静态池大小,是静态池对象数量+1
cp_info constant_pool[constant_pool_count-1];//静态池对象,有效索引是1 ~ count-1
u2 access_flags;//public、final等描述
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];//属性对象
}

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

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

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

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

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

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

最终输出的字节流

ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
DataOutputStream data = new DataOutputStream(byteStream);

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

data.writeInt(-889275714);

写入版本号

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

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

this.cp.write(data);

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

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

data.writeShort(this.accessFlags);

写入当前类的信息

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

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

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

写入接口数量

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

遍历接口,写入接口信息

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

写入字段数量

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

遍历字段,写入字段信息

fieldInerator = this.fields.iterator();
while(fieldInerator.hasNext()) {
ProxyGenerator.FieldInfo fieldInfo = (ProxyGenerator.FieldInfo) fieldInerator.next();
fieldInfo.write(data);
}

写入方法数量

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

遍历方法,写入方法信息

methodIterator = this.methods.iterator();
while(methodIterator.hasNext()) {
ProxyGenerator.MethodInfo methodInfo = (ProxyGenerator.MethodInfo) methodIterator.next();
methodInfo.write(data);
}

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

data.writeShort(0);

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


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

public class Test implements TestInt {
    private int field = 1;

    public int add(int a, int b) {
return a + b;
}
} interface TestInt {
}

生成.class文件

javac Test.java

查看.class文件

javap -v Test.class

得到结果

Classfile /Users/tianjiyuan/Documents/jvm/Test.class
Last modified 2020-7-3; size 292 bytes
MD5 checksum 1afecf9ea44088238bc8aa9804b28208
Compiled from "Test.java"
public class Test implements TestInt
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#16 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#17 // Test.field:I
#3 = Class #18 // Test
#4 = Class #19 // java/lang/Object
#5 = Class #20 // TestInt
#6 = Utf8 field
#7 = Utf8 I
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 add
#13 = Utf8 (II)I
#14 = Utf8 SourceFile
#15 = Utf8 Test.java
#16 = NameAndType #8:#9 // "<init>":()V
#17 = NameAndType #6:#7 // field:I
#18 = Utf8 Test
#19 = Utf8 java/lang/Object
#20 = Utf8 TestInt
{
public Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field field:I
9: return
LineNumberTable:
line 1: 0
line 2: 4 public int add(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LineNumberTable:
line 5: 0
}
SourceFile: "Test.java"

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

minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER

接着看Constant Pool

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

其中有如下几种类型

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

下面一个一个介绍

Class结构

CONSTANT_Class_info {
u1 tag;
u2 name_index;
}

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

#3 = Class              #18            // Test

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

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

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

#18 = Utf8               Test

Field、Method、Interface结构

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

CONSTANT_Fieldref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
} CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
} CONSTANT_InterfaceMethodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}

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

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

例如

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

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

查看4和16

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

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

NameAndType结构

CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}

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

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

例如

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

tag = 16
name_index = 8
descriptor_index = 9

查看索引8和9

   #8 = Utf8               <init>
#9 = Utf8 ()V

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

UTF-8结构

CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}

表示一个字符串常量

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结构

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

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

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

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. .Net Core实战之基于角色的访问控制的设计

    前言 上个月,我写了两篇微服务的文章:<.Net微服务实战之技术架构分层篇>与<.Net微服务实战之技术选型篇>,微服务系列原有三篇,当我憋第三篇的内容时候一直没有灵感,因此先 ...

  2. 编译e2fsprogs-1.40.2

    1.解压源码 tar zxvf e2fsprogs-1.40.2.tar.gz 2.配置 ./configure --enable-elf-shlibs --host=arm-linux --with ...

  3. ubuntu12.04 empathy添加qq登陆

    1,

  4. LVS 负载均衡器理论基础及抓包分析

    LVS 是 Linux Virtual Server 的简写,即 Linux 虚拟服务器,是一个虚拟的服务器集群系统.本项目在1998年5月由章文嵩博士成立,是中国国内最早出现的自由软件项目之一.(百 ...

  5. react中的ref的3种方式

    2020-03-31 react中的ref的3种方式 react中ref的3种绑定方式 方式1: string类型绑定 类似于vue中的ref绑定方式,可以通过this.refs.绑定的ref的名字获 ...

  6. Java 技术网站总结(不停更新)

    Spring Spring 中文手册 Spring 教程 Spring For All Spring 学习笔记 Spring Boot Break易站 Spring Cloud 中文文档 Spring ...

  7. (一)JDK安装和使用eclipse输出hello world

    目录 一.jdk安装(推荐使用压缩包安装) 二.使用eclipse输出hello world 一.jdk安装(推荐使用压缩包安装) 1.压缩包安装的优点:若可能同时使用多个jdk版本,切换jdk时只需 ...

  8. matlab-整数规划(非线性规划之蒙特卡洛法(随机取样法))

    首先编写M 文件mengte.m 定义目标函数f 和约束向量函数g,程序如下:function [f,g]=mengte(x);%定义目标函数f和约束向量函数g f=x()^+x()^+*x()^+* ...

  9. (一)HttpClient Get请求

    原文链接:https://blog.csdn.net/justry_deng/article/details/81042379 HttpClient的主要功能: 实现了所有 HTTP 的方法(GET. ...

  10. 网页中为什么常用AT替换@(repost from https://zhidao.baidu.com/question/122291.html)

    经常在个人主页上看到别人的邮箱地址中@被AT符号替代,很是迷惑,这样替代有什么好处呢?还是说html原有的原因使界面中不能出现@,查阅资料后解答如下: 写成AT [at],是为了防止被一些邮件扫描器搜 ...