通过Java从.class文件中获取ByteCode(依赖)信息

JF *_*ier 1 java bytecode java-8 jdeps

我想分析.class文件并获取有关哪个类使用哪个其他类的信息。

jdeps 是一个命令行工具,它允许您在控制台中显示一些信息,但我想避免调用外部工具并抓取命令行输出。

Hol*_*ger 5

所有依赖项都记录在类文件的中心位置,即常量池。因此,为了有效地处理所有依赖项,您需要一个允许在不查看类文件的其余部分的情况下处理常量池的库(这排除了 ASM,否则它是一个非常好的字节码处理库)。

所以使用,例如Javassist,你可以做这个工作

private static Set<String> getDependencies(InputStream is) throws IOException {
    ClassFile cf = new ClassFile(new DataInputStream(is));
    ConstPool constPool = cf.getConstPool();
    HashSet<String> set = new HashSet<>();
    for(int ix = 1, size = constPool.getSize(); ix < size; ix++) {
        int descriptorIndex;
        switch (constPool.getTag(ix)) {
            case ConstPool.CONST_Class: set.add(constPool.getClassInfo(ix));
            default: continue;
            case ConstPool.CONST_NameAndType:
                descriptorIndex = constPool.getNameAndTypeDescriptor(ix);
                break;
            case ConstPool.CONST_MethodType:
                descriptorIndex = constPool.getMethodTypeInfo(ix);
        }
        String desc = constPool.getUtf8Info(descriptorIndex);
        for(int p = 0; p<desc.length(); p++)
            if(desc.charAt(p)=='L')
                set.add(desc.substring(++p, p = desc.indexOf(';', p)).replace('/', '.'));
    }
    return set;
}
Run Code Online (Sandbox Code Playgroud)

测试它

try(InputStream is = String.class.getResourceAsStream("String.class")) {
    set = getDependencies(is);
}
set.stream().sorted().forEachOrdered(System.out::println);
Run Code Online (Sandbox Code Playgroud)

显示

private static Set<String> getDependencies(InputStream is) throws IOException {
    ClassFile cf = new ClassFile(new DataInputStream(is));
    ConstPool constPool = cf.getConstPool();
    HashSet<String> set = new HashSet<>();
    for(int ix = 1, size = constPool.getSize(); ix < size; ix++) {
        int descriptorIndex;
        switch (constPool.getTag(ix)) {
            case ConstPool.CONST_Class: set.add(constPool.getClassInfo(ix));
            default: continue;
            case ConstPool.CONST_NameAndType:
                descriptorIndex = constPool.getNameAndTypeDescriptor(ix);
                break;
            case ConstPool.CONST_MethodType:
                descriptorIndex = constPool.getMethodTypeInfo(ix);
        }
        String desc = constPool.getUtf8Info(descriptorIndex);
        for(int p = 0; p<desc.length(); p++)
            if(desc.charAt(p)=='L')
                set.add(desc.substring(++p, p = desc.indexOf(';', p)).replace('/', '.'));
    }
    return set;
}
Run Code Online (Sandbox Code Playgroud)

(在 Java 9 上)


您可以使用BCEL获得相同的结果:

private static Set<String> getDependencies(InputStream is) throws IOException {
    JavaClass cf = new ClassParser(is, "").parse();
    ConstantPool constPool = cf.getConstantPool();
    HashSet<String> set = new HashSet<>();
    constPool.accept(new DescendingVisitor(cf, new EmptyVisitor() {
        @Override public void visitConstantClass(ConstantClass cC) {
            set.add(((String)cC.getConstantValue(constPool)).replace('/', '.'));
        }
        @Override public void visitConstantNameAndType(ConstantNameAndType cNaT) {
            processSignature(cNaT.getSignature(constPool));
        }
        @Override public void visitConstantMethodType(ConstantMethodType cMt) {
            processSignature(
                constPool.constantToString(cMt.getDescriptorIndex(),
                (byte)ConstPool.CONST_Utf8));
        }
        private void processSignature(String desc) {
            for(int p = 0; p<desc.length(); p++)
                if(desc.charAt(p)=='L')
                    set.add(desc.substring(++p, p=desc.indexOf(';', p)).replace('/', '.'));
        }
    }));
    return set;
}
Run Code Online (Sandbox Code Playgroud)

  • @JFMeier 这些名称采用 [“二进制名称”](https://docs.oracle.com/javase/9​​/docs/api/java/lang/ClassLoader.html#name) 格式,而`[B`, `[C`、`[I` 表示 `byte[]`、`char[]` 和 `int[]`。`[Ljava.lang.CharSequence;` 和 `[Ljava.lang.String;` 指的是 `CharSequence[]` 和 `String[]`。基本上,它是JVM内部名称,但`/`替换为`.`。 (2认同)