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