解析class文件的属性表
字段结构和方法结构也都有属性表,所以要注意不要将这些属性表混在一起理解。但所有属性都有一个通用的结构,这在解析字段那部分已经介绍。因此,解析class文件结构的属性表我们也可以使用通用的属性结构来解析。
解析步骤是先从class文件字节缓存中读取两个字节,如果前面的解析工作都正常,那么现在读取到的这两个字节就是该class文件属性表的长度。接着根据长度创建属性表,使用通用属性结构循环解析出每个属性,循环次数为属性的总数。class文件结构的属性表解析器AttributesHandler的实现如代码清单2-54所示。
代码清单2-54 class文件结构属性表解析器
public class AttributesHandler implements BaseByteCodeHandler { @Override public int order() { return 8; } @Override public void read(ByteBuffer codeBuf, ClassFile classFile) throws Exception { classFile.setAttributes_count(new U2(codeBuf.get(), codeBuf.get())); // 获取属性总数 int len = classFile.getAttributes_count().toInt(); if (len == 0) { return; } // 创建属性表 AttributeInfo[] attributeInfos = new AttributeInfo[len]; classFile.setAttributes(attributeInfos); for (int i = 0; i < len; i++) { // 创建属性 AttributeInfo attributeInfo = new AttributeInfo(); attributeInfos[i] = attributeInfo; // 解析属性 attributeInfo.setAttribute_name_index(new U2(codeBuf.get(), codeBuf.get())); attributeInfo.setAttribute_length(new U4(codeBuf.get(), codeBuf.get(), codeBuf.get(), codeBuf.get())); int attr_len = attributeInfo.getAttribute_length().toInt(); if (attr_len == 0) { continue; } // 解析属性的info项 byte[] bytes = new byte[attr_len]; codeBuf.get(bytes, 0, bytes.length); attributeInfo.setInfo(bytes); } } }
将class文件结构的属性表解析器AttributesHandler注册到ClassFileAnalysiser。
现在我们已经编写完成class文件结构各项的解析器,并且都已经注册到ClassFileAnalysiser,现在ClassFileAnalysiser类持有的解析器有MagicHandler、VersionHandler、ConstantPoolHandler、AccessFlagsHandler、ThisAndSuperClassHandler、InterfacesHandler、FieldHandler、MethodHandler、AttributesHandler,如代码清单2-55所示。
代码清单2-55 ClassFileAnalysiser类
public class ClassFileAnalysiser { private final static List<BaseByteCodeHandler> handlers = new ArrayList<>(); static { handlers.add(new MagicHandler()); handlers.add(new VersionHandler()); handlers.add(new ConstantPoolHandler()); handlers.add(new AccessFlagsHandler()); handlers.add(new ThisAndSuperClassHandler()); handlers.add(new InterfacesHandler()); handlers.add(new FieldHandler()); handlers.add(new MethodHandler()); handlers.add(new AttributesHandler()); // 如果解析器是按顺序注册的,那么排序可以忽略 handlers.sort((Comparator.comparingInt(BaseByteCodeHandler::order))); } public static ClassFile analysis(ByteBuffer codeBuf) throws Exception { ClassFile classFile = new ClassFile(); codeBuf.position(0); for (BaseByteCodeHandler handler : handlers) { handler.read(codeBuf, classFile); } System.out.println("class文件结构解析完成,解析是否正常(剩余未解析的字节数):" + codeBuf.remaining()); return classFile; } }
最后我们还需要对所有解析器进行单元测试,此处单元测试重点关注解析完成后,class文件字节缓存中是否还有未读取的字节,如果有说明某个解析器的某个解析步骤出错了,如果没有,则所有解析器都正常工作。
重点关注ClassFileAnalysiser的analysis方法中的打印语句输出的结果。单元测试如代码清单2-56所示。
代码清单2-56 测试整个框架的解析是否正常
public class AllHandlerTest { @Test public void test() throws Exception { ByteBuffer codeBuf = ClassFileAnalysisMain.readFile("RecursionAlgorithmMain.class"); ClassFile classFile = ClassFileAnalysiser.analysis(codeBuf); } }
单元测试结果输出如图2.11所示。
图2.11 解析框架单元测试结果
从结果输出中可以看出,所有解析器解析完成后,class文件字节缓存ByteBuffer对象剩余可读的字节数为0,即ByteBuffer对象的读指针与limit指针重合,表示读完,因此解析正常。
至此,我们对整个class文件结构的解析工作就已经基本完成了。而对于属性的解析,我们都只是使用通用的解析器解析。在《Java虚拟机规范》Java SE 8版本中,预定义属性就有23个,但本书不会对每个属性都进行详细介绍。
如果想要深入理解某个属性,我们可再对其进行二次解析。如何使用我们编写的项目对class文件结构、字段结构、方法结构的属性表中的属性进行二次解析呢?我们以字段的ConstantValue属性为例。