大型软件系统开发时,某些Java组件可能涉及到多种数据库或中间件系统的连接和应用,例如一个数据传递组件需要从DB2中读取数据,并将数据通过中间件WebSphere MQ发送到其他系统,这类组件功能单一,但却需要连接多种第三方产品,使得程序员的单元测试变的非常不便,程序员不得不注视或修改部分源代码,或者在本地安装所需第三方产品。无疑这两种选择都是痛苦的。
基于以上的不便,本文开发了解析Java Class文件程序,目的是将第三方产品API的Class文件转换为Java源文件(不包括Java类的方法实现),在源文件的各种程序所需的方法里实现一些简单的语句,例如数据库连接方法永远返回true,获得数据方法永远返回 ”Hello world” 等,用JDK重新编译转换后的Java源文件,来替换真正的API 文件,这样程序员在UT测试时,无需修改源代码,也无需安装任何产品,并且能通过修改替换的API Java源文件实施各种UT测试。
为了实现以上需求,必须先要了解Java Class文件格式。Java虚拟机识别的class文件格式包含Java虚拟机指令(或者bytecodes)和一个符号表以及其他的辅助信息。本文将使用VC++语言解析Java Class文件符号表,逆向生成Java源代码结构。如图1:
图1
之所以使用VC++而不使用Java的主要是因为VC++界面开发简单;运行速度快,不需要虚拟机;需要用指针建立复杂的数据结构。
实现该工具的过程如下:
1.解析Class文件,从Class文件中读取数据并保存到称为ClassFile结构体中;
2.解析ClassFile结构体,生成源代码字符串;
3.将字符串显示到视图中。
为实现第1步,首先需要了解Class文件格式规范,参考《Java虚拟机规范》第四章class文件格式,总结class文件的数据结构如图2。
2.1.1 Class文件格式
Class文件格式ClassFile结构体的C语言描述如下:
struct ClassFile
{
u4 magic; //识别Class文件格式,具体值为0xCAFEBABE,
u2 minor_version; // Class文件格式副版本号,
u2 major_version; // Class文件格式主版本号,
u2 constant_pool_count; // 常数表项个数,
cp_info **constant_pool;// 常数表,又称变长符号表,
u2 access_flags; //Class的声明中使用的修饰符掩码,
u2 this_class; //常数表索引,索引内保存类名或接口名,
u2 super_class; //常数表索引,索引内保存父类名,
u2 interfaces_count; //超接口个数,
u2 *interfaces; //常数表索引,各超接口名称,
u2 fields_count; //类的域个数,
field_info **fields; //域数据,包括属性名称索引,
//域修饰符掩码等,
u2 methods_count; //方法个数,
method_info **methods;//方法数据,包括方法名称索引,方法修饰符掩码等,
u2 attributes_count; //类附加属性个数,
attribute_info **attributes; //类附加属性数据,包括源文件名等。
};
其中u2为unsigned short,u4为unsigned long:
typedef unsigned char u1;
typedef unsigned short u2;
typedef unsigned long u4;
cp_info **constant_pool是常量表的指针数组,指针数组个数为constant_pool_count,结构体cp_info为
struct cp_info
{
u1 tag; //常数表数据类型
u1 *info; //常数表数据
};
常数表数据类型Tag定义如下:
#define CONSTANT_Class 7
#define CONSTANT_Fieldref 9
#define CONSTANT_Methodref 10
#define CONSTANT_InterfaceMethodref 11
#define CONSTANT_String 8
#define CONSTANT_Integer 3
#define CONSTANT_Float 4
#define CONSTANT_Long 5
#define CONSTANT_Double 6
#define CONSTANT_NameAndType 12
#define CONSTANT_Utf8 1
每种类型对应一个结构体保存该类型数据,例如CONSTANT_Class 的info指针指向的数据类型应为CONSTANT_Class_info
struct CONSTANT_Class_info
{
u1 tag;
u2 name_index;
};
图2
CONSTANT_Utf8的info指针指向的数据类型应为CONSTANT_Utf8_info
struct CONSTANT_Utf8_info
{
u1 tag;
u2 length;
u1 *bytes;
};
Tag和info的详细说明参考《Java虚拟机规范》第四章4.4节。
access_flags为类修饰符掩码,域与方法都有各自的修饰符掩码。
#define ACC_PUBLIC 0x0001
#define ACC_PRIVATE 0x0002
#define ACC_PROTECTED 0x0004
#define ACC_STATIC 0x0008
#define ACC_FINAL 0x0010
#define ACC_SYNCHRONIZED 0x0020
#define ACC_SUPER 0x0020
#define ACC_VOLATILE 0x0040
#define ACC_TRANSIENT 0x0080
#define ACC_NATIVE 0x0100
#define ACC_INTERFACE 0x0200
#define ACC_ABSTRACT 0x0400
#define ACC_STRICT 0x0800
例如类的修饰符为public abstract则access_flags的值为ACC_PUBLIC | ACC_ABSTRACT=0x0401。
this_class的值是常数表的索引,索引的info内保存类或接口名。例如类名为com.sum.java.swing.SwingUtitlities2在info保存为com/sum/java/swing/SwingUtitlities2
super_class的值是常数表的索引,索引的info内保存超类名,在info内保存形式和类名相同。
interfaces是数组,数组个数为interfaces_count,数组内的元素为常数表的索引,索引的info内保存超接口名,在info内保存形式和类名相同。
field_info **fields是类域数据的指针数组,指针数组个数为fields_count,结构体field_info定义如下:
struct field_info
{
u2 access_flags; </span