ASM字节码增强技术
ASM是什么
ASM是一个Java字节码操控框架。它可以被用来动态生成类或者增强既有类的功能。ASM可以直接产生二进制的class文件,也可以在类被加载到Java虚拟机之前改变类行为。ASM的应用场景有AOP(cglib基于ASM)、热部署、修改其它jar包中的类等。
核心API:
ClassReader:用于读取已经编译好的.class文件;
ClassWriter:用于重新构建编译后的类,如修改类名、属性及方法等,或者生成一个新的类;
ClassVistor:ASM内部采用访问者模式根据字节码从上到下依次处理,对于字节码文件中不同的区域有不同的Visitor。
访问类文件时,会回调ClassVistor的visit方法;
访问类注解时,会回调ClassVistor的visitAnnotation方法;
访问类成员时,会回调ClassVistor的visitField方法;
访问类方法时,会回调ClassVistor的visitMethod方法;
.......
当访问不同区域时会回调相应方法,该方法会返回一个对应的字节码操作对象。通过修改这个对象就可以修改class文件相应结构对应的内容。最后将这个对象的字节码内容覆盖原先的.class文件就可以实现字节码的切入。
示例代码
创建一个新的类,并调用其方法。
引入asm-all-5.2.jar,这里使用的版本是5.2
package com.sankuai.test.bytecode;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import static org.objectweb.asm.Opcodes.*;
import java.io.IOException;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Method;
public class ByteCode {
public static void main(String[] args) throws Exception {
byte[] data = genMyClass2();
// 加载类
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> cls = myClassLoader.defineClass("MyClass2", data);
// 获取foo方法
Method foo = cls.getMethod("foo", int.class);
// 创建MyClass2类的对象
Object obj = cls.newInstance();
// 调用foo方法,输出:8
System.out.println(foo.invoke(obj, 5));
}
static class MyClassLoader extends ClassLoader {
public Class<?> defineClass(String name, byte[] b) {
return super.defineClass(name, b, 0, b.length);
}
}
/** 用asm创建下面这个类的字节码:
public class MyClass2{
public int foo(int a){
return a + 3;
}
}
*/
public static byte[] genMyClass2() throws IOException {
// 创建一个ClassWriter
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// 创建MyClass2类
cw.visit(V1_8, ACC_PUBLIC, "MyClass2", null, "java/lang/Object", null);
// 创建缺省构造方法
genDefaultConstructor(cw);
// 创建foo方法
genFooMethod(cw);
// 结束类
cw.visitEnd();
byte[] data = cw.toByteArray();
File file = new File("MyClass2.class");
FileOutputStream fos = new FileOutputStream(file);
fos.write(data);
fos.close();
return data;
}
//创建缺省构造方法
private static void genDefaultConstructor(ClassWriter cw){
MethodVisitor constructor = cw.visitMethod(ACC_PUBLIC, "<init>",
"()V", null, null);
constructor.visitCode();
constructor.visitVarInsn(ALOAD, 0);
constructor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
constructor.visitInsn(RETURN);
constructor.visitMaxs(1,1);
constructor.visitEnd();
}
//创建foo方法
private static void genFooMethod(ClassWriter cw){
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "foo",
"(I)I", //括号中的是参数类型,括号后面的是返回值类型
null, null);
//添加参数a
mv.visitParameter("a", ACC_PUBLIC);
mv.visitVarInsn(ILOAD, 1); //iload_1
mv.visitInsn(ICONST_3); //iconst_3
mv.visitInsn(IADD); //iadd
mv.visitInsn(IRETURN); //ireturn
//设置操作数栈最大的帧数,以及最大的本地变量数
mv.visitMaxs(2,2);
//结束方法
mv.visitEnd();
}
}
扩展阅读:
字节码增强之ASM、JavaAssist、Agent、Instrumentation: https://blog.csdn.net/hosaos/article/details/102931887
Last updated