asm學習筆記之生成接口

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






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章