Presentation
ASM生成和转换编译后class的API是基于ClassVisitor接口。简单部分通过简单方法调用(参数描述它们的内容,且返回类型为void)来访问。哪些内容长度不定且复杂的部分可以通过初始方法调用来访问,它返回附加的访问接口。visitAnnotation,visitField和visitMethod方法各自返回AnnotationVisitor、FieldVisitor和MethodVisitor。
public interface ClassVisitor {
void visit(int version, int access, String name, String signature,
String superName, String[] interfaces);
void visitSource(String source, String debug);
void visitOuterClass(String owner, String name, String desc);
AnnotationVisitor visitAnnotation(String desc, boolean visible);
void visitAttribute(Attribute attr);
void visitInnerClass(String name, String outerName, String innerName,int access);
FieldVisitor visitField(int access, String name, String desc,
String signature, Object value);
MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions);
void visitEnd();
}
public interface FieldVisitor {
AnnotationVisitor visitAnnotation(String desc, boolean visible);
void visitAttribute(Attribute attr);
void visitEnd();
}
ClassVisitor接口必须按以下顺序调用:visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )* ( visitInnerClass | visitField | visitMethod )* visitEnd
意味着,visit必须第一个被调用,紧接着最多调用一次visitSource,接着最多调用一次visitOuterClass,接着顺序调用多次visitAnnotation和visitAttribute,接着多次顺序调用visitInnerClass,visitFieldvisitMethod,调用最后visitEnd结束。ASM提供了三个基于ClassVisitor接口的核心组件来生成和转换class。
ASM提供了三个基于ClassVisitor接口的核心组件来生成和转换class。
CLassReader:将编译过的class转换为字节数组,且调用ClassVisitor实例的visitXxx方法,ClassVisitor作为参数传递给accept方法。可以看作是事件生产者。
ClassWriter:ClassVisitor接口的一个实现,它以二进制格式直接构建编译后的class。它产生包含编译后的class的二进制字节数组作为输出。可以看作是事件消费者。
ClassAdapter:ClassVisitor接口的一个实现,它委托所有来自另外一个ClassVisitor实例的方法调用。它可以看作是过滤器。
Parsing classes
转换一个已存在public class ClassPrinter implements ClassVisitor {
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
System.out.println(name + " extends " + superName + " {");
}
public void visitSource(String source, String debug) {
}
public void visitOuterClass(String owner, String name, String desc) {
}
public AnnotationVisitor visitAnnotation(String desc,boolean visible) {
return null;
}
public void visitAttribute(Attribute attr) {
}
public void visitInnerClass(String name, String outerName,String innerName, int access) {
}
public FieldVisitor visitField(int access, String name, String desc,String signature, Object value) {
System.out.println("" + desc + " " + name);
return null;
}
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
System.out.println("" + name + desc);
return null;
}
public void visitEnd() {
System.out.println("}");
}
}
第二步Classprinter与ClassReader一起使用,以便ClassReader产生的事件能被ClassPrinter来消费。
ClassPrinter cp = new ClassPrinter();
ClassReader cr = new ClassReader("java.lang.Runnable");
cr.accept(cp, 0);
accept方法在Runnable类字节码最后一行被转换后被调用,并且会调用cp上的ClassVisitor方法。
Generating classes
生成Class的唯一组件是ClassWriter。package pkg;
public interface Comparable extends Mesurable {
int LESS = -1;
int EQUAL = 0;
int GREATER = 1;
int compareTo(Object o);
}
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
"pkg/Comparable", null, "java/lang/Object",
new String[] { "pkg/Mesurable" });
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "LESS", "I",
null, new Integer(-1)).visitEnd();
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "EQUAL", "I",
null, new Integer(0)).visitEnd();
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "GREATER", "I",
null, new Integer(1)).visitEnd();
cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo",
"(Ljava/lang/Object;)I", null, null).visitEnd();
cw.visitEnd();
byte[] b = cw.toByteArray();
visit方法定义了class的头信息。V1_5是在ASM Opcodes接口中定义的常量,它指定了class的版本。ACC_XXX常量对应Java修饰符。这里我们指定
连续的调用visitField方法,用来定义三个接口域,第一个参数是修饰符,第二个参数是field的名称,第三个参数是field类型。第四个参数泛型,在这里没有使用泛型,故为null。最后一个参数是field的常量值,这个参数仅仅被常量域使用。这里没有annotation,调用visitEnd方法。 visitMethod调用被用来定义compareTo方法。第一个参数是修饰符,第二个是方法名称。第三个参数是方法的描述。第四个为泛型,最后一个参数为方法能抛出的异常数组。visitMethod方法返回MethodVisitor,用来定义方法的annotation、属性和最为重要的方法的代码。接着调用visitEnd方法返回MethodVisitor。
最后一次调用visitEnd用来通知cw类已经结束,调用toByteArray获取字节数组。
可以使用自定义ClassLoader来加载生成的class。
class MyClassLoader extends ClassLoader {
public Class defineClass(String name, byte[] b) {
return defineClass(name, b, 0, b.length);
}
}
Transforming classes
目前为止,ClassReader和ClassWriter组件被单独使用。联合使用这两个组件:
1)ClassReader生产的event直接传递给ClassWriter。
byte[] b1 = ...;
ClassWriter cw = new ClassWriter();
ClassReader cr = new ClassReader(b1);
cr.accept(cw, 0);
byte[] b2 = cw.toByteArray();//b2代表了与b1Class一样的类.
2)在ClassReader和ClassWriter引入ClassAdapter
byte[] b1 = ...;
ClasssWriter cw = new ClassWriter();
ClassAdapter ca = new ClassAdapter(cw); // ca forwards all events to cw
ClassReader cr = new ClassReader(b1);
cr.accept(ca, 0);
byte[] b2 = cw.toByteArray(); // b2 represents the same class as b1
转换链:ClassReader->ClassAdapter->ClassWriter
public class ChangeVersionAdapter extends ClassAdapter {
public ChangeVersionAdapter(ClassVisitor cv) {
super(cv);
}
@Override
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
cv.visit(V1_5, access, name, signature, superName, interfaces);
}
}
通过修改visit方法的其他参数,你可以完成其他转换。
Using transformed classes
转换过的class可以存储到磁盘上或使用ClassLoader加载,但是在一个ClassLoader内的class转换只能转换被该类的classLoader加载进来的类。如果你向转换所有class,你必须将转换置于ClassFileTransformer(定义在java.lang.instrument)内。public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
public byte[] transform(ClassLoader l, String name, Class c,
ProtectionDomain d, byte[] b)
throws IllegalClassFormatException {
ClassReader cr = new ClassReader(b);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new ChangeVersionAdapter(cw);
cr.accept(cv, 0);
return cw.toByteArray();
}
});
}
Removing class members
通过改变visitField和visitMethod方法中的access和name参数,就可以改变数据域和方法的访问修饰符或名字。这样作的效果就是数据域或方法被移除了。本例子就是移除外部类、内部类和源文件名称。
public class RemoveDebugAdapter extends ClassAdapter {
public RemoveDebugAdapter(ClassVisitor cv) {
super(cv);
}
@Override
public void visitSource(String source, String debug) {
}
@Override
public void visitOuterClass(String owner, String name, String desc) {
}
@Override
public void visitInnerClass(String name, String outerName,
String innerName, int access) {
}
}
下例移除了指定方法
public class RemoveMethodAdapter extends ClassAdapter {
private String mName;
private String mDesc;
public RemoveMethodAdapter(
ClassVisitor cv, String mName, String mDesc) {
super(cv);
this.mName = mName;
this.mDesc = mDesc;
}
@Override
public MethodVisitor visitMethod(int access, String name,
String desc, String signature, String[] exceptions) {
if (name.equals(mName) && desc.equals(mDesc)) {
// do not delegate to next visitor -> this removes the method
return null;
}
return cv.visitMethod(access, name, desc, signature, exceptions);
}
}
Adding class members
public class AddFieldAdapter extends ClassAdapter {
private int fAcc;
private String fName;
private String fDesc;
private boolean isFieldPresent;
public AddFieldAdapter(ClassVisitor cv, int fAcc, String fName,
String fDesc) {
super(cv);
this.fAcc = fAcc;
this.fName = fName;
this.fDesc = fDesc;
}
@Override
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
if (name.equals(fName)) {
isFieldPresent = true;
}
return cv.visitField(access, name, desc, signature, value);
}
@Override
public void visitEnd() {
if (!isFieldPresent) {
FieldVisitor fv = cv.visitField(fAcc, fName, fDesc, null, null);
if (fv != null) {
fv.visitEnd();
}
}
cv.visitEnd();
}
}