我正在尝试通过ASM API在运行时将类Callee
合并到类Caller
。以下部分代码是从http://asm.ow2.org/current/asm-transformations.pdf中的3.1.5(Merging Two Classes into One)
复制而来的。我修改了示例代码,因为我使用的是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;
这是不正确的。因此,当main方法结束时m.invoke()(重新加载)时,这将导致“尝试重复类名定义:”异常。
我认为所有所有者:类Caller中的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的实体中实现。谁能提出建议?
答案 0 :(得分:1)
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));
}