原创 吴就业 126 0 2018-09-25
本文为博主原创文章,未经博主允许不得转载。
本文链接:https://wujiuye.com/article/c7b55f40b9ba48db8364ab82d14211cc
作者:吴就业
链接:https://wujiuye.com/article/c7b55f40b9ba48db8364ab82d14211cc
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。
相信你也很好奇.java文件编译后的.class文件结构?我也很好奇,所以来了就一起挖一挖这个坑吧。这是我读《深入理解java虚拟机》这本书的第六章“类文件结构”之后写的,目的是为了帮助大家更好的理解这一章的内容。
本篇内容:
java源码文件编译后的class文件是一个二进制文件,所以我们需要借助一些专业的工具来打开class文件。其实我之前在专一的时候就用c#写了一个以二进制方式打开文件将文件内容显示为16进制的windows程序,但是多年过去 …..不是我找不到那个程序了,是我已经不用windows系统了,连同c#这门语言也忘了。
我要推荐给大家下载的工具是UE(UltraEdit),因为我也实在找不到其它更好用的工具了,而且我们也只是要能查看class文件内容这一功能而已,再者,UE很多人都在用,用的人多了自然破解的教程也多了,连最新版的都能找到破解教程。下载的话到官网下载就好了,百度UltraEdit下载。如果你也是mac os系统的用户,这里给你分享一个破解教程:https://bbs.feng.com/read-htm-tid-10828753.html,涵盖了很多版本的破解方法,而且简单到只需要一条命令。
官网下载链接:https://www.ultraedit.com/downloads/uex.html,不要看到链接就点啦,打不开的,需要复制然后到浏览器中打开,相信你有办法的。
这里纠正一个概念,就是java中的字段与属性有什么区别,可能很多人都跟我一样傻傻分不清,我记得我前几篇文章还有把字段和属性理解为一个意思的,笔者在此向各位读者朋友们致歉,可能误导了大家。
Java中的属性(property),通常可以理解为get和set方法。 而字段(field),通常叫做“类成员”,或 “类成员变量”,用来承载数据的。 这两个概念是完全不同的。
字段(filed):通常是在类中定义的类成员变量,例如:
public class People{
private String name = "wjy";
}
我们可以说People类中有一个成员变量叫做name,或People类有一个字段name 。 字段一般用来承载数据,所以为了安全性,一般定义为私有的。
属性(property):属性只局限于类中方法的声明,并不与类中其他成员相关,属于JavaBean的范畴。例如:
void setName(String name){}
String getName(){}
当一个类中拥有这样一对方法时,我们可以说,这个类中拥有一个可读写的name属性(注意是小写n)。如果去掉了set的方法,则是可读属性,反之去掉了get的方法则是只写属性。
写一些尽量简单的java类作为分析的对象,如果使用eclipse开发环境编写java代码,那么编译后的class文件在项目的bin目录下。
我简单的定义了一个IStudent接口,在接口中定义一个learning方法;一个People抽象类,只定义一些字段和属性;还有一个Student类,Student类有一个stuId的字段。至于为什么要写三个类,看到后面你就知道了。这三个类的类图关系如下。
[People.java]
package wjy.stu.jcfile;
//抽象类
public abstract class People {
private String name;//姓名
private int age;//年龄
private char sex;//性别
......省略getter、setter方法
}
[IStudent.java]
package wjy.stu.jcfile;
public interface IStudent {
//学习
void learning();
}
[Student.java]
package wjy.stu.jcfile;
public class Student extends People implements IStudent{
String stuId;//学号
//带参数构造方法
public Student(String stuId) {
super();
this.stuId = stuId;
}
//实现接口的方法
@Override
public void learning() {
System.out.println("我正在学习,我的学号是"+stuId);
}
}
另外编写一个Main类,然后提供一个main方法,主要作用是简单创建一个student对象,调用其learning方法,运行该main方法之后就能在项目根目录下生成class文件了,省去使用java命令编译的麻烦。
package wjy.stu.jcfile;
public class Main {
public static void main(String[] args) {
IStudent student = new Student("1516040934");
student.learning();
}
}
编译运行后会在项目的根目录下生成对应的class文件,可以忽略掉Main.class这个文件。
class文件是以字节(8位)为单位的二进制流,各项数据严格按照顺序紧凑地排列在文件之中。什么意思呢?就是先是class文件头,比如jdk版本号,接着存储常量、访问标志,再是存储文件继承自哪个类实现哪些接口、存储类的字段、方法、属性。当遇到需要占用多个字节以上空间的数据项时,则会按照高位在前低位在后的方式分割成若干个字节进行存储。
以下内容来自《深入理解Java虚拟机》第六章“类文件结构”:
Class文件格式采用类似C语言结构体的伪结构来存储数据,这种结构只有两种数据类型:无符号数和表。
需要注意的是class文件是没有分隔符的,所以每个的二进制数据类型都是严格定义的。具体的顺序定义如下:
[class文件格式表]
在class文件中,主要分为魔数、Class文件的版本号、常量池、访问标志、类索引(包括父类索引和接口索引集合)、字段表集合、方法表集合、属性表集合。
使用UE打开Student.class文件。差点忘了说明,我使用的是jdk 1.8编译的这几个java类。
根据上一节class文件结构的知识,我们知道在class文件存储的内容和顺序为魔数、Class文件的版本号(编译该类的jdk的版本号)、常量池、访问标志、类索引(包括父类索引和接口索引集合)、字段表集合、方法表集合、属性表集合。重要的事情说三遍不嫌多。
注意:这里的属性代表的并不是Java类中的字段属性,而是Class文件、字段表、方法表表中各个模块所携带的属性。
那我们就根据这个顺序来分析,首先是魔数,其唯一作用是用于确定这个文件是否为一个能被虚拟机接受的class文件,根据查表[class文件格式表]其类型为u4,即占用四个字节。从UE中可以看出, Student.class文件的头四个字节内容为0xCAFEBABE。如果一个文件不是以0xCAFEBABE开头,那它就肯定不是Java class文件。
紧挨着魔数的4个字节是class文件版本号,版本号又分为主版本号(major_version)和次版本号(minor_version),前两个字节用于表示次版本号,而后两个字节用于表示主版本号,不要把顺序弄混了哦。
版本号是随着jdk版本的不同而表示不同的版本范围的。Java的版本号是从45开始的。如果class文件的版本号超过虚拟机版本,将被拒绝执行,这句话很容易理解,比如你用jdk 1.8编译的java代码肯定不能在jdk 1.6上运行嘛,跟系统一样只能向后兼容,总不能预测未来。
次版本号我就不解析了,因为我并不关心这个,对我来说也没什么用。
我们来接着看主版本号后面的内容,根据[class文件格式表]得知排在主版本号后面的是常量个数。由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型(两个字节)的数据,代表常量池容量计数值(constant_pool_count)。
从UE中可以看出Student.class这个类中表示这个常量池计数值的是0x0031,转为10进制数为49,代表常量池中有48项常量(49-1),为什么是(49-1)?因为索引值范围为1~48(这个容器计数是从1开始的),第0项常量具有特殊意义,如果某些指向常量池索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况可以将索引值置为0来表示。
查[class文件格式表]得知,接着往下就是常量池表的内容,我们必须要读懂常量池表的内容才能计算出常量池表的长度,这样才能找到挨在常量池表后面的访问标志啊。
[常量池的14种项目类型]
[常量池的14种项目类型]该表列出了14种常量类型,而这14种常量类型又有各自的结构,见下表。
[常量池中的14种常量项的结构总表]
现在不理解这两张表不要紧,也不要慌,继续听我慢慢道来。首先查看表示常量个数的后面的一个字节。
表示第一个常量的类型的值为0x07,转换为十进制是7,查表 [常量池的14种项目类型] 标志为7的类型是CONSTANT_Class_info,即类或接口的符号引用。我们再来看该类型对应的结构,查表[常量池中的14种常量项的结构总表]。
该类型的结构是一个tag字段+index字段,tag字段长度为u1(一个字节),该类型的标志,而index字段长度为u2(两个字节 )指向全局限定名常量项的索引。那么这个类型为CONSTANT_Class_info的常量的长度就是u1+u2=u3即三个字节。
第一个常量已经找出来了,长度三个字节,类型为CONSTANT_Class_info,其中tag的值为0x07,index的值为 0x0002。
我们来接着找第二个常量,找完第二个其它的就由你们自己去找了,不然找完这48个常量都什么时候咯。
紧挨着第一个常量的就是第二个常量了,可以看到,第二个常量的标志位(类型)的值为0x01,10进制为1,查表[常量池的14种项目类型]得类型为CONSANT_Utf8_info,接着查表[常量池中的14种常量项的结构总表]得该类型的结构为:
CONSANT_Utf8_info{
u1 tag #值为1
u2 length #utf8编码的字符串的长度
u1[length] bytes #长度为length的urf8编码的字符串
}
length的长度为u2,即用两个字节表示bytes存储的字符串的长度。0x0016转为10进制为22,也就是bytes的长度为22个u1。
所以第二个常量的长度为(tag长度)u1+(length长度)u2+ length*u1 = 25字节。bytes的内容为”wjy/stu/jcfile/Student”。
[48个常量位置,隔一个画一条红线,有点难看懂,还是自己找吧]
我们找出48个常量之后,紧挨着最后一个常量的就是类的访问标志符了。
如图所示,Student这个类的访问标志为0x0021,用二进制表示为
0b0000 0000 0010 0001。
[类的访问标志]
注意这个访问标志表只适用于类,字段和方法都有些区别。student类我们声明了public,所以ACC_PUBLIC为1,而ACC_SUPER肯定为1,所以ACC_PUBLIC | ACC_SUPER 就是0x0021了。
我们来做个实验吧,试着把ACC_ABSTRACT修改为1,然后运行一下main函数。 使用UE可以直接修改class文件,把0x0021修改为0x0421即可。
运行出错,new Student(“”)这行代码出错,抛出的异常为实例化错误。ok,你明白了吗?就是我们把这个类标志为抽象类了,而抽象类是不能使用new来实例化的。【只有java文件的内容被修改eclips才会重新编译文件,而我们修改的是class文件,所以不会导致eclips重新编译Student这个类,所以测试才会得到我们想要的结果】
现在我们先暂停分析Student这个类,用UE打开People抽象类的class文件和IStudent接口的class文件,并分别找到它们的访问标志。
People抽象类的类访问标志为0x0421,即ACC_ABSTRACT位为1、ACC_PUBLIC位为1、ACC_SUPER位为1。
IStudent接口的类访问标志位0x0601,二进制为0000 0110 0000 0001,即ACC_INTERFACE位为1,ACC_ABSTRACT为1,ACC_SUPER位为1。可见,当一个类的类型是接口的时候,该类也是一个抽象类。
继续回过头来分析 Student这个类class文件结构。查表[class文件结构表],紧挨着类访问标志后的是类索引(this_class),类型为u2(两个字节),跟着类索引的是父类索引(super_class),类型也是u2,跟着父类索引的是接口个数,类型为u2,跟着接口个数的是接口索引,类型为u2。如果接口个数为02,那么索引后面的四个字节就都是表示接口索引的,两个字节表示一个接口索引。我们一个个来看。
类索引(this_class):0x0001,这个0x0001是一个索引值,指向的是常量(常量表,常量是用一个常量表描述的,所以常量也即常量表,下文同)。记得第一个常量表的类型是什么吗?CONSTANT_Class_info,这个常量表的值为0x07 0002,tag=0x07,index=0x0002。
父类索引(super_class):0x0003,也是一个索引,指向第三个常量(常量表)。第三个常量表也是一个CONSTANT_Class_info类型,值为0x07 0004,tag=0x07,index=0x0004。
接口个数:0x0001,即接口的个数为1。
接口索引:0x0005,是个索引值,指向第五个常量,第五个常量也是一个CONSTANT_Class_info类型,值为0x07 0006,tag=0x07,index=0x0006。
this_class索引指向的CONSTANT_Class_info结构体的index指向0x02即第二个常量,第二个常量类型是CONSTANT_Utf8_info,保存的是一个utf8编码的字符串:“wjy/stu/jcfile/Student”。
super_class索引指向的CONSTANT_Class_info结构体的index指向0x04即第四个常量,第四个常量类型是CONSTANT_Utf8_info,保存的是一个utf8编码的字符串:“wjy/stu/jcfile/People”。
第一个接口索引(Student只实现了一个接口,所以只有一个接口索引)指向的CONSTANT_Class_info结构体的index指向0x06即第六个常量,第六个常量类型是CONSTANT_Utf8_info,保存的是一个utf8编码的字符串:“wjy/stu/jcfile/IStudent”。
在最后一个接口索引后面的是字段的个数和字段表,首先是字段的个数,类型为u2(两个字节)。
Student类的字段个数为0x0001,即1个字段。
People类的字段个数为0x0003,即字段个数为3个。注意不要读错,因为People这个类没有实现任何接口,接口个数为0,所以接口个数后面紧挨着的就是字段个数了。
字段表就是字段表集合,一个字段需要使用一个字段表来描述。字段表用于描述类中声明的变量(字段),包括静态变量,不包括在方法内部声明的变量。
在Java中一般通过如下几项描述一个字段:字段作用域(public、protected、private修饰符)、是否有static修饰符、是否可变(final修饰符)、并发可见性(volatile修饰符)、可序列化与否(transient修饰符)、字段数据类型(基本类型、对象、数组)以及字段名称。在字段表中,变量修饰符使用标志位表示,字段数据类型和字段名称则引用常量池中常量表示。(总算知道常量池为什么那么多常量了)
[字段表结构]
字段表包含的固定数据项到descriptor_index结束,之后跟随一个属性表集合用于存储一些附加信息。 这里面的属性代表的并不是Java类中的字段属性,而是Class文件、字段表、方法表表中各个模块所携带的属性。字段表集合中不会列出从父类或父接口中继承的字段。(Java语言中字段是不能重载的,两个字段无论数据类型、修饰符是否相同,都不能使用相同的名称;但是对于字节码,只要字段描述符不同,字段重名就是合法的。)
由字段表结构可以看出,描述一个字段的字段表所占的字节大小为u2(access_flags)+u2(name_index)+u2(descriptor_index)+u2(attributes_count)+attribute_info的长度 x 属性个数。当属性个数为0时,那么一个字段表的大小就为8个字节。
首先我们根据之前写的java代码来猜测一下各个字段的字段表的访问标志。
public class Student extends People implements IStudent{
String stuId;
}
Student类只有一个stuId字段,根据[字段访问标志表]分析:
所以Student类的stuId字段的访问标志的值为0x0000。
public abstract class People {
private String name;// 姓名
private int age;// 年龄
private char sex;// 性别
}
再看People类的name字段,根据[字段访问标志表]分析:
所以People类的name字段的访问标志的值为0x0002。
解析到这里你因为能看得懂字段表了吧,看不懂没关系,我们一起看。
Student类只有stuId这一个字段,而第四个u2的值为0x00,即attributes_count的值为0,所以这个stuId字段的字段表长度为8个字节。0x0007是一个常量索引,指向字段名称的字符串常量“stuId”,不相信可以字节去验证一下。0x0008也是一个常量索引,指向的是字段的类型“Ljava/lang/String;”,这是字段的描述符(我记得之前我在写android ndk开发的时候有说过,不了解的去百度搜索资料自行学习吧),也叫JNI字段描述符(JavaNative Interface FieldDescriptors) 。
People有三个字段,分别为name、age、sex,这三个字段的字段表的attributes_count值都为0x00,所以这三个字段的字段表长度都为8个字节。0x0005指向的是字符串常量“name”,0x0006指向的是字符串常量“Ljava/lang/String;”(即String类型),0x0007指向的是字符串常量“age”,0x0008指向的是字符串常量“I”(即int类型),0x0009指向的是字符串常量“sex”,0x000a指向的是字符串常量“C”(即char类型)。这需要你自己去验证了。
对于本例中Student和People类的字段stuId、name、age、sex,它们的属性表计数器为0,也就是没有需要额外描述的信息,但是,如果将字段name的声明改为“final static int age=23;,那就可能会存在一项名称为ConstantValue的属性,其值指向常量23。关于属性表在最后一节内容讲到,先不急着理解。
【附加的学习内容,可以跳过看下一节】
我们修改一下Student这个类,添加一个静态字段,看看是否有对应的字段表。我加了一个静态字段schoolName。
public class Student extends People implements IStudent{
String stuId;
private static String schoolName;
public Student(String stuId) {
super();
this.stuId = stuId;
}
@Override
public void learning() {
System.out.println("我正在学习,我的学号是"+stuId);
}
}
这里教大家一个快速找到类的访问标志的方法,就是常量池的最后一个常量保存的是这个类的类名,如这里的“Student.java”,找到这个.java的字符串后面就是类的访问标志了,如下图。
现在要做的就是看看字段的个数和字段表中是否有schoolName这个字段表。
跟之前的对比,很显然,字段个数变为了2,第二个字段表的值为0x000a 0009 0008 0000,分别对应的是字段的访问标志0x000a、字段名在常量池中的索引0x0009、字段描述符在常量池中的索引0x0008,属性个数为0x0000。
0x000a = private(0x0002) + static(0x0008),即私有的静态字段。
索引0x0009指向的字符串常量是“schoolName;”。
索引0x0008指向的字符串常量是“Ljava/lang/String;”。
记得在这里先恢复一下Student这个类,把刚加的静态字段去掉,然后重新编译一次Student类,不然会影响后面的分析。
跟字段个数和字段表一样,方法表的结构也跟字段表相似。但是不同的是方法的访问标志有些区别,因为volatile关键字和transient关键字不能修饰方法。而因为方法可以被synchronized、native、abstract关键字修饰,所以访问标志中添加了ACC_SYNCHRONIZED、ACC_NATIVE、ACC_ABSTRACT。
方法的方法表结构也是跟字段表一样的。只是每个成员所表示的含义不同。在方法表中access_flags表示的是方法的访问标志,name_index方法名称在常量池中的索引,descriptor_index为方法描述符索引。而attribute_count和attribute_info跟字段表的含义一样分别是属性个数和属性表。
这里的属性代表的并不是Java类中的字段属性,而是Class文件、字段表、方法表表中各个模块所携带的属性。
先看Student这个类。
紧接着最后一个字段表的就是方法的个数,类型为u2,所以方法的个数是0x0002,十进制也是2。回顾一下Student的源码,是不是有两个方法,一个是实现IStudent接口的learning方法,一个是Student的带参数构造方法。
第一个方法表是构造参数的方法表,验证一下。首先0x0001是方法的访问标志,根据方法访问标志表可知这个方法是public修饰的。0x0009指向的是方法名在常量池中表的索引,第九个常量表类型是CONSANT_Utf8_info,bytes保存的是字符串“”,所以这个方法是构造方法没错了。再看0x000a,十进制是10,指向第十个常量“(Ljava/lang/String;)V”,这是方法的描述符,括号里面“Ljava/lang/String;”表示带一个String类型的参数,括号外面的”V”是方法的返回值类型,V是Void类型的类型描述符,这个不懂的需要花时间自己去学一下了,这里不可能一篇文章什么都介绍呢。
接着方法描述符的是属性个数,值为0x0001,说明这个方法有一个属性表。0x000b指向第十一个常量,即“Code”。先暂停在这,因为不懂属性表的结构是没办法看懂这个“Code”属性表的。
就算不写构造方法,编译器会默认帮我们生成一个无参数构造方法,我们来验证一下,看People这个类的方法个数和第一个方法表是否是指向无参数构造方法。
People类的方法个数为0x0007,十进制也是7,为什么是7个方法,因为我给people类的三个属性name、age、sex添加了get和set方法,所以是6个了,虽然我们没有给people类添加构造方法,但是编译器会帮我们添加一个默认的构造方法,所以总共就是7个方法了。
第一个方法的访问标志是0x0001,也就是这个方法是public修饰的。接着是方法名索引0x000b,指向第11个常量“”,然后是方法的描述符所以0x000c,指向第12个常量“()V”,这个方法描述符表示的是该方法不需要任何参数且返回值类型为void。后面的0x0001是属性表的个数,0x000d是第一个属性表的属性名索引,指向“Code”。
与字段表集合相对应的,如果父类方法在子类中没有被重写(Override),方法表集合中就不会出现来自父类的方法信息。
你可能会问,方法的定义可以通过访问标志、名称索引、描述符索引表达清楚,但方法里面的代码去哪里了?方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为“Code”的属性里面,属性表是Class文件格式中最具扩展性的一种数据项目,在下一节解析。
不要把这个属性个数和属性表联系到java类的属性,不要把属性表理解为类的成员变量的get和set方法。注意了,搞混了这个本篇文章解析class文件结构的内容出现属性的地方你都会看不懂。这里面的属性代表的并不是Java类中的字段属性,而是Class文件、字段表、方法表表中各个模块所携带的属性。
属性表(attribute_info)在Class文件、字段表、方法表都出现过,Class文件、字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息。
属性表集合不要求各个属性表具有严格的顺序,且只要不与其它已有属性名重复,任何编译器都可以向属性表中写入自定义的属性信息,java虚拟机运行时会忽略掉它不认识的属性。为了能正确解析Class文件,java虚拟机规范预定义了九项虚拟机能识别的属性。
一个属性表至少需要提供属性名索引、属性长度和属性信息三个部分,其结构如下表所示。所以前面在分析方法表的时候我们看到Student的第一个方法有一个属性,然后直接读取属性个数(attributes_count)后面的两个字节(u2)作为属性名在常量池中的索引了,就是因为属性表的前面两个字节存储的必然是属性名在常量池中的索引。
每个属性表的属性名规定必须是从常量池中引用的一个CONSTANT_Utf8_info类型的常量。属性长度所存储的就是属性值info的长度,而属性值结构完全是自定义的,只要通过长度为u4的属性长度说明属性值占用多少个单位u1就可以了。
这里就介绍一种Code属性,因为在方法表的时候出现过。因为出现过,所以根据例子讲起来就会很容易理解,其它的就需要读者自己去学习了。本文也是参考《深入理解java虚拟机》的第六章”类文件结构”写的。
java类中的方法体的代码经过编译处理后最终变为字节码指令存储在Code属性内容。所以前面将的每个方法表包括构造方法、get、set方法等都会有一个Code属性。但是并不是方法都会存在这个属性的,接口中方法的定义和抽象类中抽象方法的定义都不会有这个Code属性。
我们先来验证一下,看下IStudent接口的learning方法的方法表是否存在Code属性。
[IStudent.class]
[IStudent.class]
所以IStudent接口的learning方法的属性个数为0,也证明了接口中定义的方法并没有Code这个属性。
好了,我们继续回来分析Code属性。Code属性表的结构如下。
一个u2(两个字节无符号整数)类型的属性名索引,指向属性名在常量池表中的对应项,在Code属性中常量值固定为“Code”。一个u4类型(四个字节无符号整数)存储该属性值的长度(除属性名长度[u2]和属性值长度[u4]外的内容的长度)。即属性值长度=整个属性表的长度-6个字节。
man_stack代表操作数栈深度的最大值,在方法执行的任意时候操作数栈都不会超过这个深度。虚拟机运行的时候需要根据这个值来分配栈帧中操作数栈深度。
max_locals存储局部变量表所需要的存储空间,单位是Slot。Slot是虚拟机为局部变量分配内存所使用的最小单位,对于byte、char、float、int、short、boolean和returnAddress等长度不超过32位的数据类型,每个局部变量占用一个Slot,而double和long这两种64位的数据类型则需要两个Slot来存放。方法参数(包括实例方法中的隐藏参数“this”)、显式异常处理器的参数(Exception Handler Parameter,就是try-catch语句中catch块所定义的异常)、方法体中定义的局部变量都需要使用局部变量表来存放。
code_length存储方法体字节码指令的长度,单位为u1,即字节码指令每条指令占用一个字节。
code存储方法体字节码指令。当虚拟机读取到code中的一个字节时,就可以对应找出这个字节码代表的指令是什么,并且可以知道这条指令后面是否需要跟随参数,如果需要跟随参数就接着读取后面参数单位的字节内容当作参数。
exception_table_length、exception_table就不做介绍了,属性中包含的属性(属性个数attrutes_count、attrutes)就更不用介绍了,还是这个attribute_info结构,就是一个属性表中又包含其它属性表。
Student类的第一个方法表,即构造函数的方法表,有一个属性为Code属性,Code属性的内容在图中已经给出。更多内容还是推荐去看《深入理解java虚拟机这本书》,这本书个人觉得写的很好,通俗易懂。
声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。
上一篇有说到过BeanDefinition,主要关注的是其扩展接口AnnotateBeanDefinition和其子类AnnotateGenericBeanDefinition。本篇先超前介绍spring是如果通过BeanDefinition来创建一个bean的。
本篇主要介绍一些重要的接口及类,下一篇介绍bean的创建过程,接着介绍AnnotationConfigApplicationContext的初始化流程,最后一篇通过源码总结bean的生命周期。
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。