获取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(); } }