java字节码框架——ASM

ASM 是一个 Java 字节码操控框架。它能够以二进制形式修改已有类或者动态生成类。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

1
2
3
4
5
 <dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>

主要类

ClassReader

用于读取解析字节码文件

1
ClassReader cr = new ClassReader(Test.class.getName());

ClassWriter

用于生产一个新的字节码文件,继承ClassVisitor。

1
ClassWriter cw = new ClassWriter(0);

ClassAdapter

ClassAdapter继承ClassVisitor。构造一个ClassAdapter需要传入一个ClassWriter。这是装饰模式。对ClassWirter的增强。

1
ClassAdapter ca= new ClassAdapter(cw);

ClassAdapter可以被一个ClassReader接受,将ClassReader产生的事件传递给ClassWriter。这样方便基于ClassReader读取的class上设置新的class文件。默认的ClassAdapter会完全将ClassReader复制到ClassWriter,所以一般都会根据实现ClassAdapter的自定义子类。

1
2
3
4
ClassReader cr = new ClassReader(Test.class.getName());
ClassWriter cw = new ClassWriter(0);
ClassAdapter ca= new ClassAdapter(cw);
cr.accept(ca,0);

ClassVisitor

ClassVisitor的接口API。ClassAdapter和ClassWriter都继承ClassVisitor,他们的API都一样。
ClassAdapter的对应方法参数都是从ClassReader中读取的。ClassWriter通过设置对应方法这些参数生成类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class ClassVisitor {
public ClassVisitor(int api);
public ClassVisitor(int api, ClassVisitor cv);
//类头(类版本号,访问控制标识,类名,类签名(包括泛型),父类名,接口)
public void visit(int version, int access, String name, String signture, String superName, String[] interfaces);
//源文件
public void visitSource(String source, String debug);
//
public void visitOuterClass(String owner, String name, String desc);
//注解
AnnotationVisitor visitAnnotation(String desc, boolean visible);
//属性
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);
//方法
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions);

//用于添加属性
void visitEnd();
}

使用例子

在Bean类加一个字段,并生成class文件

1
2
3
4
5
6
7
8
9
10
11
public class Bean {
private String id;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//1.读取Bean类
ClassReader cr = new ClassReader(Bean.class.getName());
//2.创建ClassWriter
ClassWriter cv = new ClassWriter(0);
ClassAdapter ca= new ClassAdapter(cv){

//一般添加信息都在visitEnd上添加,修改的话就在对应的方法上修改,如要把id字段的类型改为int,
//就在ClassAdapter的visitField上调用cv.visitField。不重写的方法,ClassWriter都会复制ClassReader中的全部信息
@Override
public void visitEnd() {
//调用ClassWriter的visitField添加字段, 不知道参数怎么配置的可以将ClassReader的配置打印出来参考
cv.visitField(2,"test","Ljava/lang/String;",null,null);
}
};
cr.accept(ca,0);
byte[] data = cw.toByteArray();
File file = new File("C://Bean.class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();

添加方法

这个添加一个无参数构造方法的实现。

1
2
3
4
5
6
7
8
9
MethodVisitor mv = classWriter.visitMethod(1, "<init>", "()V", null, null);
//添加方法methodVisitor需要执行下面的方法,生成方法体
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
//第二个参数是父类名,这里是调用父类的构造函数
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();

将classWriter转成Class

调用classLoader的defineClass方法将字节数组转化为Class,defineClass方法是protected方法,所以要继承ClassLoader才可以使用。

1
2
byte[] bytes = classWriter.toByteArray();
return defineClass(className, bytes, 0, bytes.length);