什么是树API
就是采用基于对象的模型,类可以用一个对象树表示,每个对象表示类的一部分,比如类本身、一个字段、一个方法、一条指令,等等,每个对象都有一些引用,指向表示其组成部分的对象。
生成类
ClassNode cn = new ClassNode();
cn.version = V1_8;
cn.access = ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE;
cn.name = "pkg/Comparable";
cn.superName = "java/lang/Object";
cn.interfaces.add("pkg/Mesurable");
cn.fields.add(new FieldNode(ACC_PUBLIC + ACC_FINAL + ACC_STATIC,
"LESS", "I", null, new Integer(-1)));
cn.fields.add(new FieldNode(ACC_PUBLIC + ACC_FINAL + ACC_STATIC,
"EQUAL", "I", null, new Integer(0)));
cn.fields.add(new FieldNode(ACC_PUBLIC + ACC_FINAL + ACC_STATIC,
"GREATER", "I", null, new Integer(1)));
cn.methods.add(new MethodNode(ACC_PUBLIC + ACC_ABSTRACT,
"compareTo", "(Ljava/lang/Object;)I", null, null));
使用树API生成类时,需要多花费大约30%的时间,占用的内存也多于使用核心API。但可以按任意顺序生成类元素,这在一些情况下可能非常方便。
添加和删除类成员
添加和删除类就是在ClassNode对象的fields或methods列表中添加或删除元素
public class ClassTransformer {
protected ClassTransformer ct;
public ClassTransformer(ClassTransformer ct) {
this.ct = ct;
}
public void transform(ClassNode cn) {
if (ct != null) {
ct.transform(cn);
}
}
}
public class RemoveMethodTransformer extends ClassTransformer {
private String methodName;
private String methodDesc;
public RemoveMethodTransformer(ClassTransformer ct, String methodName, String methodDesc) {
super(ct);
this.methodName = methodName;
this.methodDesc = methodDesc;
}
@Override
public void transform(ClassNode cn) {//重点在这里
Iterator<MethodNode> i = cn.methods.iterator();
while (i.hasNext()) {
MethodNode mn = i.next();
if (methodName.equals(mn.name) && methodDesc.equals(mn.desc)) {
i.remove();
}
}
super.transform(cn);
}
}
public class AddFieldTransformer extends ClassTransformer {
private int fieldAccess;
private String fieldName;
private String fieldDesc;
public AddFieldTransformer(ClassTransformer ct, int fieldAccess, String fieldName, String fieldDesc) {
super(ct);
this.fieldAccess = fieldAccess;
this.fieldName = fieldName;
this.fieldDesc = fieldDesc;
}
@Override
public void transform(ClassNode cn) {//重点在这里
boolean isPresent = false;
for (FieldNode fn : cn.fields) {
if (fieldName.equals(fn.name)) {
isPresent = true;
break;
}
}
if (!isPresent) {
cn.fields.add(new FieldNode(fieldAccess, fieldName, fieldDesc, null, null));
}
super.transform(cn);
}
}
它与核心 API 的主要区别是需要迭代所有方法,而在使用核心 API 时是不需要这样做的(这一工作会在 ClassReader 中为你完成)。事实上,这一区别对于几乎所有基于树的转换都是有效的。
和生成类的情景一样,使用树 API 转换类时,所花费的时间和占用的内存也要多于使用核 心 API 的时候。但使用树 API 有可能使一些转换的实现更为容易。比如有一个转换,要向一个类中添加注释,包含其内容的数字签名,就属于上述情景。在使用核心 API 时,只有在访问了 整个类之后才能计算数字签名,但这时再添加包含其内容的注释就太晚了,因为对注释的访问必须位于类成员之前。而在使用树 API 时,这个问题就消失了,因为这时不存在此种限制。
类的生成和转化
上面只是如何创建和转换 ClassNode 对象,但还没有看到如何由一个类的字节数组表示来构造一个 ClassNode,或者反过来,由 ClassNode 构造这个字节数组。要由字节数组构建 ClassNode,可以将它与 ClassReader 合在一起,使 ClassReader 生成的事件可供 ClassNode 组件使用,从而初始化其字段
ClassNode cn = new ClassNode(ASM4);
ClassReader cr = new ClassReader(...);
cr.accept(cn, 0);
... // 可以在这里根据需要转换 cn
ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
byte[] b = cw.toByteArray();
类转换器有两种常见模式。
- 使用继承:
public class MyClassAdapter extends ClassNode {
public MyClassAdapter(ClassVisitor cv) {
super(ASM7);
this.cv = cv;
}
@Override
public void visitEnd() {
// put your transformation code here
accept(cv);
}
}
- 使用委托
public class MyClassAdapter extends ClassVisitor {
ClassVisitor next;
public MyClassAdapter(ClassVisitor cv) {
super(ASM7, new ClassNode());
next = cv;
}
@Override
public void visitEnd() {
ClassNode cn = (ClassNode) cv;
// 将转换代码放在这里
cn.accept(next);
}
}
方法转换
到目前为止,我们仅看到了如何创建和转换 MethodNode 对象,却还没有看到与类的字节数组表示进行链接。方法转换的模式其实也有两种
- 基于继承的模式
public class MyMethodAdapter extends MethodNode {
public MyMethodAdapter(int access, String name, String desc, String signature, String[] exceptions, MethodVisitor mv) {
super(ASM7, access, name, desc, signature, exceptions);
this.mv = mv;
}
@Override
public void visitEnd() {
// 将你的转换代码放在这儿
accept(mv);
}
}
- 基于委托的模式
public class MyMethodAdapter extends MethodVisitor {
MethodVisitor next;
public MyMethodAdapter(int access, String name, String desc, String signature, String[] exceptions, MethodVisitor mv) {
super(ASM7, new MethodNode(access, name, desc, signature, exceptions));
next = mv;
}
@Override
public void visitEnd() {
MethodNode mn = (MethodNode) mv;
//将你的转换代码放在这儿
mn.accept(next);
}
}
这些模式表明,可以将树 API 仅用于方法,将核心 API 用于类。在实践中经常使用这一策略。
用MethodNode生成一个方法
假设有如下java方法
public void checkAndSetF(int f) {
if (f >= 0) {
this.f = f;
} else {
throw new IllegalArgumentException();
}
}
他对应的字节码如下
ILOAD 1
IFLT label
ALOAD 0
ILOAD 1
PUTFIELD pkg/Bean f I
GOTO end
label:
NEW java/lang/IllegalArgumentException
DUP
INVOKESPECIAL java/lang/IllegalArgumentException <init> ()V
ATHROW
end:
RETURN
那么对照字节码可以写出如下的生成代码
MethodNode mn = new MethodNode(...);
InsnList il = mn.instructions;
il.add(new VarInsnNode(ILOAD, 1));
LabelNode label = new LabelNode();
il.add(new JumpInsnNode(IFLT, label));
il.add(new VarInsnNode(ALOAD, 0));
il.add(new VarInsnNode(ILOAD, 1));
il.add(new FieldInsnNode(PUTFIELD, "pkg/Bean", "f", "I"));
LabelNode end = new LabelNode();
il.add(new JumpInsnNode(GOTO, end));
il.add(label);
il.add(new FrameNode(F_SAME, 0, null, 0, null));
il.add(new TypeInsnNode(NEW, "java/lang/IllegalArgumentException"));
il.add(new InsnNode(DUP));
il.add(new MethodInsnNode(INVOKESPECIAL,"java/lang/IllegalArgumentException", "<init>", "()V"));
il.add(new InsnNode(ATHROW));
il.add(end);
il.add(new FrameNode(F_SAME, 0, null, 0, null));
il.add(new InsnNode(RETURN));
mn.maxStack = 2;
mn.maxLocals = 2;
由此可见,要想对ASM的树API有很好的了解,必须对字节码有很好的了解
网友评论