如何使用 ASM 重写 visitMethod 以将两个 class 合并为一个 class

How to rewrite visitMethod to merge two classes into one class using ASM

我试图在运行时通过 ASM API 将 class Callee 合并到 class Caller。我下面的部分代码是从 3.1.5(Merging Two Classes into One) 中的 http://asm.ow2.org/current/asm-transformations.pdf 复制的。我修改了示例代码,因为我使用的是 ASM 5.0 版本。

public class Caller {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        new Caller().test("xu", "shijie");
    }

    public void test(String a, String b){
        Callee obj = new Callee(a,b);
        System.out.println(obj.calculate(10));

        System.out.println("1..........");
    }
}
public class Callee {

    final String concat;
    public Callee(String a, String b){
        concat = a+b;
    }

    public String calculate(int t){
        return concat+t;
    }

}

class MergeAdapter extends ClassVisitor {
    private ClassNode cn;
    private String cname;

    public MergeAdapter(ClassVisitor cv, ClassNode cn) {
        super(Opcodes.ASM5, cv);
        this.cn = cn;
    }

    public void visit(int version, int access, String name, String signature,
            String superName, String[] interfaces) {
        super.visit(version, access, name, signature, superName, interfaces);
        this.cname = name;
    }

    public MethodVisitor visitMethod(int access, String name, String desc,
            String signature, String[] exceptions) {
        return super.visitMethod(access, name, desc, signature, exceptions);
    }

    public void visitEnd() {
        for (Iterator<FieldNode> it = cn.fields.iterator(); it.hasNext();) {
            ((FieldNode) it.next()).accept(this);
        }
        for (Iterator it = cn.methods.iterator(); it.hasNext();) {
            MethodNode mn = (MethodNode) it.next();
            String[] exceptions = new String[mn.exceptions.size()];
            mn.exceptions.toArray(exceptions);
            MethodVisitor mv = cv.visitMethod(mn.access, mn.name, mn.desc,
                    mn.signature, exceptions);
            mn.instructions.resetLabels();
            mn.accept(new RemappingMethodAdapter(mn.access, mn.desc, mv,
                    new SimpleRemapper(cn.name, cname)));
        }
        super.visitEnd();
    }
}

public class Main extends ClassLoader{

    public byte[] generator(String caller, String callee) throws ClassNotFoundException{
        String resource = callee.replace('.', '/') + ".class";
        InputStream is =  getResourceAsStream(resource);
        byte[] buffer;
        // adapts the class on the fly
        try {
            ClassReader cr = new ClassReader(is);
            ClassNode classNode = new ClassNode();
            cr.accept(classNode, 0);

            resource = caller.replace('.', '/')+".class";
            is = getResourceAsStream(resource);
            cr = new ClassReader(is);
            ClassWriter cw = new ClassWriter(0);
            ClassVisitor visitor = new MergeAdapter(cw, classNode);
            cr.accept(visitor, 0);

            buffer= cw.toByteArray();

        } catch (Exception e) {
            throw new ClassNotFoundException(caller, e);
        }

        // optional: stores the adapted class on disk
        try {
            FileOutputStream fos = new FileOutputStream("/tmp/data.adapted");
            fos.write(buffer);
            fos.close();
        } catch (IOException e) {}
        return buffer;
     }


      @Override
      protected synchronized Class<?> loadClass(final String name,
                final boolean resolve) throws ClassNotFoundException {
            if (name.startsWith("java.")) {
                System.err.println("Adapt: loading class '" + name
                        + "' without on the fly adaptation");
                return super.loadClass(name, resolve);
            } else {
                System.err.println("Adapt: loading class '" + name
                        + "' with on the fly adaptation");
            }
            String caller = "code.sxu.asm.example.Caller";
            String callee = "code.sxu.asm.example.Callee";
            byte[] b = generator(caller, callee);
            // returns the adapted class
            return defineClass(caller, b, 0, b.length);
        }

        public static void main(final String args[]) throws Exception {
            // loads the application class (in args[0]) with an Adapt class loader
            ClassLoader loader = new Main();
            Class<?> c = loader.loadClass(args[0]);
            Method m = c.getMethod("main", new Class<?>[] { String[].class });
            String[] applicationArgs = new String[args.length - 1];
            System.arraycopy(args, 1, applicationArgs, 0, applicationArgs.length);
            m.invoke(null, new Object[] { applicationArgs });
        }
}

上面的主要问题是新创建的Callee对象

            **Callee obj = new Callee(a,b);**

Caller::test(String a, String b)的正文中还在,生成的测试字节码是:

public void test(java.lang.String, java.lang.String);
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=4, args_size=3
         0: new           #26                 // class code/sxu/asm/example/Callee
         3: dup           
         4: aload_1       
         5: aload_2       
         6: invokespecial #28                 // Method code/sxu/asm/example/Callee."<init>":(Ljava/lang/String;Ljava/lang/String;)V
         9: astore_3      
        10: getstatic     #34                 // Field java/lang/System.out:Ljava/io/
PrintStream;

这是不正确的。因此,当 m.invoke() (重新加载)在 main 方法结束时,这将导致 "attempted duplicate class definition for name:" 异常。

我认为 class 调用者中的所有所有者:code/sxu/asm/example/Callee 也应该映射到 code/sxu/asm/example/Caller。因此我重写了

public MethodVisitor visitMethod(int access, String name, String desc,
            String signature, String[] exceptions) {
        return super.visitMethod(access, name, desc, signature, exceptions);
    }

但我不确定如何在 visitMethod 的主体中实现。谁能给个建议?

public MethodVisitor visitMethod(int access, String name, String desc,
        String signature, String[] exceptions) {

    MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
    return new RemappingMethodAdapter(access, desc, mv,
                new SimpleRemapper(cn.name, cname));
}