Java字节码框架ASM简介

1. ASM概述

1.1 ASM简介

ASM 是一个通用的 Java 字节码操作和分析框架。一般用来动态生成类或者增强既有类的功能。也就是既可以创建class文件,也可以修改class文件。

ASM官网地址:https://asm.ow2.io/index.html
在这里插入图片描述

1.2 ASM提供API

ASM提供了两种API
Core API(ClassVisitor 、MethodVisitor等)
Tree API(ClassNode,MethodNode等)
ASM API文档地址:https://asm.ow2.io/asm4-guide.pdf
在这里插入图片描述

1.3 ASM核心模块

ClassReader 负责解析 .class 文件中的字节码,并将所有字节码传递给 ClassWriter。

ClassVisitor: 负责访问.class文件的各个元素,可以解析或者修改.class文件的内容。

ClassWriter:继承自 ClassVisitor,它是生成字节码的工具类,负责将修改后的字节码输出为 byte 数组。

2. ASM应用

2.1 ASM依赖

mvn仓库地址:https://mvnrepository.com/artifact/org.ow2.asm/asm/9.2

<!-- https://mvnrepository.com/artifact/org.ow2.asm/asm -->
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>9.2</version>
</dependency>

2.2 代码实现

Employee

/**
 * @author zrj
 * @since 2022/1/24
 **/
public class Employee {

    private String employeeName;

    public void working() {
        System.out.println(employeeName + " is working !");
    }
}

EmployeeClassLoader

/**
 * @author zrj
 * @since 2022/1/24
 **/
public class EmployeeClassLoader extends ClassLoader {

    public Class defineClassFromClassFile(String className,byte[] classFile) throws ClassFormatError{
        return defineClass(className, classFile, 0, classFile.length);
    }

    public Class<?> defineClassForName(String name, byte[] data) {
        return this.defineClass(name, data, 0, data.length);
    }
}

EmployeeClassVisitor

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
 * @author zrj
 * @since 2022/1/24
 **/
public class EmployeeClassVisitor extends ClassVisitor {
    private String className;
    private String superName;

    public EmployeeClassVisitor(ClassVisitor classVisitor) {
        super(Opcodes.ASM5, classVisitor);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        this.className = name;
        this.superName = superName;
        //super.visit(version, access, name, signature, superName, interfaces);
        super.visit(version, access, name + "$EnhancedByASM", signature, name, interfaces);
    }

    @Override
    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
        System.out.println("【ClassVisitor visitField 】access:" + access + ", name:" + name + ", desc:" + desc + ",signature:" + signature + ",value:" + value);
        return super.visitField(access, name, desc, signature, value);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        System.out.println("【ClassVisitor visitMethod】access:" + access + ", name:" + name + ", desc:" + desc + ", signature:" + signature + ", exceptions:" + exceptions);
        MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
        MethodVisitor wrappedMv = mv;
        //判断当前读取的方法
        if ("working".equals(name)) {
            //如果是working方法,则包装一个方法的Visitor
            wrappedMv = new EmployeeMethodVisitor(Opcodes.ASM5, mv);
        } else if ("<init>".equals(name)) {
            //如果是构造方法,处理子类中父类的构造函数调用
            wrappedMv = new EmployeeConstructorMethodVisitor(Opcodes.ASM5, mv, superName);
        }
        return wrappedMv;
    }
}

EmployeeMethodVisitor.java

import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
 * @author zrj
 * @since 2022/1/24
 **/
public class EmployeeMethodVisitor extends MethodVisitor {
    private String className;
    private String methodName;

    public EmployeeMethodVisitor(int Opcodes, MethodVisitor methodVisitor) {
        super(Opcodes, methodVisitor);
    }

    //MethodVisitor 中定义了不同的visitXXX()方法,代表的不同的访问阶段。
    //visitCode表示刚刚进入方法。
    @Override
    public void visitCode() {
        //添加一行System.currentTimeMillis()调用
        visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        //并且将其存储在局部变量表内位置为1的地方
        visitVarInsn(Opcodes.LSTORE, 1);
        //上面两个的作用就是在Studying方法的第一行添加 long start = System.currentTimeMillis()
    }

    //visitInsn 表示访问进入了方法内部
    @Override
    public void visitInsn(int opcode) {
        //通过opcode可以得知当前访问到了哪一步,如果是>=Opcodes.IRETURN && opcode <= Opcodes.RETURN 表明方法即将退出
        if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
            //加载局部变量表中位置为1的数据,也就是start的数据,并传入给下面的方法
            visitVarInsn(Opcodes.LLOAD, 1);
            //然后调用自定义的一个工具方法,用来输出耗时
            visitMethodInsn(Opcodes.INVOKESTATIC, "com/zjz/Before", "end", "(J)V", false);
        }
        super.visitInsn(opcode);
    }

}

class EmployeeConstructorMethodVisitor extends MethodVisitor {
    //定义一个全局变量记录父类名称
    private String superClassName;

    public EmployeeConstructorMethodVisitor(int i, MethodVisitor methodVisitor, String superClassName) {
        super(i, methodVisitor);
        this.superClassName = superClassName;
    }

    @Override
    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean b) {
        //当开始初始化构造函数时,先访问父类构造函数,类似源码中的super()
        if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) {
            owner = superClassName;
        }
        super.visitMethodInsn(opcode, owner, name, desc, b);
    }
}

EmployeeOw2AsmTest

import org.objectweb.asm.*;

import java.io.FileOutputStream;

/**
 * @author zrj
 * @since 2022/1/24
 **/
public class EmployeeOw2AsmTest {

    public static void main(String[] args) throws Exception {

        //1.定义ClassReader
        String sourceClassName = "com.zrj.unit.asm.Employee";
        ClassReader classReader = new ClassReader(sourceClassName);
        //2.定义ClassWriter
        ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS);
        //3.定义ClassVisitor
        ClassVisitor classVisitor = new EmployeeClassVisitor(classWriter);

        // 定义classVisitor输入数据,
        // SKIP_DEBUG 如果设置了此标志,则这些属性既不会被解析也不会被访问
        // EXPAND_FRAMES 依次调用ClassVisitor 接口的各个方法
        classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);

        // 将最终修改的字节码以byte数组形式返回
        byte[] bytes = classWriter.toByteArray();

        String targetClassName = "com.zrj.unit.asm.Employee$EnhancedByASM";
        Class<?> clazz = new EmployeeClassLoader().defineClassFromClassFile(targetClassName, bytes);
        System.out.println("【EmployeeOw2AsmTest】clazz:" + clazz);

        // 通过文件流写入方式覆盖原先的内容,实现class文件的改写
        FileOutputStream fileOutputStream = new FileOutputStream("D:\\data\\asm\\Employee$EnhancedByASM.class");
        fileOutputStream.write(bytes);
        fileOutputStream.close();
    }

}

2.3 测试验证

【ClassVisitor visitField 】access:2, name:employeeName, desc:Ljava/lang/String;,signature:null,value:null
【ClassVisitor visitMethod】access:1, name:<init>, desc:()V, signature:null, exceptions:null
【ClassVisitor visitMethod】access:9, name:main, desc:([Ljava/lang/String;)V, signature:null, exceptions:null
【EmployeeOw2AsmTest】clazz:class com.zrj.unit.asm.Employee$EnhancedByASM
  • 4
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值