Java Agent(三)、ASM 操作字节码入门

目录

1、前言

2、什么是ASM?

2.1、工作流程

2.2、ASM集合核心API

2.1.1、ClassReader

2.1.2、ClassWriter

2.1.3、 ClassVisitor

2.1.4、MethodVisitor

2.1.5、 FieldVisitor

2.1.6、Opcodes

3、简单示例

3.1、maven依赖

3.2、hello world

3.3、执行结果

4、ASM和Javassist

4.1、操作层级

4.2、性能

4.3、应用场景


1、前言

在上一篇Javassist入门中,我们介绍了如何使用Javassist操作Java字节码,Javassist主要是利用Java源码以及反射机制来实现的。而今天将要介绍另一种能操作Java字节码的技术,也就是ASM。他相比Javassist更灵活,提供了更细粒度的控制。

2、什么是ASM?

ASM是一个通用的 Java 字节码操作和分析框架。它可用于修改现有类或动态生成类(直接以二进制形式)。ASM 提供了一些常见的字节码转换和分析算法,可从中构建自定义复杂转换和代码分析工具。ASM 提供与其他 Java 字节码框架类似的功能,但更注重性能。由于它的设计和实现尽可能小巧和快速,因此非常适合在动态系统中使用(但当然也可以以静态方式使用,例如在编译器中)。

官网地址:ASM

2.1、工作流程

  1. 基于访问者模式:
  • ASM 使用访问者模式来遍历和修改字节码。ClassReader 类负责读取class文件并通知给 ClassVisitor 的实现。
  • ClassVisitor 接收来自 ClassReader 的事件,并可以选择性地将这些事件传递给下一个 ClassVisitor 或者修改字节码内容。
  • MethodVisitor 和 FieldVisitor 分别用于处理方法和字段相关的字节码信息。
  1. 字节码流解析:
  • ClassReader 会把 class 文件的内容解析成一系列的字节码指令流。
  • 这些指令流按照 Java 虚拟机规范定义的格式进行组织,包括常量池、访问标志、字段表、方法表等结构。
  1. 字节码生成与转换:
  • ClassWriter 类是 ASM 中用来生成新的 class 文件的核心组件。
  • 它实现了 ClassVisitor 接口,在接收到各种字节码元素后,根据需要构建出新的字节码序列。
  • 在这个过程中可以插入、删除或修改原有的字节码指令,从而实现对类行为的动态调整。

流程图如下:

2.2、ASM集合核心API

2.1.1、ClassReader

ClassReader 用于读取 .class 文件的字节码,解析其内容并提供访问类结构的方法。常用的方法有:

  • Classreader(InputStream):从输入流读取类字节码。
  • accept(ClassVisitor visitor, int flags):将类的字节码委托给ClassVisitior进行访问和处理,其中flags参数有:
    • ClassReader.EXPAND_FRAMES: 自动计算和修复栈帧。
    • ClassReader.SKIP_DEBUG: 跳过调试信息。
    • ClassReader.SKIP_FRAMES: 跳过栈帧信息。

使用方式:

ClassReader classReader = new ClassReader("java.lang.String");
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES);

2.1.2、ClassWriter

ClassWriter 用于生成或修改类的字节码,并输出为字节数组,甚至可以生成新的字节码文件。常用的方法有:

  • ClassWriter(int flags):创建 ClassWriter 对象。其中flags参数有:
    • ClassWriter.COMPUTE_MAXS:自动计算方法的最大栈深度。
    • ClassWriter.COMPUTE_FRAMES:自动计算方法的栈帧。
  • toByteArray():将生成的字节码转换为字节数组。

使用方式:

ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
byte[] modifiedClass = classWriter.toByteArray();

2.1.3、 ClassVisitor

ClassVisitor 是访问类结构的核心接口,所有对类的操作都需要通过它完成。常用的方法有:

  • visit: 访问类的基本信息(版本号、类名等)。
  • visitMethod:访问类中的方法。
  • visitField: 访问类中的字段。
  • visitEnd: 访问结束。

使用方式:

ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9) {@Overridepublic void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {System.out.println("Class name:" + name);}
};

2.1.4、MethodVisitor

MethodVisitor 用于访问和修改方法的字节码指令。常用的方法有:

  • visitCode(): 方法开始时调用。
  • visitInsn(int opcode):访问无操作数的指令。
  • visitVarInsn(int opcode, int var):访问局部变量相关指令。
  • visitLdcInsn(Object value): 插入一个常量。
  • visitEnd():方法结束时调用。

使用方式:

MethodVisitor methodVisitor = new MethodVisitor(Opcodes.ASM9) {@Overridepublic void visitCode() {super.visitCode();mv.visitLdcInsn("Hello, ASM!");mv.visitInsn(Opcodes.ARETURN);}
};

2.1.5、 FieldVisitor

FieldVisitor 用于访问和修改类中的字段。常用方法有:

  • visitAnnotation(String descriptor, boolean visible):访问字段上的注解。
  • visitEnd():访问字段结束。

使用方式:

FieldVisitor fieldVisitor = new FieldVisitor(Opcodes.ASM9) {@Overridepublic void visitEnd() {System.out.println("Field visit finished.");}
};

2.1.6、Opcodes

Opcodes 是 ASM 提供的一组常量,用于表示字节码中的操作码、访问标志等。常用的常量有:

  • 类访问标志:
    • Opcodes.ACC_PUBLIC:表示公共访问权限。
    • Opcodes.ACC_FINAL:表示不可继承。
  • 方法操作码:
    • Opcodes.RETURN:表示方法返回。
    • Opcodes.INVOKEVIRTUAL:表示调用实例方法。
  • 字段操作码:
    • Opcodes.GETFIELD:表示获取字段的值。
    • Opcodes.PUTFIELD:表示设置字段的值。

使用方式:

MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "myMethod", "()V", null, null);

3、简单示例

3.1、maven依赖

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

3.2、hello world

使用ASM操作字节码,创建一个MyClass类,并构造一个方法,方法打印hello world, i am from asm信息。

package org.example.asm;import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;public class AsmDemo {public static void main(String[] args) throws Exception {// 创建一个 ClassWriter 实例,用于生成类的字节码ClassWriter classWriter = new ClassWriter(0);// 定义一个新的类 MyClass// Opcodes.V1_8 表示 Java 8 的版本号// Opcodes.ACC_PUBLIC 表示类的访问权限为 publicclassWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "MyClass", null, "java/lang/Object", null);// 添加默认的构造函数// Opcodes.ACC_PUBLIC 表示构造函数的访问权限为 public// <init> 表示构造函数的名称,必须为 <init>。在 Java 源代码中,编译器会自动为类生成构造方法(如果未显式声明),生成的构造方法在字节码中始终以 <init> 作为名称。<init> 是 JVM 规范中固定表示构造方法的名称。// ()V 表示构造函数的参数类型为空,即该方法无参MethodVisitor constructor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);constructor.visitCode();// visitVarInsn 用于操作局部变量的字节码指令方法// Opcodes.ALOAD 表示加载一个局部变量到操作数栈上,0表示当前对象 thisconstructor.visitVarInsn(Opcodes.ALOAD, 0);// Opcodes.INVOKESPECIAL 表示调用一个特殊方法,这里调用父类 Object 的构造函数constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);constructor.visitInsn(Opcodes.RETURN); // 返回constructor.visitMaxs(1, 1); // 设置操作数栈和局部变量表的最大深度constructor.visitEnd(); // 结束构造函数的定义// 添加 sayHello 方法,同上构造函数创建MethodVisitor mv = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "sayHello", "()V", null, null);mv.visitCode();// 获取 System.out// Opcodes.GETSTATIC 表示从静态字段中获取值,这里获取 System.out// Ljava/io/PrintStream; 表示 PrintStream 类型mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");// 调用 PrintStream.println 方法mv.visitLdcInsn("Hello world, i am  from ASM!"); // 将字符串 "Hello from ASM!" 压入操作数栈mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); // 调用 println 方法mv.visitInsn(Opcodes.RETURN); // 返回mv.visitMaxs(2, 2); // 设置操作数栈和局部变量表的最大深度mv.visitEnd(); // 结束 sayHello 方法的定义// 完成类定义classWriter.visitEnd();// 获取生成的类的字节码byte[] classData = classWriter.toByteArray();// 创建自定义类加载器MyClassLoader classLoader = new MyClassLoader();// 使用自定义类加载器加载生成的类Class<?> myClass = classLoader.defineClass("MyClass", classData);// 创建 MyClass 的实例Object instance = myClass.getDeclaredConstructor().newInstance();// 调用 MyClass 的 sayHello 方法myClass.getMethod("sayHello").invoke(instance);}// 自定义类加载器static class MyClassLoader extends ClassLoader {public Class<?> defineClass(String name, byte[] data) {// 使用父类的 defineClass 方法定义类return defineClass(name, data, 0, data.length);}}
}

3.3、执行结果

4、ASM和Javassist

上面介绍了基本的ASM用法以及API,在上一篇中讲述了Javassist的方式操作字节码。同样操作字节码的技术,我们对比一下两者的区别以及联系。

4.1、操作层级

ASM:基于字节码指令的低级别操作,直接操作字节码,接近 JVM 的底层实现。开发者需要熟悉 JVM 字节码的结构(例如操作数栈、局部变量表、指令等),更灵活但也更复杂。

Javassist:基于高层级的 API,提供类似 Java 源代码的操作方式,无需直接理解和操作字节码指令。更加高抽象,适合快速开发动态字节码功能,易于理解和使用。

4.2、性能

ASM:性能更高,因为它直接操作字节码,无额外的抽象层。更适合性能敏感的场景,例如框架底层实现或对运行时性能要求非常高的工具。

Javassist:性能略低于 ASM,因为其高层级 API 会引入一定的开销。

4.3、应用场景

ASM:用于开发高性能框架和工具,例如 AOP 框架、性能监控工具等;需要对字节码做精细控制。适合在Spring、MyBatis 等框架使用 ASM 提供底层的字节码增强能力。

Javassist:用于快速开发动态字节码功能,例如动态代理、简单方法增强;更适合业务层代码增强场景。适合快速开发,例如动态生成 POJO 类、简单的性能监控工具等。

综上,其实不难可以看出,ASM更接近于字节码底层的操作手法,天然的更具备灵活性,但是相应的代码的可读性和学习难度也较高。而Javassist更多像是个二方包,将底层字节码的操作方式封装为可读性更强的API,更方便开发者进行调用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/1792.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL数据库(SQL分类)

SQL分类 分类全称解释DDLData Definition Language数据定义语言&#xff0c;用来定义数据库对象&#xff08;数据库&#xff0c;表&#xff0c;字段&#xff09;DMLData Manipulation Language数据操作语言&#xff0c;用来对数据库表中的数据进行增删改DQLData Query Languag…

[微服务]redis数据结构

介绍 我们常用的Redis数据类型有5种&#xff0c;分别是&#xff1a; StringListSetSortedSetHash 还有一些高级数据类型&#xff0c;比如Bitmap、HyperLogLog、GEO等&#xff0c;其底层都是基于上述5种基本数据类型。因此在Redis的源码中&#xff0c;其实只有5种数据类型。 …

PyQt5

PyQt5 环境搭建安装 pycharm安装 PyQt5 打包成exe安装 pyinstaller打包 报错进程已结束&#xff0c;退出代码-1073740791&#xff08;0xC0000409&#xff09; 环境搭建 安装 pycharm 安装 PyQt5 pip install pyqt5 -i https://pypi.tuna.tsinghua.edu.cn/simplepip install …

高级运维:shell练习2

1、需求&#xff1a;判断192.168.1.0/24网络中&#xff0c;当前在线的ip有哪些&#xff0c;并编写脚本打印出来。 vim check.sh #!/bin/bash# 定义网络前缀 network_prefix"192.168.1"# 循环遍历1-254的IP for i in {1..254}; do# 构造完整的IP地址ip"$network_…

Grails应用http.server.requests指标数据采集问题排查及解决

问题 遇到的问题&#xff1a;同一个应用&#xff0c;Spring Boot(Java)和Grails(Groovy)混合编程&#xff0c;常规的Spring Controller&#xff0c;可通过Micromete Pushgateway&#xff0c; 采集到http.server.requests指标数据&#xff0c;注意下面的指标名称是点号&#…

pycharm+pyside6+desinger实现查询汉字笔顺GIF动图

一、引言 这学期儿子语文期末考试有一道这样的题目&#xff1a; 这道题答案是B&#xff0c;儿子做错了选了C。我告诉他“车字旁”和“车”的笔顺是不一样的&#xff0c;因为二者有一个笔画是不一样的&#xff0c;“车字旁”下边那笔是“提”&#xff0c;而“车”字是“横”&am…

【2025 Rust学习 --- 17 文本和格式化 】

字符串与文本 Rust 的主要文本类型 String、str 和 char 内容概括&#xff1a; Unicode 背景知识&#xff1f;单个 Unicode 码点的 char&#xff1f;String 类型和 str 类型都是表示拥有和借用的 Unicode 字符序列。Rust 的字符串格式化工具&#xff0c;比如 println! 宏和 …

EasyCVR视频汇聚平台如何配置webrtc播放地址?

EasyCVR安防监控视频系统采用先进的网络传输技术&#xff0c;支持高清视频的接入和传输&#xff0c;能够满足大规模、高并发的远程监控需求。平台支持多协议接入&#xff0c;能将接入到视频流转码为多格式进行分发&#xff0c;包括RTMP、RTSP、HTTP-FLV、WebSocket-FLV、HLS、W…

rknn环境搭建之docker篇

目录 1. rknn简介2. 环境搭建2.1 下载 RKNN-Toolkit2 仓库2.2 下载 RKNN Model Zoo 仓库2.3 下载交叉编译器2.4 下载Docker镜像2.5 下载ndk2.5 加载docker镜像2.6 docker run 命令创建并运行 RKNN Toolkit2 容器2.7 安装cmake 3. 模型转换3.1 下载模型3.2 模型转换 4. 编译cdem…

【MySQL实战】mysql_exporter+Prometheus+Grafana

要在Prometheus和Grafana中监控MySQL数据库&#xff0c;如下图&#xff1a; 可以使用mysql_exporter。 以下是一些步骤来设置和配置这个监控环境&#xff1a; 1. 安装和配置Prometheus&#xff1a; - 下载和安装Prometheus。 - 在prometheus.yml中配置MySQL通过添加以下内…

W25Q64-FLASH

前言&#xff1a; 1.理解flash的组织结构&#xff0c;block块, sector扇区&#xff0c;page页&#xff0c;之间的结构怎么组织安排划分的。 2.理解flash的特性&#xff0c;只能从1写为0&#xff0c;不能从0写为1&#xff0c;这就是为什么写之前要先擦除操作。(这个特性一直困扰…

FPGA EDA软件的位流验证

位流验证&#xff0c;对于芯片研发是一个非常重要的测试手段&#xff0c;对于纯软件开发人员&#xff0c;最难理解的就是位流验证。在FPGA芯片研发中&#xff0c;位流验证是在做什么&#xff0c;在哪些阶段需要做位流验证&#xff0c;如何做&#xff1f;都是问题。 我们先整体的…

Docker官网安装

1.官网 官方文档 https://www.docker.com/ Docker Hub官网 镜像 https://hub.docker.com/ 2.Docker 的三要素 1、镜像 2、容器 3、仓库 小总结 3.Docker 平台架构图 &#xff08;架构版本&#xff09; 4.安装Docker CentOS | Docker Docs 1.确定你是CentOS7及以上版本 …

互斥与同步

1&#xff1a;思维导图 2&#xff1a;有一个隧道&#xff0c;长1000m&#xff0c;有一辆高铁&#xff0c;每秒100米&#xff0c;有一辆快车&#xff0c;每秒50m 要求模拟这两列火车通过隧道的场景。 3&#xff1a;有一个隧道&#xff0c;长1000m&#xff0c;有一辆高铁&#…

LabVIEW智能水肥一体灌溉控制系统

本文详细介绍了一种基于LabVIEW的智能水肥一体灌溉控制系统的设计与实现。该系统采用模糊控制策略&#xff0c;能够自动调节土壤湿度和肥液浓度&#xff0c;满足不同作物在不同生长阶段的需求&#xff0c;有效提高水肥利用效率&#xff0c;对现代精准农业具有重要的实践和推广价…

迅为RK3568开发板篇OpenHarmony配置HDF驱动控制LED-配置创建私有配置文件

接 下 来 新 建 vendor/hihope/rk3568/hdf_config/khdf/topeet/topeet_config.hcs 文 件 &#xff0c;topeet_config.hcs 为驱动私有配置文件&#xff0c;用来填写一些驱动的默认配置信息。HDF 框架在加载驱动时&#xff0c;会获取相应的配置信息并将其保存在 HdfDeviceObject …

鸿蒙面试 2025-01-10

写了鉴权工具&#xff0c;你在项目中申请了那些权限&#xff1f;&#xff08;常用权限&#xff09; 位置权限 &#xff1a; ohos.permission.LOCATION_IN_BACKGROUND&#xff1a;允许应用在后台访问位置信息。 ohos.permission.LOCATION&#xff1a;允许应用访问精确的位置信息…

Pycharm 使用教程

一、基本配置 1. 切换Python解释器 pycharm切换解释器版本 2. pycharm虚拟环境配置 虚拟环境的目的&#xff1a;创建适用于该项目的环境&#xff0c;与系统环境隔离&#xff0c;防止污染系统环境&#xff08;包括需要的库&#xff09;虚拟环境配置存放在项目根目录下的 ven…

C++中的STL

STL&#xff08;标准模板库&#xff09;在广义上分为&#xff1a;容器&#xff0c;算法&#xff0c;迭代器 容器和算法之间通过迭代器进行无缝衔接 STL大体上分为六大组件:分别为容器&#xff0c;算法&#xff0c;迭代器&#xff0c;仿函数&#xff0c;适配器&#xff0c;空间…

STL之VectorMapList针对erase方法踩坑笔记

前沿 如下总结的三种容器&#xff0c;开头都会涉及当前容器的特点&#xff0c;再者就本次针对erase方法的使用避坑总结。 一.Vector vector关联关联容器&#xff0c;存储内存是连续&#xff0c;且特点支持快速访问&#xff0c;但是插入和删除效率比较地(需要找查找和移动)。另…