如何使用 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));
}
我试图在运行时通过 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));
}