asm簡介
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方法實現的字節碼。