获取class信息 动态生成Class 使用生成的类 修改已存在的Class 优化 删除成员 添加成员
[asm4-guide](http://download.forge.objectweb.org/asm/asm4-guide.pdf)学习心得
获取class信息 下来的示例中我们通过重写ClassVisitor相关函数然后依次打印出类型信息, 字段信息和函数信息.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class ClassPrinter extends ClassVisitor { public ClassPrinter () { super (Opcodes.ASM4); } 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("}" ); } }
然后我们写一段运行代码
1 2 3 4 5 6 7 8 9 public class Test { public static void main (String[] args) throws IOException { ClassReader cr = new ClassReader("Test" ); ClassPrinter cp = new ClassPrinter(); cr.accept(cp, 0 ); } }
结果为
1 2 3 4 5 6 Test extends java/lang/Object { <init>()V main ([Ljava/lang/String;) V lambda$main$22(Ljava/lang/Integer;) V lambda$main$21(Ljava/lang/Integer;Ljava/lang/Integer;) I }
在测试代码中我们首先创建了一个ClassReader实例用于读取Test字节码. 然后由accept()方法依次调用ClassPrinter的方法
动态生成Class 我们仅仅使用ClassWriter就可以生成一个类, 例如我们要生成一个如下的接口
1 2 3 4 5 6 7 package pkg;public interface Comparable extends Mesurable { int LESS = -1 ; int EQUAL = 0 ; int GREATER = 1 ; int compareTo (Object o) ; }
我们仅仅需要调用ClassVisitor的六个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class Test { public static void main (String[] args) throws IOException { ClassWriter cw = new ClassWriter(0 ); cw.visit(V1_8, 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.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "compareTo" , "(Ljava/lang/Object;)I" , null , null ) .visitEnd(); cw.visitEnd(); byte [] b = cw.toByteArray(); } }
使用生成的类 记下来我们自定义一个ClassLoader来加载生成的字节码
1 2 3 4 5 class MyClassLoader extends ClassLoader { public Class defineClass (String name, byte [] b) { return defineClass(name, b, 0 , b.length); } }
然后使用它
1 2 3 byte [] bytes = genComparableInterface();MyClassLoader myClassLoader = new MyClassLoader(); Class c = myClassLoader.defineClass("pkg.Comparable" , bytes);
我们直接使用defineClass函数来加载这个类.
另外我们还可以重写findClass这个函数来动态的生成我们所需要的类
1 2 3 4 5 6 7 8 9 10 11 12 class StubClassLoader extends ClassLoader { @Override protected Class findClass (String name) throws ClassNotFoundException { if (name.endsWith("_Stub" )) { ClassWriter cw = new ClassWriter(0 ); ... byte [] b = cw.toByteArray(); return defineClass(name, b, 0 , b.length); } return super .findClass(name); } }
修改已存在的Class 在上篇文章中我们只是单独的使用了ClassReader和ClassWriter,但是更多的应用其实应该是将其组合到一起使用
1 2 3 4 5 byte [] b1 = ...;ClassWriter cw = new ClassWriter(0 ); ClassReader cr = new ClassReader(b1); cr.accept(cw, 0 ); byte [] b2 = cw.toByteArray();
这个例子中我们什么都没有做, 只不过完成了一个copy字节码的功能, 接下来我们在这俩个过程中加入ClassVisitor
1 2 3 4 5 6 7 byte [] b1 = ...;ClassWriter cw = new ClassWriter(0 ); ClassVisitor cv = new ClassVisitor(ASM4, cw) { }; ClassReader cr = new ClassReader(b1); cr.accept(cv, 0 ); byte [] b2 = cw.toByteArray();
这段代码的处理流程如下图
方框代表我们的核心组件, 箭头代表我们的数据流.
下面我们给出一个ClassVisitor小例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class ChangeVersionAdapter extends ClassVisitor { public ChangeVersionAdapter (ClassVisitor cv) { super (ASM4, 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函数(修改类本身函数, 将class版本号转化为1.5), 其他的方法都没有重写,因此他们什么改变都不会做. 下来我们给出这个类执行的时序图 从这个时序图中我们可以看出, 用户调用了accept方法之后, 有ASM自动调用ClassReader的visti(version)方法, 接着调用ChangeVersionAdapter的visti(1.5)方法, 最后调用ClassWriter的相关方法. 从这个模式中我们可以看出, ASM的调用模式是链式调用的, 先调用visit, 然后调用责任链中所有的ClassVisitor的vist最后调用ClassWriter的完结方法. 当visit调用完之后再调用visitSource责任链流程, 依次类推下去.
优化 在上述的代码中, 其实代码的运行效率并不是高效进行的. 这是因为当b1字节码被ClassReader读取并通过ClassVisitor将其执行转换的时候, 我们可能只改变了class的版本号, 其他部分并没有转换, 但是在实际的执行中其他的部分也都被执行了一边, 那这就浪费了cpu计算和内存空间的占用, 其实只需要将不需要改变的字节从b1直接拷贝到b2就好了.
好在ASM为我们内部构建了这种优化过程. *
删除成员 如果我们想将class中的某个成员删除掉, 那么只需在执行asm责任链调用时, 中断调用过程(不调用super或者直接return)就可以了.
例如我们下面的例子我们将类中的内部类和外部类以及编译成该class的源文件信息删除掉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class RemoveDebugAdapter extends ClassVisitor { public RemoveDebugAdapter (ClassVisitor cv) { super (ASM4, 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) { } }
看,就是如此简单, 我们在这三个方法内部什么都不做(不进行super调用)就轻松地完成了我们需要的功能, 但是这种做法却并不适合 字段和方法的删除, 因为在字段和方法的删除中除了不进行super调用之外还需要return null, 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class RemoveMethodAdapter extends ClassVisitor { private String mName; private String mDesc; public RemoveMethodAdapter ( ClassVisitor cv, String mName, String mDesc) { super (ASM4, 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)) { return null ; } return cv.visitMethod(access, name, desc, signature, exceptions); } }
添加成员 当我们中断方法调用的时候,会删除成员. 但是当我们在责任链中的原生方法调用(visitXxx方法)中新增加一些方法调用的话, 会增加成员.
例如如果你想要增加一个字段, 那么你必须在visitXxx方法中增加一个visitField方法调用. 需要注意的是visitXxx方法只包含visitInnerClass,visitField, visitMethod,visitEnd这四个方法, 这是因为visit,visitSource,visitOuterClass,visitAnnotation,visitAttribute 这些方法正如我们在第一篇文章中给出那些顺序一样, visitField方法只能在这些方法之后调用.
需要注意的是,由于visitInnerClass,visitField, visitMethod这些方法会进行多次调用, 因此有可能会添加N个相同的成员, 因此我们建议在visitEnd的时候进行成员添加, 这是因为这个方法总会有且只有一次调用.
如下例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class AddFieldAdapter extends ClassVisitor { private int fAcc; private String fName; private String fDesc; private boolean isFieldPresent; public AddFieldAdapter (ClassVisitor cv, int fAcc, String fName, String fDesc) { super (ASM4, 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(); } }