【问题标题】:When visiting a class using ASM, how to know the ancestor class of that class without loading any class?使用 ASM 访问一个类时,如何在不加载任何类的情况下知道该类的祖先类?
【发布时间】:2020-07-26 14:11:00
【问题描述】:

我想做的是:我有一个ClassVisitor适配器,当进入visit方法时,我想知道AncestorClass这个类是不是那个类的祖先类?我曾尝试像这样使用反射 (Class.forname(...)):

MyTransformer.class:

public class MyTransformer implements ClassFileTransformer {

    public byte[] transform(ClassLoader loader, String className,
                            Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {

        if (className == null){
            return classfileBuffer;
        }
        ClassReader cr = new ClassReader(classfileBuffer);
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES);
        CollectionCVAdapter mca = new MyCVAdapter(cw, className);
        cr.accept(mca, 0);
        return cw.toByteArray();
    }
}

MyCVAdapter.class:

class MyCVAdapter extends ClassVisitor {

    private String className;
    private boolean isDescendantClass;

    CollectionCVAdapter(ClassVisitor classVisitor, String className){
        super(Opcodes.ASM5, classVisitor);
        this.className = className;
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        // check if this class is a descendant class of AncestorClass.class
        try{
            Class superClass = Class.forName(superName.replace("/", "."));
            do {
                if (superClass == AncestorClass.class) {
                    this.isDescendantClass= true;
                    break;
                }
                superClass = superClass.getSuperclass();
            } while (superClass != null);
        }catch (Exception e){
            e.printStackTrace();
        }
        super.visit(version, access, name, signature, superName, interfaces);
    }
    ...
}

但问题是我还想检测已检测类的父类。我发现一旦Class.forName()加载了父类,父类就不会进入transform方法。这意味着不能再检测父类。那么是否有任何替代方法可以确定该类是否是AncestorClass 的后代类?

【问题讨论】:

  • 第一:使用ClassReader.getSuperName()。第二:用loader.getResourceAsStream(superName + ".class")加载类文件。用 ClassReader 解析它。
  • 使用经典的ClassLoader 架构,没有万无一失的方法。 getResourceAsStream(internalName + ".class") 是在不加载类的情况下获取字节码的最佳方式,只要特定的类加载器实现始终如一地提供类文件的内容作为资源(所有标准加载器都这样做)。对于模块化代码,代码总是由委派给ModuleReader 的标准加载器加载以获取字节码,因此getResourceAsStream 将始终与loadClass 在同一个ModuleLayer 内保持一致。
  • @Holger 你能解释更多关于ModuleReader 的信息吗?我以前没听说过。在transform 方法中,我可以获得ClassLoader 参数,以使用getResourceAsStream。但在其他情况下,当我没有可用的ClassLoader 并且我想使用getResourceAsStream 来获取字节码时,我通常使用Class.forName(className).getClassLoader().getResourceAsStream。实际上,我不确定哪个加载器可以给我想要的字节码。还有更优雅的方式吗?
  • 由于您正在实现ClassFileTransformer,因此您应该使用作为transform 方法的第一个参数提供的ClassLoader。如果是null,则使用ClassLoader.getSystemResourceAsStream(…)
  • 但是如果你检查的基类不是系统类,你可以排除系统类成为它的子类,所以当类加载器参数是null时你可以直接跳过。同样,只要类名以 java/ 开头,您就可以使用快捷方式。

标签: java jvm instrumentation java-bytecode-asm javaagents


【解决方案1】:

感谢@JohannesKuhn 和@Holger,我终于找到了静态分析继承关系的方法。

    @Override
    public void visit(int version, int access, String name, String signature, String superSlashName, String[] interfaces) {
        String originalSuperName = superSlashName;
        // check if this class is a subclass of AncestorClass.class
        try{
            while (superSlashName != null){
                if (superSlashName.equals(AncestorClassName)){
                    this.isDescendantClass= true;
                    break;
                }else{
                    InputStream is = this.loader.getResourceAsStream(superSlashName + ".class");
                    byte[] superBytes = FileUtil.loadByteCode(is);
                    ClassReader parentCr = new ClassReader(superBytes);
                    superSlashName = parentCr.getSuperName();
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        super.visit(version, access, name, signature, originalSuperName, interfaces);
    }

【讨论】:

    猜你喜欢
    • 2013-05-21
    • 1970-01-01
    • 1970-01-01
    • 2016-07-19
    • 1970-01-01
    • 1970-01-01
    • 2010-10-30
    • 2016-01-13
    • 1970-01-01
    相关资源
    最近更新 更多