概述
在ASM的Core API中使用的是访问者模式来实现对类的操作,主要包含如下类:
一、ClassVisitor接口:
在这个接口中主要提供了和类结构同名的一些方法,这些方法可以对相应的类结构进行操作。如下:
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();
}
这里定义的方法调用是有顺序的,在ClassVisitor中定义了调用的顺序和每个方法在可以出现的次数,如下:
visit [ visitSource ] [ visitOuterClass ] ( visitAnnotation | visitAttribute )* (visitInnerClass | visitField | visitMethod )* visitEnd。
二、ClassReader类:
这个类会提供你要转变的类的字节数组,它的accept方法,接受一个具体的ClassVisitor,并调用实现中具体的visit,
visitSource, visitOuterClass, visitAnnotation, visitAttribute, visitInnerClass, visitField, visitMethod和 visitEnd方法。
三、ClassWriter类:
这个类是ClassVisitor的一个实现类,这个类中的toByteArray方法会将最终修改的字节码以byte数组形式返回,在这个类的构造时可以指定让系统自动为我们计算栈和本地变量的大小(COMPUTE_MAXS),也可以指定系统自动为我们计算栈帧的大小(COMPUTE_FRAMES)。
四、ClassAdapter类:
这个类也是ClassVisitor的一个实现类,这个类可以看成是一个事件过滤器,在这个类里,它对ClassVisitor的实现都是委派给一个具体的ClassVisitor实现类,即调用那个实现类实现的方法。
五、AnnotationVisitor接口:
这个接口中定义了和Annotation结构想对应的方法,这些方法可以操作Annotation中的定义,如下:
public interface AnnotationVisitor {
void visit(String name, Object value);
void visitEnum(String name, String desc, String value);
AnnotationVisitor visitAnnotation(String name, String desc);
AnnotationVisitor visitArray(String name);
void visitEnd();
}
调用顺序如下:
(visit | visitEnum | visitAnnotation | visitArray)* visitEnd
六、FieldVisitor接口:
这个接口定义了和属性结构相对应的方法,这些方法可以操作属性,如下:
public interface FieldVisitor {
AnnotationVisitor visitAnnotation(String desc, boolean visible);
void visitAttribute(Attribute attr);
void visitEnd();
}
调用顺序:
( visitAnnotation | visitAttribute )* visitEnd .
七、MethodVisitor接口:
这个接口定义了和方法结构相对应的方法,这些方法可以去操作源方法,具体的可以查看一下源码。
八、操作流程:
一般情况下,我们需要操作一个类时,首先是获得其二进制的字节码,即用ClassReader来读取一个类,然后需要一个能将二进制字节码写回的类,即用ClassWriter类,最后就是一个事件过滤器,即ClassAdapter。事件过滤器中的某些方法可以产生一个新的XXXVisitor对象,当我们需要修改对应的内容时只要实现自己的XXXVisitor并返回就可以了。
九、例子:
在这个例子中,我们将对Person类的sayName方法做出一些修改,源类:
public class Person {
private String name;
public void sayName() {
System.out.println(name);
}
}
如果我们定义一个Person类然后调用其sayName()方法将会得到的是一个null,行成的二进制字节码如下:
public void sayName();
Code:
Stack=2, Locals=1, Args_size=1
0: getstatic #17; //Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #23; //Field name:Ljava/lang/String;
7: invokevirtual #25; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
}
我们修改一下这个方法,让它输出"zhangzhuo",代码如下:
public class GenerateNewPerson {
public static void main(String[] args) throws Exception {
// 使用全限定名,创建一个ClassReader对象
ClassReader classReader = new ClassReader(
"org.victorzhzh.core.ic.Person");
// 构建一个ClassWriter对象,并设置让系统自动计算栈和本地变量大小
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassAdapter classAdapter = new GeneralClassAdapter(classWriter);
classReader.accept(classAdapter, ClassReader.SKIP_DEBUG);
byte[] classFile = classWriter.toByteArray();
// 将这个类输出到原先的类文件目录下,这是原先的类文件已经被修改
File file = new File(
"target/classes/org/victorzhzh/core/ic/Person.class");
FileOutputStream stream = new FileOutputStream(file);
stream.write(classFile);
stream.close();
}
}
public class GeneralClassAdapter extends ClassAdapter {
public GeneralClassAdapter(ClassVisitor cv) {
super(cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature,
exceptions);
// 当是sayName方法是做对应的修改
if (name.equals("sayName")) {
MethodVisitor newMv = new SayNameMethodAdapter(mv);
return newMv;
} else {
return mv;
}
}
// 定义一个自己的方法访问类
class SayNameMethodAdapter extends MethodAdapter {
public SayNameMethodAdapter(MethodVisitor mv) {
super(mv);
}
// 在源方法前去修改方法内容,这部分的修改将加载源方法的字节码之前
@Override
public void visitCode() {
// 记载隐含的this对象,这是每个JAVA方法都有的
mv.visitVarInsn(Opcodes.ALOAD, 0);
// 从常量池中加载“zhangzhuo”字符到栈顶
mv.visitLdcInsn("zhangzhuo");
// 将栈顶的"zhangzhuo"赋值给name属性
mv.visitFieldInsn(Opcodes.PUTFIELD,
Type.getInternalName(Person.class), "name",
Type.getDescriptor(String.class));
}
}
}
这时,我们在查看一下Person的字节码:
public void sayName();
Code:
Stack=2, Locals=1, Args_size=1
0: aload_0
1: ldc #13; //String zhangzhuo
3: putfield #15; //Field name:Ljava/lang/String;
=============以上是我们新增加的内容================================
6: getstatic #21; //Field java/lang/System.out:Ljava/io/PrintStream;
9: aload_0
10: getfield #15; //Field name:Ljava/lang/String;
13: invokevirtual #27; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
16: return
}
再次调用Person对象,输出结果为:zhangzhuo
最后
以上就是平常跳跳糖为你收集整理的ASM系列之三:ASM中的访问者模式的全部内容,希望文章能够帮你解决ASM系列之三:ASM中的访问者模式所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复