一、什么是ASM
ASM是一个JAVA字节码分析、创建和修改的开源应用框架。在ASM中提供了诸多的API用于对类的内容进行字节码操作的方法。与传统的BCEL和SERL不同,在ASM中提供了更为优雅和灵活的操作字节码的方式。目前ASM已被广泛的开源应用架构所使用,例如:Spring、Hibernate等。
二、ASM能干什么
分析一个类、从字节码角度创建一个类、修改一个已经被编译过的类文件
三、ASM初探例子
这里我们使用ASM的CoreAPI(ASM提供了两组API:Core和Tree,Core是基于访问者模式来操作类的,而Tree是基于树节点来操作类的)创建一个MyClass类,目标类如下:
public class MyClass {
private String name;
public Myclass(){
this.name = "zhangzhuo";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
这个类在构造方法中初始化了属性name,并提供了两个public方法来修改和访问name属性。
接下来就要书写创建这个类的代码了,现将代码给出,然后逐步解释,代码如下:
public class GenerateClass {
public void generateClass() {
//方法的栈长度和本地变量表长度用户自己计算
ClassWriter classWriter = new ClassWriter(0);
//Opcodes.V1_6指定类的版本
//Opcodes.ACC_PUBLIC表示这个类是public,
//“org/victorzhzh/core/classes/MyClass”类的全限定名称
//第一个null位置变量定义的是泛型签名,
//“java/lang/Object”这个类的父类
//第二个null位子的变量定义的是这个类实现的接口
classWriter.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC,"com/znn/mv2/MyClass", null,"java/lang/Object", null);
ClassAdapter classAdapter = new MyClassAdapter(classWriter);
classAdapter.visitField(Opcodes.ACC_PRIVATE, "name",Type.getDescriptor(String.class), null, null);//定义name属性
classAdapter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null,null).visitCode();//定义构造方法
String setMethodDesc = "(" + Type.getDescriptor(String.class) + ")V";classAdapter.visitMethod(Opcodes.ACC_PUBLIC, "setName", setMethodDesc,null, null).visitCode();//定义setName方法
String getMethodDesc = "()" + Type.getDescriptor(String.class);
classAdapter.visitMethod(Opcodes.ACC_PUBLIC, "getName", getMethodDesc,null, null).visitCode();//定义getName方法
byte[] classFile = classWriter.toByteArray();//生成字节码
MyClassLoader classLoader = new MyClassLoader();//定义一个类加载器
Class clazz = classLoader.defineClassFromClassFile("org.victorzhzh.core.classes.MyClass", classFile);
try {//利用反射方式,访问getName
Object obj = clazz.newInstance();
Method method = clazz.getMethod("getName");
System.out.println(obj.toString());
System.out.println(method.invoke(obj, null));
} catch (Exception e) {
e.printStackTrace();
}
}
class MyClassLoader extends ClassLoader {
public Class defineClassFromClassFile(String className, byte[] classFile)
throws ClassFormatError {
return defineClass(className, classFile, 0, classFile.length);
}
}
public static void main(String[] args) {
GenerateClass generateClass = new GenerateClass();
generateClass.generateClass();
}
}
class MyClassLoader extends ClassLoader {
public Class defineClassFromClassFile(String className, byte[] classFile)
throws ClassFormatError {
return defineClass(className, classFile, 0, classFile.length);
}
}
public static void main(String[] args) {
GenerateClass generateClass = new GenerateClass();
generateClass.generateClass();
}
}
代码2:
public classMyClassAdapter extends ClassAdapter {
public MyClassAdapter(ClassVisitor cv) {
super(cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor methodVisitor = cv.visitMethod(access, name, desc,
signature, exceptions);
if (name.equals("<init>")) {
return new InitMethodAdapter(methodVisitor);
} else if (name.equals("setName")) {
return new SetMethodAdapter(methodVisitor);
} else if (name.equals("getName")) {
return new GetMethodAdapter(methodVisitor);
} else {
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
// 这个类生成具体的构造方法字节码
class InitMethodAdapter extends MethodAdapter {
public InitMethodAdapter(MethodVisitor mv) {
super(mv);
}
@Override
public void visitCode() {
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object",
"<init>", "()V");// 调用父类的构造方法
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitLdcInsn("zhangzhuo");// 将常量池中的字符串常量加载刀栈顶
mv.visitFieldInsn(Opcodes.PUTFIELD,
"com/znn/mv2/MyClass", "name",
Type.getDescriptor(String.class));// 对name属性赋值
mv.visitInsn(Opcodes.RETURN);// 设置返回值
mv.visitMaxs(2, 1);// 设置方法的栈和本地变量表的大小
}
};
// 这个类生成具体的setName方法字节码
class SetMethodAdapter extends MethodAdapter {
public SetMethodAdapter(MethodVisitor mv) {
super(mv);
}
@Override
public void visitCode() {
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitFieldInsn(Opcodes.PUTFIELD,
"com/znn/mv2/MyClass", "name",
Type.getDescriptor(String.class));
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(2, 2);
}
}
// 这个类生成具体的getName方法字节
class GetMethodAdapter extends MethodAdapter {
public GetMethodAdapter(MethodVisitor mv) {
super(mv);
}
@Override
public void visitCode() {
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, "com/znn/mv2/MyClass", "name",
Type.getDescriptor(String.class));// 获取name属性的值
mv.visitInsn(Opcodes.ARETURN);// 返回一个引用,这里是String的引用即name
mv.visitMaxs(1, 1);
}
}
}
运行结果:
在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 class Test {
public static void main(String[] args) {
Person person = new Person();
person.sayName();
}
}
结果:
我们修改一下这个方法,让它输出"zhangzhuo",代码如下:
public classGenerateNewPerson {
public static void main(String[] args) throws Exception {
// 使用全限定名,创建一个ClassReader对象
// Person person = new Person();
// person.sayName();
ClassReader classReader = new ClassReader(
"com.znn.mv.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(
"bin/com/znn/mv/Person.class");
FileOutputStream stream = new FileOutputStream(file);
// File f = new File("");
// System.out.println(f.getAbsolutePath());
stream.write(classFile);
stream.close();
// Person person = new Person();
// person.sayName();
System.out.println("Modify finish...");
}
}
public classGeneralClassAdapter 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));
}
}
}
运行GenerateNewPerson
输出:Modify finish...
此时Person.class已被更改
运行person.java
Person类行成的二进制字节码如下:
补充:
javap查看字节码
首先使用javap -help可以看到各种指令,都很好玩,可以试着玩下
这里只演示javap -c和javap -version
例如:
javap -c HelloWorld
http://blog.csdn.net/zwx622?viewmode=list