Java虚拟机字节码:从入门到实战
上QQ阅读APP看书,第一时间看更新

解析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属性为例。