Java开发只需要编写Java代码之后通过javac命令将其编译成.class文件,.class文件可以被JVM虚拟机加载并执行。如果需要Java能够像动态语言那样编码,通常需要修改.class文件的内容,这种情况下了解.class文件的内部结构就很有必要。
Java的class文件内容大致上包含如下的各种结构,如果某个节点有多个会被表示成数组结构,数组的长度通常都在实际数据之前。
ClassFile {
u4 magic; // 魔法数字,表明当前文件是.class文件,固定0xCAFEBABE
u2 minor_version; // 分别为Class文件的副版本和主版本
u2 major_version;
u2 constant_pool_count; // 常量池计数
cp_info constant_pool[constant_pool_count-1]; // 常量池内容
u2 access_flags; // 类访问标识
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]; // 各种属性
}
constant_pool常量池是一种表结构,它包含Class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池不同于其他,索引从1开始到constant_pool_count - 1。其他的结构都比较简单不需要做深入的了解,只要知道它们的存在就够了。
现在定义一个简单的类查看它的.class文件内容,之后通过Javassist来实现读取.class内容。
package callsuper;
public class Person {
private int age;
public void say() {
System.out.println("Hello Person");
}
}
这个类定义的非常简单,就只有一个字段,一个函数,调用了简单的打印方法。现在通过javac对它做编译操作,再使用javap工具将生成的.class文件反编译查看。
javac callsuper.Person.java
javap -v Person.class
使用javap会将生成的Person.class文件反编译,列出class文件中的各种数据,展示如下:
Classfile /D:/workspace/Super/src/callsuper/Person.class
Last modified 2018-7-4; size 420 bytes
MD5 checksum df344c4b08a989c2471d097e90aa39d8
Compiled from "Person.java"
public class callsuper.Person
minor version: 0 // 主副版本好
major version: 52
flags: ACC_PUBLIC, ACC_SUPER // 访问标识
Constant pool: // 常量池
#1 = Methodref #6.#16 // java/lang/Object."":()V
#2 = Fieldref #17.#18 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #19 // Hello Person
#4 = Methodref #20.#21 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #22 // callsuper/Person
#6 = Class #23 // java/lang/Object
#7 = Utf8 age
#8 = Utf8 I
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 say
#14 = Utf8 SourceFile
#15 = Utf8 Person.java
#16 = NameAndType #9:#10 // "":()V
#17 = Class #24 // java/lang/System
#18 = NameAndType #25:#26 // out:Ljava/io/PrintStream;
#19 = Utf8 Hello Person
#20 = Class #27 // java/io/PrintStream
#21 = NameAndType #28:#29 // println:(Ljava/lang/String;)V
#22 = Utf8 callsuper/Person
#23 = Utf8 java/lang/Object
#24 = Utf8 java/lang/System
#25 = Utf8 out
#26 = Utf8 Ljava/io/PrintStream;
#27 = Utf8 java/io/PrintStream
#28 = Utf8 println
#29 = Utf8 (Ljava/lang/String;)V
{
public callsuper.Person(); // 构造函数
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 3: 0
public void say(); // 普通函数
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello Person
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 7: 0
line 8: 8
}
SourceFile: "Person.java"
上面的带#的部分就是常量池中数据,可以看到里面包含各种方法、字段和字符串等常量,所有存在方法和定义里的常量都被替换成了该常量在常量池中的索引值。上面的代码反编译之后解析出来的是JVM的命令,和Dalvik的smali差别还是比较大的。
class文件内容还是比较复杂的,如果由开发者直接人工解析还是很费事的,这里推荐使用Javassist的接口来编码查看class文件的内部数据。
CtClass ctClass = ClassPool.getDefault().get("callsuper.Person");
// 获取解析到的类文件对象
ClassFile classFile = ctClass.getClassFile();
// 获取到常量池对象
ConstPool constPool = classFile.getConstPool();
System.out.println("=======Version========");
System.out.println(classFile.getMajorVersion());
System.out.println(classFile.getMinorVersion());
System.out.println("=======Constant Pool Size========");
System.out.println(classFile.getConstPool().getSize());
System.out.println("=======Access Flag========");
int flag = classFile.getAccessFlags();
System.out.println("package = " + AccessFlag.isPackage(flag));
System.out.println("private = " + AccessFlag.isPrivate(flag));
System.out.println("protected = " + AccessFlag.isProtected(flag));
System.out.println("public = " + AccessFlag.isPublic(flag));
System.out.println("=======Super Class=========");
System.out.println(classFile.getSuperclass());
System.out.println("=========This class========");
System.out.println(classFile.getConstPool().getClassName());
System.out.println("=======Interface Info========");
System.out.println(classFile.getInterfaces().length);
for (int i = 0; i < classFile.getInterfaces().length; i++) {
String face = classFile.getInterfaces()[i];
System.out.println(face);
}
System.out.println("=======Method Info========");
List methodInfoList = classFile.getMethods();
for (MethodInfo methodInfo : methodInfoList) {
System.out.println(methodInfo.getName());
List attributeInfoList = methodInfo.getAttributes();
for (AttributeInfo attributeInfo : attributeInfoList) {
System.out.println(attributeInfo.getName());
}
System.out.println(methodInfo.getDescriptor());
System.out.println("--------------------------");
}
System.out.println("=======Field Info========");
List fieldInfoList = classFile.getFields();
for (int i = 0; i < fieldInfoList.size(); i++) {
FieldInfo fieldInfo = fieldInfoList.get(i);
System.out.println(fieldInfo.getName());
System.out.println(fieldInfo.getDescriptor());
System.out.println("---------------------------");
}
System.out.println("=======Attributes Info========");
List attributeInfoList = classFile.getAttributes();
for (AttributeInfo attributeInfo : attributeInfoList) {
System.out.println(attributeInfo.getName());
}
System.out.println("=======All Ref classes========");
System.out.println(constPool.getClassNames());
System.out.println(Arrays.asList(classFile.getInterfaces()));
运行上面的代码查看分析class文件的结果,与前面javap反编译的结果是一致的。
=======Version========
52
0
=======Constant Pool Size========
33
=======Access Flag========
package = false
private = false
protected = false
public = true
=======Super Class=========
java.lang.Object
=========This class========
callsuper.Person
=======Interface Info========
0
=======Method Info========
Code
()V
--------------------------
say
Code
()V
--------------------------
=======Field Info========
age
I
---------------------------
=======Attributes Info========
SourceFile
=======All Ref classes========
[java/lang/Object, callsuper/Person, java/lang/System, java/io/PrintStream]
[]