简单介绍ASM核心API
核心类是 ClassVisitor
public abstract class ClassVisitor {
public ClassVisitor(int api) {}
public ClassVisitor(int api, ClassVisitor classVisitor) {}
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {}
public void visitSource(String source, String debug) {}
public ModuleVisitor visitModule(String name, int access, String version) {}
public void visitNestHost(String nestHost) {}
public void visitOuterClass(String owner, String name, String descriptor) {}
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {}
public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {}
public void visitAttribute(Attribute attribute) {}
public void visitNestMember(String nestMember) {}
public void visitInnerClass(String name, String outerName, String innerName, int access) {}
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {}
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {}
public void visitEnd() {}
}
它的调用顺序是首先调用 visit,然后是对 visitSource 的最多一个调用,接下来是对 visitOuterClass 的最多一个调用,然后是可按任意顺序对 visitAnnotation 和 visitAttribute 的任意多个访问,接下来是可按任意顺序对 visitInnerClass、 visitField 和 visitMethod 的任意多个调用,最后以一个 visitEnd 调用结束。
ASM 提供了三个基于 ClassVisitor API 的核心组件,用于生成和变化类:
- ClassReader类分析以字节数组形式给出的已编译类,并针对在其accept方法参数中传送的ClassVisitor实例,调用相应的visitXxx方法。这个类可以看作一个事件产生器。
- ClassWriter类是ClassVisitor抽象类的一个子类,它直接以二进制形式生成编译后的类。它会生成一个字节数组形式的输出,其中包含了已编译类,可以用toByteArray方法来取。这个类可以看作一个事件使用器。
- ClassVisitor类将它收到的所有方法调用都委托给另一个ClassVisitor类。这个类可以看作一个事件筛选器。
读取类
在分析一个已经存在的类时,惟一必需的组件是ClassReader组件.以下内容就是用来打印一个类的内容的(简单化了的)
public class ClassPrinter extends ClassVisitor {
public ClassPrinter() {
super(ASM7);
}
public ClassPrinter(ClassVisitor cv) {
super(ASM7, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
Log.e("ClassVisitor", name + " extends " + superName + " {");
}
@Override
public void visitSource(String source, String debug) {
super.visitSource(source, debug);
}
@Override
public ModuleVisitor visitModule(String name, int access, String version) {
return super.visitModule(name, access, version);
}
@Override
public void visitOuterClass(String owner, String name, String desc) {
super.visitOuterClass(owner, name, desc);
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return super.visitAnnotation(desc, visible);
}
@Override
public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
return super.visitTypeAnnotation(typeRef, typePath, desc, visible);
}
@Override
public void visitAttribute(Attribute attr) {
super.visitAttribute(attr);
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
super.visitInnerClass(name, outerName, innerName, access);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
Log.e("ClassVisitor", " " + desc + " " + name);
return super.visitField(access, name, desc, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
Log.e("ClassVisitor", " " + name + desc);
return super.visitMethod(access, name, desc, signature, exceptions);
}
@Override
public void visitEnd() {
super.visitEnd();
Log.e("ClassVisitor", "}");
}
}
接下来可以将这个 ClassPrinter 与一个 ClassReader 组件合并在一起,使 ClassReader 产生的事件由我们的 ClassPrinter 使用
ClassPrinter cp = new ClassPrinter();
ClassReader cr = new ClassReader(Runnable.class.getName());
cr.accept(cp, 0);
这是java的写法.注意ClassReader构造函数,Android写法不一样.Android的apk里面不会自己带有Runnable.class文件,所以需要如下做
ClassPrinter cp = new ClassPrinter();
File srcFile = new File(Environment.getExternalStorageDirectory() + "/TestRunnable.class");
ClassReader cr = new ClassReader(new FileInputStream(srcFile));
cr.accept(cp, 0);
生成类
//写文件
String className = "com/example/XXX/asmdemo/Comparable";//最后一个挨着class名字必须是斜杠,否则生成的class文件不会默认import
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_8, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
className, null, "java/lang/Object", null);
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();
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
File sdCardDir = Environment.getExternalStorageDirectory();
File saveFile = new File(sdCardDir, "Comparable.class");//必须是.class结尾,否则放IDE里面是乱码
FileOutputStream outStream = new FileOutputStream(saveFile);
outStream.write(b);
outStream.close();
}
最终把生成的class文件adb pull出来放AS里面查看如下
package com.example.XXX.asmdemo;
public interface Comparable {
int LESS = -1;
int EQUAL = 0;
int GREATER = 1;
int compareTo(Object var1);
}
转化类
可以通过ClassVisitor进行属性转化
byte[] b1 = cw.toByteArray();
ClassReader cr = new ClassReader(b1);
ClassWriter cw2 = new ClassWriter(0);//优化方法,加入cr------ClassWriter(cr,0)
// cv 将所有事件转发给 cw
ChangeVersionAdapter ca = new ChangeVersionAdapter(cw2);
cr.accept(ca, 0);
byte[] b2 = cw.toByteArray(); // b2 与 b1 表示同一个类
public class ChangeVersionAdapter extends ClassVisitor {
public ChangeVersionAdapter(ClassVisitor cv) {
super(ASM7, cv);
}
@Override
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
cv.visit(V1_7, access, name, signature, superName, interfaces);//就修改了java版本号
}
}
但这么做有个问题,上面的流程中整个 b1 均被分析,并 利用相应的事件从头从头构建了 b2,这种做法的效率不是很高。如果将 b1 中不被转换的部分 直接复制到 b2 中,不对其分析,也不生成相应的事件,其效率就会高得多
优化方法
在ClassReader组件的accept方法参数中传送了ClassVisitor,如果ClassReader检测到这个ClassVisitor返回的MethodVisitor来自一个ClassWriter,这意味着这个方法的内容将不会被转换,事实上,应用程序甚至不会看到其内容。在这种情况下,ClassReader组件不会分析这个方法的内容,不会生成相应事件,只是复制 ClassWriter中表示这个方法的字节数组。
缺点:对于那些增加字段、方法或指令的转换来说,这一点不 成问题,但对于那些要移除或重命名许多类成员的转换来说,这一优化将导致类文件大于未优化 时的情况。因此,仅对“增加性”转换应用这一优化。
移除类成员
public class RemoveMemberAdapter extends ClassVisitor {
private String name;
private String desc;
public RemoveMemberAdapter(int api, String name, String desc) {
super(api);
this.name = name;
this.desc = desc;
}
public RemoveMemberAdapter(int api, ClassVisitor classVisitor, String name, String desc) {
super(api, classVisitor);
this.name = name;
this.desc = desc;
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
if (this.name.equals(name) && this.desc.equals(descriptor)) {
// 不要委托至下一个访问器 -> 这样将移除该方法
return null;
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
}
增加类成员
如果要向一个类中添加一个字段,必须在原方法调用之间添加对 visitField 的一 个新调用,而且必须将这个新调用放在类适配器的一个访问方法中。比如,不能在 visit 方法 中这样做,因为这样可能会导致对 visitField 的调用之后跟有 visitSource、 visitOuterClass、visitAnnotation 或 visitAttribute,这是无效的。出于同样的原因,不能将这个新调用放在 visitSource、visitOuterClass、visitAnnotation 或 visitAttribute 方法中. 仅有的可能位置是 visitInnerClass、visitField、 visitMethod 或 visitEnd 方法。
事实上,惟一真正正确的解决方案是在 visitEnd 方法中添加更多调用,以添加新成员。实际上, 一个类中不得包含重复成员,要确保一个新成员没有重复成员,惟一方法就是将它与所有已有成员进行对 比,只有在 visitEnd 方法中访问了所有这些成员后才能完成这一工作。这种做法是相当受限制的。在 实践中,使用程序员不大可能使用的生成名,比如_counter$或4B7F i就足以避免重复成员了, 并不需要将它们添加到 visitEnd 中。
public class AddFieldAdapter extends ClassVisitor {
private ClassVisitor classVisitor;
private int fAcc;
private String fName;
private String fDesc;
private boolean isFieldPresent;
public AddFieldAdapter(ClassVisitor classVisitor, int fAcc, String fName, String fDesc) {
super(ASM7);
this.classVisitor = classVisitor;
this.fAcc = fAcc;
this.fName = fName;
this.fDesc = fDesc;
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
if (name.equals(fName) && this.fDesc.equals(descriptor)) {
isFieldPresent = true;
}
return classVisitor.visitField(access, name, descriptor, signature, value);
}
@Override
public void visitEnd() {
if (!isFieldPresent) {
FieldVisitor fv = cv.visitField(fAcc, fName, fDesc, null, null);
if (fv != null) {
fv.visitEnd();
}
}
classVisitor.visitEnd();
}
}
谈谈MethodVisitor
下面将介绍一下如何通过MethodVisitor来生成一个方法.有如下一个类
package pkg;
public class Bean {
private int f;
public int getF() {
return this.f;
}
}
getter 方法的字节代码为:
ALOAD 0
GETFIELD pkg/Bean f I
IRETURN
那么生成getter方法的步骤如下
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, "pkg/Bean", "f", "I");
mv.visitInsn(IRETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
第一个调用启动字节代码的生成过程。然后是三个调用,生成这一方法的三条指令(可以看出,字节代码与 ASM API 之间的映射非常简单)。对 visitMaxs 的调用必须在已经访问了所有指令后执行。它用于为这个方法的执行帧定义局部变量和操作数栈部分的大小。最后一次调用用于结束此方法的生成过程。
为什么visitMaxs分别是1呢?这个和基于栈的指令集有关,相关内容参考
深入理解 JAVA 虚拟机(十)基于栈的字节码解释执行引擎
Java字节码的介绍