asm指代C语言中的__asm__关键字,是一种java字节码引擎库,可以用它在运行期修改类的字节码,也可以用它来动态地生成stub类和其他代理类。
asm提供了两套分析和修改字节码的API:
(1)core API :核心API,使用访问者模式基于事件的编程模型来操纵字节码,只加载访问到的部分到内存,所占用内存较少。由于core API效率高,使用比较多,后续只介绍coreAPI,示例也基于core API。
(2)Tree API:树形API,将整个字节码文件加载到内存进行操作,占用内存多,优点是操作比较简单。
asm基于Core API提供了三大组件:
(1)ClassReader:解析字节码byte数组,通过accept方法将其传给ClassVisitor的visitXxx()方法,ClassReader相当于事件的生产者;
(2)ClassWriter:它是ClassVisitor的子类,通过visitXxx方法可以生成字节码,其toByteArray方法可以将字节码转换为byte数组,ClassWriter相当于事件的消费者;
(3)ClassVisitor:它就ClassReader与ClassWriter沟通的桥梁,起代理作用,当然它也可以在代理的过程中执行过滤操作,它相当于事件的过滤器。
以下是JVM规范摘录的类文件的结构:
ClassFile {
u4 magic;//魔数
u2 minor_version;//副版本号
u2 major_version; //主版本号
u2 constant_pool_count; //常量池计数器
cp_info constant_pool[constant_pool_count-1];//常量池
u2 access_flags; //访问标志
u2 this_class; //类索引
u2 super_class; //父类索引
u2 interfaces_count; //接口计数器
u2 interfaces[interfaces_count]; //接口表
u2 fields_count; //字段计数器
field_info fields[fields_count];//字段表
u2 methods_count; //方法计数器
method_info methods[methods_count];//方法表
u2 attributes_count; //属性计数器
attribute_info attributes[attributes_count];//属性表
}
ClassVisitor针对类的每个部分都提供了相应的visit方法,以下是ClassVisitor的API:
public void visit(int version, int access, String name,String signature, 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();
在对类文件进行访问时,这些方法调用必须遵循以下顺序:
visit visitSource? visitOuterClass? ( visitAnnotation | visitAttribute )*( visitInnerClass | visitField | visitMethod )*visitEnd
现在使用asm ClassVisitor来生成接口的字节码,假设有如下接口:
package asm.demo;
public interface Oper {
}
用ASM生成如下所示的java代码:
package asm.demo;
public interface AddOper extends Oper {
public static final String SYMBOL = "+";
public int add(int a, int b);
}
AddOperGenerator类在已有asm.demo.Oper接口的基础上通过ClassWriter生成了AddOper类对应的字节码,代码如下:
package asm.demo;
import org.objectweb.asm.ClassWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import static org.objectweb.asm.Opcodes.*;
public class AddOperGenerator {
public static void main(String[] args) throws IOException {
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
"asm/demo/AddOper", null, "java/lang/Object",
new String[]{"asm/demo/Oper"});
cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC, "SYMBOL", "Ljava/lang/String;",
null, "+").visitEnd();
cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "add",
"(II)I", null, null).visitEnd();
cw.visitEnd();
FileOutputStream fos = new FileOutputStream(new File("D:/code/asmdemo/out/production/asmdemo/asm/demo/AddOper.class"));
fos.write(cw.toByteArray());
fos.close();
}
}
(1)visit方法表示开始生成字节码,其参数指示需要使用的版本,类名等信息,具体如下:
version:JVM版本,这里是Opcodes.V1_5,即jdk5;
access:类或者接口访问标志modifier,这里是 public abstract interface;
name:类或者接口的全限定名,这里是asm/demo/AddOper;
signature:类或者接口泛型参数,这里没有使用泛型,所以为null;
superName:父类java/lang/Object;
interfaces:父接口数组 new String[]{"asm/demo/Oper"}。
(2)visitField与visit类似,其参数分别表示 字段的访问标志,字段名,字段描述,泛型参数以及默认值。
(3)每个visitMethod访问完都需要加上visitEnd。
(4)调用visitEnd方法结束对这个类的访问,最后通过ClassWriter.toByteArray()方法得到字节码的byte数组。
通过javap -constants参数查看这个类:
public interface asm.demo.AddOper extends asm.demo.Oper {
public static final java.lang.String SYMBOL = "+";
public abstract int add(int, int);
}
下节分析如何用asm生成add方法实现的字节码。