ASM(字节码处理工具)
Posted 凉茶方便面
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ASM(字节码处理工具)相关的知识,希望对你有一定的参考价值。
ASM是一种Java字节码生成和分析的框架,我们可以用它通过操作二进制的方式来修改现成的类或者动态生成class文件。ASM不仅提供了和其他字节码生成框架相类似的功能,此外它还关注框架的易用性和性能。
ASM的源代码中包含了若干packages:
- org.objectweb.asm:是项目的核心包,它提供了ASM的访问API,并且提供了ClassReader和ClassWriter来读取和生成Java的class文件。这个不依赖于其他包,可以单独使用。
- org.objectweb.asm.signature:提供了读写签名(这些签名可以是类型的签名,或者是方法的签名)的API,它独立于核心包,同时完善核心包的内容。
- org.objectweb.asm.tree:提供一种类似于DOM操作的API,从而便于实现复杂形式的类。
- org.objectweb.asm.tree.analysis:在tree包的基础上形成了一种静态字节码分析工具,可以用来实现需要明确知道栈帧中每条指令的状态的复杂情况。
- org.objectweb.asm.util:可以提供一些类的visitor和adapter,用来调试,通常运行时不需要使用。
- org.objectweb.asm.xml:提供了class文件和XML文件之间的相互转换的功能,但是基于性能考虑不建议在现实中使用(最好使用核心包)。
- org.objectweb.asm.commons:提供一些有用的adapter(基于核心包),这些adapter可被用于实现更复杂的类转换功能。
整个项目中的转换和编译文件的工作都是由ClassReader来做的,而转换处理工作则是由与之相关的其他类来做的。
- Classes:ClassWriter 是主要的入口,它包含着类文件的版本,访问标记,类名等信息,同时还包含着表示常量池,域,方法,注解,类的额外属性之类的内容;
- Constant pool:Item类被用于表示常量池中的条目;
- Field:FieldWriter用于写类的域,它包含着域的名字,类型,签名以及值,同时它还包含着描述域的注解和属性的对象;
- Methods:MethodWriter用于编写类的方法,它包含方法名字,签名,异常列表,方法注解,以及额外属性;Handler用于表示try-catch代码块,每个Handler包含三个Lable对象用于表示try的开始和结束以及catch代码块;Label类被用于表示指令,但是同时也表示基本代码块,可以使用它计算栈帧的大小;Frame用于自动处理每个方法的栈帧;Edge用于表示每个方法的控制流。
- Annotations:AnnotationWriter被用于处理注解,它会被ClassWriter,FieldWriter和MethodWriter引用,因为所有的类,域,方法都可以包含注解;
- Attributes:Attributes类可以被用来读写类的非标准属性;
- Ressources:ByteVector被用来在访问类属性时序列化这些属性,它被用来表示常量池,注解值,方法代码,栈图,行号。
以下是官方给出的示例,用于生成一个包含main函数的Example类:
import java.io.FileOutputStream;
import java.io.PrintStream;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
/**
* @author Eric Bruneton
*/
public class Helloworld extends ClassLoader implements Opcodes
public static void main(String args[]) throws Exception
// 生成如下类文件
// public class Example
// public static void main (String[] args)
// System.out.println("Hello world!");
//
//
// 为Example提供ClassWriter,要求Example继承自Object
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_1, ACC_PUBLIC, "Example", null, "java/lang/Object", null);
// 为隐含的默认构造器创建MethodWriter
MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null,
null);
// 将this变量放入局部变量表
mw.visitVarInsn(ALOAD, 0);
// 调用父类的默认构造函数
mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V",false);
mw.visitInsn(RETURN);
// 这段代码使用了最大为1的栈元素,且只有一个局部变量
mw.visitMaxs(1, 1);
mw.visitEnd();
// 为main方法创建MethodWriter
mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main",
"([Ljava/lang/String;)V", null, null);
// 将System的out域入栈
mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out",
"Ljava/io/PrintStream;");
// String类型的"Hello World!"常量入栈
mw.visitLdcInsn("Hello world!");
// 调用System.out的println方法
mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
"(Ljava/lang/String;)V", false);
mw.visitInsn(RETURN);
// 这段代码使用了最大为2的栈元素,包含两个局部变量
mw.visitMaxs(2, 2);
mw.visitEnd();
// 获得Example类的字节码,并且动态加载它
byte[] code = cw.toByteArray();
FileOutputStream fos = new FileOutputStream("Example.class");
fos.write(code);
fos.close();
Helloworld loader = new Helloworld();
Class<?> exampleClass = loader.defineClass("Example", code, 0,
code.length);
// 使用动态生成的类打印'Helloworld'
exampleClass.getMethods()[0].invoke(null, new Object[] null );
// ------------------------------------------------------------------------
// 使用GeneratorAdapter的示例(很方便但是更慢)
// ------------------------------------------------------------------------
cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(V1_1, ACC_PUBLIC, "Example", null, "java/lang/Object", null);
// 为隐含的默认构造器创建GeneratorAdapter
Method m = Method.getMethod("void <init> ()");
GeneratorAdapter mg = new GeneratorAdapter(ACC_PUBLIC, m, null, null, cw);
mg.loadThis();
mg.invokeConstructor(Type.getType(Object.class), m);
mg.returnValue();
mg.endMethod();
// 为main方法创建GeneratorAdapter
m = Method.getMethod("void main (String[])");
mg = new GeneratorAdapter(ACC_PUBLIC + ACC_STATIC, m, null, null, cw);
mg.getStatic(Type.getType(System.class), "out",
Type.getType(PrintStream.class));
mg.push("Hello world!");
mg.invokeVirtual(Type.getType(PrintStream.class),
Method.getMethod("void println (String)"));
mg.returnValue();
mg.endMethod();
cw.visitEnd();
code = cw.toByteArray();
loader = new Helloworld();
exampleClass = loader.defineClass("Example", code, 0, code.length);
// 使用动态生成的类打印'Helloworld'
exampleClass.getMethods()[0].invoke(null, new Object[] null );
从上边的代码可以看出,整个操作过程就是定义了字节码级别的类、方法处理的流程,将这些流程定义好后可以产生类中的类,方法,域,代码段等一系列信息,然后将这些信息汇总,最后就可以得到定义好流程的class文件的字节码了。
以上是关于ASM(字节码处理工具)的主要内容,如果未能解决你的问题,请参考以下文章
字节码插桩AOP 技术 ( “字节码插桩“ 技术简介 | AspectJ 插桩工具 | ASM 插桩工具 )
ASM字节码操作 工具类与常用类 CheckClassAdapter 介绍
ASM字节码操作 工具类与常用类 PrinterASMifierTextifier 介绍
ASM字节码操作 工具类与常用类 SerialVersionUIDAdder 介绍