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

解析方法表

方法表存放一个类或者接口的所有方法。与字段结构一样,方法结构也有属性表,方法编译后的字节码指令是存放在方法结构的属性表中的,对应Code属性。但不是所有方法都会有Code属性,如接口中的方法不一定会有Code属性,如抽象方法一定没有Code属性。方法包括静态方法、以及类的初始化方法<clinit>和类的实例初始化方法<init>。参照《Java虚拟机规范》,方法结构如表2-49所示。

表2-49 方法结构

其中方法名称索引、方法描述符索引与字段结构中的字段名索引和字段类型描述符索引,都是指向常量池中某个CONSTABT_Utf8_info结构的常量,属性总数与属性表也与字段结构中的一样,但不同的是,属性的结构不同,如方法有Code属性而字段没有。访问标志也与字段的访问标志有些区别,如字段有ACC_VOLATILE标志而方法没有。关于方法的访问权限及属性标志如表2-50所示。

表2-50 方法访问权限及属性标志映射表

根据表2-49方法结构创建对应的Java类MethodInfo,如代码清单2-51所示。

代码清单2-51 MethodInfo类

public class MethodInfo {

    private U2 access_flags;
    private U2 name_index;
    private U2 descriptor_index;
    private U2 attributes_count;
    private AttributeInfo[] attributes;

}

与字段表的解析流程一样,我们暂时不关心属性表的具体属性的解析,属性表的解析只使用通用属性结构解析。方法表解析器的实现如代码清单2-52所示。

代码清单2-52 方法表解析器

public class MethodHandler implements BaseByteCodeHandler {

    @Override
    public int order() {
        // 排在字段解析器的后面
        return 7;
    }

    @Override
    public void read(ByteBuffer codeBuf, ClassFile classFile)
                        throws Exception {
       classFile.setMethods_count(new U2(codeBuf.get(), codeBuf.get()));
        // 获取方法总数
        int len = classFile.getMethods_count().toInt();
        if (len == 0) {
            return;
        }
        // 创建方法表
        MethodInfo[] methodInfos = new MethodInfo[len];
        classFile.setMethods(methodInfos);
        for (int i = 0; i < methodInfos.length; i++) {
            // 解析方法
            methodInfos[i] = new MethodInfo();
            methodInfos[i].setAccess_flags(new U2(codeBuf.get(),codeBuf.get()));
            methodInfos[i].setName_index(new U2(codeBuf.get(), codeBuf.get()));
            methodInfos[i].setDescriptor_index(new U2(codeBuf.get(), codeBuf.get()));
            methodInfos[i].setAttributes_count(new U2(codeBuf.get(), codeBuf.get()));
            // 获取方法的属性总数
            int attr_len = methodInfos[i].getAttributes_count().toInt();
            if (attr_len == 0) {
                continue;
            }
            // 创建方法的属性表
            methodInfos[i].setAttributes(new AttributeInfo[attr_len]);
            for (int j = 0; j < attr_len; j++) {
                methodInfos[i].getAttributes()[j] = new AttributeInfo();
                // 解析方法的属性
                methodInfos[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());
                methodInfos[i].getAttributes()[j] .setAttribute_length(attr_info_len);
                if (attr_info_len.toInt() == 0) {
                    continue;
                }
                // 解析info
                byte[] info = new byte[attr_info_len.toInt()];
                codeBuf.get(info, 0, attr_info_len.toInt());
                methodInfos[i].getAttributes()[j].setInfo(info);
            }
        }
    }

}

将方法表解析器注册到ClassFileAnalysiser后,我们来编写单元测试。方法表解析器的单元测试与字段表解析器的单元测试逻辑差不多,如代码清单2-53所示。

代码清单2-53 方法表解析器单元测试

public class MethodHandlerTest {

    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 testMethodHandlerHandler() throws Exception {
        ByteBuffer codeBuf = ClassFileAnalysisMain.readFile("Builder.class");
        ClassFile classFile = ClassFileAnalysiser.analysis(codeBuf);
        System.out.println("方法总数:" + classFile.getMethods_count().toInt());
        System.out.println();
        MethodInfo[] methodInfos = classFile.getMethods();
    // 遍历方法表
        for (MethodInfo methodInfo : methodInfos) {
            System.out.println("访问标志和属性:" +   FieldAccessFlagUtils
.toFieldAccessFlagsString(methodInfo.getAccess_flags()));
            System.out.println("方法名:" + getName(methodInfo.getName_index(), classFile));
            System.out.println("方法描述符:"
             + getName(methodInfo.getDescriptor_index(), classFile));
            System.out.println("属性总数:" + methodInfo.getAttributes_count().toInt());
            System.out.println();
        }
    }

}

单元测试结果输出如图2.10所示。

图2.10 方法表解析器单元测试

从输出的结果可以看出,该单元测试解析的class文件,有5个方法,访问权限都是public,其中有一个方法是静态方法;这五个方法的属性表都只有一个属性,实际都是Code属性;这五个方法的方法名称分别是<init>、setA、setB、setC和main;还能看到各个方法的方法描述符。