解析字段表
同一个Class文件中,不会存在两个相同的字段。相同指定的是字段名与类型描述符都相同。字段结构与Class文件结构一样,都有访问标志项,但两者的访问标志项,在访问权限和属性上有些区别。参照《Java虚拟机规范》,字段中的访问权限和属性标志如表2-41所示。
表2-41 字段访问权限与属性标志
字段的结构如表2-42所示。
表2-42 字段的结构
其中,access_flags是字段的访问标志,name_index是字段名称,descriptor_index是字段的类型描述符。字段结构与方法结构、Class文件结构都有属性表attributes,属性表的属性个数可以是0个或多个。属性的通用结构如表2-43所示。
表2-43 属性的通用结构
关于属性,我们先了解属性的通用结构,实现属性的初步解析,让字段解析器能够完成字段的解析工作,至于属性info是什么暂时先不关心。
创建字段结构对应的Java类FieldInfo,如代码清单2-44所示。
代码清单2-44 FieldInfo类
public class FieldInfo { private U2 access_flags; private U2 name_index; private U2 descriptor_index; private U2 attributes_count; private AttributeInfo[] attributes; }
创建属性结构对应的Java类AttributeInfo,如代码清单2-45所示。
代码清单2-45 AttributeInfo类
public class AttributeInfo { private U2 attribute_name_index; private U4 attribute_length; private byte[] info; }
创建字段表解析器FieldHandler,实现字段表的解析。字段结构的属性表的解析工作也由字段表解析器完成。解析流程如下:
1、先从class文件字节缓存中读取到字段总数,根据字段总数创建字段表;
2、循环解析出每个字段;
3、解析字段的属性表时,先解析获取到属性总数,再根据属性总数创建属性表;
4、使用通用属性结构循环解析出字段的每个属性;
5、解析属性时,先解析出attribute_name_index,再解析attribute_length获取属性info的长度,根据长度读取指定长度的字节数据存放到属性的info字段。
字段表解析器的实现,如代码清单2-46所示。
代码清单2-46 FieldHandler类
public class FieldHandler implements BaseByteCodeHandler { @Override public int order() { // 排在接口解析器的后面 return 6; } @Override public void read(ByteBuffer codeBuf, ClassFile classFile) throws Exception { // 读取字段总数 classFile.setFields_count(new U2(codeBuf.get(), codeBuf.get())); int len = classFile.getFields_count().toInt(); if (len == 0) { return; } // 创建字段表 FieldInfo[] fieldInfos = new FieldInfo[len]; classFile.setFields(fieldInfos); // 循环解析出每个字段 for (int i = 0; i < fieldInfos.length; i++) { // 解析字段 fieldInfos[i] = new FieldInfo(); // 读取字段的访问标志 fieldInfos[i].setAccess_flags(new U2(codeBuf.get(), codeBuf.get())); // 读取字段名称 fieldInfos[i].setName_index(new U2(codeBuf.get(), codeBuf.get())); // 读取字段类型描述符索引 fieldInfos[i].setDescriptor_index(new U2(codeBuf.get(), codeBuf.get())); // 读取属性总数 fieldInfos[i].setAttributes_count(new U2(codeBuf.get(), codeBuf.get())); // 获取字段的属性总数 int attr_len = fieldInfos[i].getAttributes_count().toInt(); if (attr_len == 0) { continue; } // 创建字段的属性表 fieldInfos[i].setAttributes(new AttributeInfo[attr_len]); // 循环解析出每个属性,先使用通用属性结构解析每个属性 for (int j = 0; j < attr_len; j++) { // 解析字段的属性 fieldInfos[i].getAttributes()[j] .setAttribute_name_index(new U2(codeBuf.get(),codeBuf.get())); // 获取属性info的长度 U4 attr_info_len = new U4(codeBuf.get(), codeBuf.get(), codeBuf.get(), codeBuf.get()); fieldInfos[i].getAttributes()[j].setAttribute_length(attr_info_len); // 解析info byte[] info = new byte[attr_info_len.toInt()]; codeBuf.get(info, 0, attr_info_len.toInt()); fieldInfos[i].getAttributes()[j].setInfo(info); } } } }
编写将字段的访问标志access_flags转为字符串输出的工具类FieldAccessFlagUtils,如代码清单2-47所示。
代码清单2-47 access_flags转字符串工具类
public class FieldAccessFlagUtils { private static final Map<Integer, String> fieldAccessFlagMap = new HashMap<>(); static { fieldAccessFlagMap.put(0x0001, "public"); fieldAccessFlagMap.put(0x0002, "private"); fieldAccessFlagMap.put(0x0004, "protected"); fieldAccessFlagMap.put(0x0008, "static"); fieldAccessFlagMap.put(0x0010, "final"); fieldAccessFlagMap.put(0x0040, "volatile"); fieldAccessFlagMap.put(0x0080, "transient"); fieldAccessFlagMap.put(0x1000, "synthtic"); fieldAccessFlagMap.put(0x4000, "enum"); } /** * 获取16进制对应的访问标志和属性字符串表示 (仅用于类的访问标志) * * @param flag字段的访问标志 * @return */ public static String toFieldAccessFlagsString(U2 flag) { final int flagVlaue = flag.toInt(); StringBuilder flagBuild = new StringBuilder(); fieldAccessFlagMap.keySet() .forEach(key -> { if ((flagVlaue & key) == key) { flagBuild.append(fieldAccessFlagMap.get(key)).append(","); } }); return flagBuild.length() > 0 && flagBuild.charAt(flagBuild.length() - 1) == ',' ? flagBuild.substring(0, flagBuild.length() - 1) : flagBuild.toString(); } }
字段表解析器编写完成,我们先将字段解析器注册到ClassFileAnalysiser,再编写单元测试。编写单元测试用于验证字段解析器是否能够正确完成字段表地解析。编写单元测试要求将解析后的所有字段的名称、类型、以及字段的访问标志转为字符串打印出来,以验证结果是否正确。字段表解析器单元测试如代码清单2-48所示。
代码清单2-48 字段表解析器单元测试
public class FieldHandlerTest { private static String getName(U2 name_index, ClassFile classFile) { CONSTANT_Utf8_info name_info = (CONSTANT_Utf8_info) classFile.getConstant_pool()[name_index.toInt() - 1]; return name_info.toString(); } @Test public void testFieldHandlerHandler() throws Exception { ByteBuffer codeBuf = ClassFileAnalysisMain.readFile("Builder.class"); ClassFile classFile = ClassFileAnalysiser.analysis(codeBuf); System.out.println("字段总数:" + classFile.getFields_count().toInt()); System.out.println(); FieldInfo[] fieldInfos = classFile.getFields(); // 遍历字段表 for (FieldInfo fieldInfo : fieldInfos) { System.out.println("访问标志和属性:" + FieldAccessFlagUtils .toFieldAccessFlagsString(fieldInfo.getAccess_flags())); System.out.println("字段名:" + getName(fieldInfo.getName_index(), classFile)); System.out.println("字段的类型描述符:" + getName(fieldInfo.getDescriptor_index(), classFile)); System.out.println("属性总数:" + fieldInfo.getAttributes_count().toInt()); System.out.println(); } } }
单元测试结果输出如图2.9所示。
图2.9 字段表解析器单元测试
从结果输出可以看出,该class有三个字段,访问权限都是private的,字段名分别是a、b、c,并且类型描述符都是“I”,即字段的类型都是整型。