访问class
访问一个class的最简单的方式是声明一个ClassReader类,然后复写其中的方法。ClassReader可以接受类全称、byte数组或者Inputstream的参数。
生成class
生成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();
我们生成class文件后,如果要使用自己生成的class只需自定义classloader即可。一种方案是实现classloader的definClass即可,另一种是复写findClass方法。
翻译class
如果我们期望修改或者翻译某个class文件,我们可以联合使用ClassReader和ClassWriter
byte[] b1 =...;
ClassWriter cw = new ClassWriter(0);
ClassReader cr = new ClassReader(b1);
cr.accept(cw, 0);
byte[] b2 = cw.toByteArray(); // b2 represents the same class as b1
或者中间再插入几个Filter
byte[] b1 =...;
ClassWriter cw = new ClassWriter(0); // cv forwards all events to cw
ClassVisitor cv = new ClassVisitor(ASM4, cw) {
};
ClassReader cr = new ClassReader(b1);
cr.accept(cv, 0);
byte[] b2 = cw.toByteArray(); // b2 represents the same class as b1
简单优化
类的修改的代码大部分都是只修改类的一小部分组件,这样如果解析整个类其效率就不高。
如果ClassReader检测到ClassVisitor返回的MethodVisitor是从ClassWriter产生的,并且被传递给自己的accept方法,那么这个方法就不会被翻译,系统压根看不到这个方法。
-
这种情况下,ClassReader并不解析这个方法,也不会产生相应的事件,只是将整个字节流拷贝到对应的ClassWriter。
byte[] b1 =... ClassReader cr = new ClassReader(b1); ClassWriter cw = new ClassWriter(cr, 0); ChangeVersionAdapter ca = new ChangeVersionAdapter(cw); cr.accept(ca, 0); byte[] b2 = cw.toByteArray();
不过以上优化对于添加成员的修改是非常有效的,由于这个优化需要拷贝所有的常量定义,因此对于重命名、删除组件的翻译会导致生成的class文件比未优化版本大。