【Java】浅聊应用ASM CoreAPI修改字节码那些事

本文介绍了ASM框架,一个用于动态生成和修改Java字节码的工具,重点讲解了ClassReader、ClassWriter和ClassVisitor在处理ClassFile结构中的关键作用,包括如何修改类的访问修饰符、添加接口和字段,以及修改方法内容。
摘要由CSDN通过智能技术生成
<dependencies>
    <dependency>
        <groupId>org.ow2.asm</groupId>
        <artifactId>asm</artifactId>
        <version>9.2</version>
    </dependency>
    <dependency>
        <groupId>org.ow2.asm</groupId>
        <artifactId>asm-util</artifactId>
        <version>9.2</version>
    </dependency>
    <dependency>
        <groupId>org.ow2.asm</groupId>
        <artifactId>asm-commons</artifactId>
        <version>9.2</version>
    </dependency>
</dependencies>

ASM概念

ASM(Objectweb ASM)是一个轻量级的 Java 字节码操作框架,可以用于动态生成类、修改现有类的字节码以及分析类文件。ASM 提供了一种基于事件驱动的 API,使得开发者可以方便地操作和修改 Java 类的字节码。

Java字节码&ClassFile

Java 字节码是一种由 JVM 执行的二进制指令集,它是 Java 源代码编译后生成的中间形式。Java 字节码包含了 JVM 能够识别和执行的指令,通过这些指令可以实现对 Java 类的各种操作和逻辑。

ClassFile 结构则是描述 Java 类文件格式的一种规范,它定义了一个类文件的结构和包含的信息。在 ClassFile 结构中,包含了类的版本号、常量池、访问标志、字段表、方法表等内容,这些信息都是以特定的格式和顺序存储在类文件中的。

Java 字节码实际上就是存储在 ClassFile 结构中的具体指令和数据,它们是 JVM 执行的基本单位。当 JVM 加载一个类文件时,会解析其中的 ClassFile 结构,然后将其中的字节码指令加载到内存中,并按照指令顺序执行,从而实现对 Java 类的功能。

ClassFile结构

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];
}
field_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
// method的方法体由Code属性表示
Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack; 
    u2 max_locals; 
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}
// u? 表示占用?个字节

核心理念:拆分修改重组

CoreAPI最重要的三个类:

  • ClassReader: 读取字节码文件,并拆分为不同的部分(decompose)
  • ClassVisitor: 对字节码中某一部分进行修改(modify)FieldVisitor、MethodVisitor
  • ClassWriter: 将各个部分重组为完整的字节码文件(recompose)FieldWriter、MethodWriter

ClassReader用于读取字节码文件,ClassWriter用于生成字节码文件,ClassVisitor用于修改字节码

ClassWriter 本身就是一个 ClassVisitor 的实现类,它可以接受对类的访问和修改操作,并将修改后的类文件输出为字节数组。

当需要生成新的类文件时,通常会创建一个继承自 ClassVisitor 的自定义 Visitor 类,通过重写其中的方法来实现对类文件的定制化操作,然后将这个 Visitor 类传递给 ClassWriter,利用 ClassWriter 输出最终的类字节码。

需要修改类文件时,要以ClassReader为旧字节码的入口,ClassWriter为新字节码的出口,中间可以通过多个ClassVisitor来修改字节码。

修改字节码最简化模型

将下面的cv替换为我们自定义的ClassVisitor子类即可

ClassReader cr = new ClassReader("classfile");
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
ClassVisitor cv = new ClassVisitor(Opcodes.ASM9, cw) {
    // TODO
};
cr.accept(cv, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
cw.toByteArray();

代码示例

ASM修改类的基本信息

假设要将一个类的访问修饰符修改为 public,或添加一个实现的接口 “Comparable”,下面是一个示例代码:

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

public class CustomClassVisitor extends ClassVisitor {

    public CustomClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM9, cv);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        // 修改类的访问修饰符为 public
        super.visit(version, access | Opcodes.ACC_PUBLIC, name, signature, superName, interfaces);
        
        // 添加一个实现的接口 "Comparable"
        String[] newInterfaces = new String[interfaces.length + 1];
        System.arraycopy(interfaces, 0, newInterfaces, 0, interfaces.length);
        newInterfaces[interfaces.length] = "java/lang/Comparable";
        super.visit(version, access, name, signature, superName, newInterfaces);
    }
}

public class ClassModifier {
    public static byte[] modifyClass(byte[] originalClass) {
        ClassReader cr = new ClassReader(originalClass);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        CustomClassVisitor cv = new CustomClassVisitor(cw);
        cr.accept(cv, 0);
        return cw.toByteArray();
    }
}

ASM修改类的字段

当使用 ASM 修改类的字段时,可以通过自定义 ClassVisitor 的子类来实现。

假设要向一个类中添加一个名为 “newField” 的字段,下面是一个示例代码:

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

public class CustomClassVisitor extends ClassVisitor {

    public CustomClassVisitor(ClassVisitor cv) {
        super(Opcodes.ASM9, cv);
    }

    @Override
    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
        // 添加一个名为 "newField" 的字段
        if ("newField".equals(name)) {
            return null; // 跳过已存在的字段
        }
        return super.visitField(access, name, descriptor, signature, value);
    }

    @Override
    public void visitEnd() {
        // 在 visitEnd 方法中添加一个新字段 "newField"
        FieldVisitor fv = super.visitField(Opcodes.ACC_PRIVATE, "newField", "Ljava/lang/String;", null, null);
        if (fv != null) {
            fv.visitEnd();
        }
        super.visitEnd();
    }
}

public class ClassModifier {
    public static byte[] modifyClass(byte[] originalClass) {
        ClassReader cr = new ClassReader(originalClass);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        CustomClassVisitor cv = new CustomClassVisitor(cw);
        cr.accept(cv, 0);
        return cw.toByteArray();
    }
}

ASM修改类的方法

当使用 ASM 修改类的方法时,同样可以通过自定义 ClassVisitor 的子类来实现。

假设我们要向一个类中添加一个方法进入和退出时打印日志的方法,下面是一个示例代码:

常规实现
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class MethodInOutVisitor extends ClassVisitor {
    private final String methodName;
    private final String methodDesc;

    public MethodInOutVisitor(int api, ClassVisitor classVisitor, String methodName, String methodDesc) {
        super(api, classVisitor);
        this.methodName = methodName;
        this.methodDesc = methodDesc;
    }

    @Override
**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

![img](https://img-blog.csdnimg.cn/img_convert/fbdf83e010622b3a73bbc7ada6cca60c.png)

 

![img](https://img-blog.csdnimg.cn/img_convert/ca80308fdf9e66cd8b0d4c6b155ad660.png)

![img](https://img-blog.csdnimg.cn/img_convert/46506ae54be168b93cf63939786134ca.png)

![img](https://img-blog.csdnimg.cn/img_convert/252731a671c1fb70aad5355a2c5eeff0.png)

![img](https://img-blog.csdnimg.cn/img_convert/6c361282296f86381401c05e862fe4e9.png)

![img](https://img-blog.csdnimg.cn/img_convert/9f49b566129f47b8a67243c1008edf79.png)

 

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)**

6f86381401c05e862fe4e9.png)

![img](https://img-blog.csdnimg.cn/img_convert/9f49b566129f47b8a67243c1008edf79.png)

 

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)**

<img src="https://img-community.csdnimg.cn/images/fd6ebf0d450a4dbea7428752dc7ffd34.jpg" alt="img" style="zoom:50%;" />
  • 18
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值