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方法实现的字节码。






你可能感兴趣的:(JAVA基础)