news 2026/7/30 12:25:20

ASM详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ASM详解

ASM 是一个 Java 字节码操作框架,它允许你在不破坏原有逻辑的情况下,动态地生成、修改和转换字节码。CGLIB 等著名的动态代理框架底层正是基于 ASM 来实现的。

要在方法执行前后插入自定义逻辑(类似 AOP 面向切面编程),核心是理解 ASM 的访问者模式(Visitor Pattern)。以下是 ASM 的基本方法和使用步骤:

1. ASM 的核心组件

在操作字节码时,主要会用到以下三个核心类:

  • ClassReader(类读取器):负责读取已经编译好的.class文件的字节码,并将解析出的类结构以事件的形式传递给ClassVisitor
  • ClassVisitor(类访问者):抽象类,用于访问和修改类的结构。当ClassReader解析到类的方法、字段等节点时,会回调ClassVisitor中对应的visit方法。
  • ClassWriter(类写入器)ClassVisitor的一个实现类。它负责接收修改后的字节码事件,并最终通过toByteArray()方法生成修改后的字节码数组。

2. 实现方法执行前后动作的核心逻辑

要在方法执行前后插入逻辑,需要结合ClassVisitorMethodVisitor(方法访问者)来实现。核心步骤如下:

第一步:拦截目标方法
重写ClassVisitorvisitMethod方法。在这个方法中,你可以判断当前正在访问的方法名,如果匹配目标方法,则返回一个自定义的MethodVisitor;否则返回默认的MethodVisitor

第二步:在方法进入时插入前置逻辑
在自定义的MethodVisitor中,重写visitCode()方法。该方法会在 ASM 开始访问某一个方法的 Code 区(即方法体)时被调用。在这里插入的字节码指令,就会在目标方法执行前运行。

第三步:在方法退出时插入后置逻辑
重写visitInsn(int opcode)方法。ASM 在访问到无参数的指令(如returnathrow)时会回调此方法。你需要判断当前的opcode是否为返回或抛出异常的指令,如果是,则在执行该指令前插入后置逻辑的字节码。

3. 代码实战演示

假设我们有一个Base类,其中包含一个process()方法。我们希望在process()方法执行前打印 "start",执行后打印 "end"。

java

import org.objectweb.asm.*; public class AopClassVisitor extends ClassVisitor { public AopClassVisitor(ClassVisitor classVisitor) { super(Opcodes.ASM9, classVisitor); } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); // 1. 拦截目标方法,跳过构造方法 <init> if (mv != null && !"process".equals(name) && !"<init>".equals(name)) { return mv; } if ("<init>".equals(name)) { return mv; } // 返回自定义的 MethodVisitor 来处理 process 方法 return new MethodVisitor(Opcodes.ASM9, mv) { // 2. 方法进入时,插入前置逻辑 @Override public void visitCode() { super.visitCode(); // 插入 System.out.println("start"); 的字节码 mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("start"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); } // 3. 方法退出时,插入后置逻辑 @Override public void visitInsn(int opcode) { // 判断是否是 return 或 throw 指令 if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) { // 在真正返回前,插入 System.out.println("end"); 的字节码 mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("end"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); } // 执行原有的 return 或 throw 指令 super.visitInsn(opcode); } }; } }

4. 完整的转换流程

将上述ClassVisitorClassReaderClassWriter结合,即可完成字节码的修改:

java

// 1. 读取原始类的字节码 ClassReader cr = new ClassReader("com/example/Base"); // 2. 创建 ClassWriter 用于生成新字节码 ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_FRAMES); // 3. 使用自定义的 AopClassVisitor 进行处理 ClassVisitor cv = new AopClassVisitor(cw); // 4. 触发解析和修改过程 cr.accept(cv, 0); // 5. 获取修改后的字节码数组 byte[] newBytes = cw.toByteArray();

下面我将一步步解析CGLIB是如何利用 ASM 实现这一过程的,并辅以带详细注释的代码示例。

第一步:定义目标类和拦截器

首先,我们需要一个被代理的普通类(无需实现接口)和一个用于定义增强逻辑的拦截器。

java

// 1. 目标类:一个普通的类,没有实现任何接口 public class UserService { public void addUser(String username) { System.out.println("【核心业务】添加用户: " + username); } } // 2. 拦截器:实现 MethodInterceptor 接口,定义前置和后置逻辑 import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class LogInterceptor implements MethodInterceptor { /** * 所有被代理的方法调用都会经过这里 * @param obj 代理对象本身 * @param method 被调用的方法 * @param args 方法参数 * @param methodProxy 用于调用父类(目标类)原始方法的代理 */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 前置增强逻辑 System.out.println("[前置日志] 方法 " + method.getName() + " 开始执行..."); // 调用目标类的原始方法 // invokeSuper 会绕过代理,直接调用父类(UserService)的方法 Object result = methodProxy.invokeSuper(obj, args); // 后置增强逻辑 System.out.println("[后置日志] 方法 " + method.getName() + " 执行完毕."); return result; } }

第二步:使用 Enhancer 创建代理对象

Enhancer是 CGLIB 的核心入口类。它负责配置代理参数并触发代理类的生成。

java

import net.sf.cglib.proxy.Enhancer; public class CglibDemo { public static void main(String[] args) { // 1. 创建 Enhancer 对象,它是代理类的“工厂” Enhancer enhancer = new Enhancer(); // 2. 设置父类(即目标类)。CGLIB 会通过继承来创建代理类 enhancer.setSuperclass(UserService.class); // 3. 设置回调函数。当代理类的方法被调用时,会执行这个拦截器 enhancer.setCallback(new LogInterceptor()); // 4. 创建代理对象。 // 【关键步骤】在这里,CGLIB 会调用底层的 ASM,在内存中动态生成一个 // UserService 的子类,例如 UserService$$EnhancerByCGLIB$$xxxx UserService proxy = (UserService) enhancer.create(); // 5. 调用代理对象的方法,触发拦截器逻辑 proxy.addUser("Alice"); } }

运行结果:

text

[前置日志] 方法 addUser 开始执行... 【核心业务】添加用户: Alice [后置日志] 方法 addUser 执行完毕.

第三步:CGLIB 底层如何使用 ASM(核心原理)

当你调用enhancer.create()时,CGLIB 内部会启动一个复杂的字节码生成流程。虽然我们无法看到 CGLIB 内部的源码,但其核心逻辑可以概括为以下使用 ASM 的步骤:

  1. 分析目标类:CGLIB 首先会读取UserService的字节码,分析其所有非final的方法。
  2. 生成子类骨架:使用 ASM 的ClassWriter开始构建一个新的类,这个类继承自UserService,并实现 CGLIB 内部的Factory接口。
  3. 重写方法并注入拦截逻辑:这是最关键的一步。CGLIB 会为每一个需要代理的方法(如addUser)生成重写代码。用 ASM 的伪代码表示如下:

java

// 这是 CGLIB 通过 ASM 在内存中生成的代理类的大致结构 public class UserService$$EnhancerByCGLIB$$xxxx extends UserService { // CGLIB 会在类中生成一个字段,用来持有我们设置的拦截器 private MethodInterceptor CGLIB$CALLBACK_0; // 重写目标类的 addUser 方法 @Override public void addUser(String username) { // 【ASM 生成的字节码逻辑】 // 1. 获取拦截器实例 MethodInterceptor interceptor = this.CGLIB$CALLBACK_0; // 2. 如果拦截器不为空,则将调用委托给它 if (interceptor != null) { // 调用 intercept 方法,并将 this、Method对象、参数、MethodProxy 传入 // 注意:CGLIB 会预先缓存 Method 和 MethodProxy 对象以提升性能 interceptor.intercept( this, CGLIB$addUser$Method, // 预缓存的 Method 对象 new Object[]{username}, // 参数数组 CGLIB$addUser$Proxy // 预缓存的 MethodProxy 对象 ); } else { // 3. 如果没有拦截器,则直接调用父类方法 super.addUser(username); } } }
  1. 生成 FastClass 以优化性能:为了避免 Java 反射的性能开销,CGLIB 还会利用 ASM 生成两个额外的FastClass
    • 一个为代理类生成方法索引,使得MethodProxy.invokeSuper()可以通过索引直接调用父类方法,而不是通过反射。
    • 一个为目标类生成方法索引,用于其他场景下的快速方法调用。

总结

CGLIB 对 ASM 的使用,本质上是将“在方法前后插入逻辑”这个高级需求,翻译成了底层的字节码操作:

  • 它用 ASM 创建了一个子类ClassWriter,visitMethod)。
  • 它用 ASM 重写了目标方法,并在方法体中插入了调用MethodInterceptor.intercept()的指令(MethodVisitor,visitCode,visitMethodInsn)。
  • 它用 ASM 生成了辅助类FastClass),通过方法索引来替代反射,极大地提升了运行时性能。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/30 12:24:14

Visual Studio 2019 C++开发环境:从安装配置到项目实战全指南

1. 项目概述&#xff1a;为什么选择Visual Studio 2019进行C开发&#xff1f;如果你正准备踏入C开发的世界&#xff0c;或者从其他IDE&#xff08;比如Dev-C、Code::Blocks&#xff09;迁移过来&#xff0c;那么Visual Studio 2019&#xff08;简称VS2019&#xff09;大概率会成…

作者头像 李华
网站建设 2026/7/30 12:19:18

Base64:让二进制数据变成纯文本

文章目录Base64&#xff1a;让二进制数据变成纯文本1. 引入&#xff1a;为什么需要Base64&#xff1f;1.1 一个场景1.2 Base64的解决方案2. Base64编码原理2.1 核心思想2.2 如果数据不是3的倍数怎么办&#xff1f;2.3 解码过程3. Java中的Base64使用3.1 标准库&#xff08;Java…

作者头像 李华
网站建设 2026/7/30 12:14:28

综合能源系统优化调度中的绿证-碳排协同交易机制

1. 项目背景与核心挑战在双碳目标背景下&#xff0c;综合能源系统的优化调度面临两个关键约束&#xff1a;可再生能源配额制度下的绿证交易机制&#xff0c;以及碳排放权交易市场的价格波动。传统调度模型往往将二者割裂考虑&#xff0c;导致实际运行中出现政策执行偏差或经济性…

作者头像 李华
网站建设 2026/7/30 12:12:35

AI生成内容检测与降重技术方案解析

1. 项目背景与核心挑战 最近在学术圈和内容创作领域&#xff0c;AI生成内容&#xff08;AIGC&#xff09;的检测与降重需求急剧升温。作为一名长期关注AI技术应用的从业者&#xff0c;我注意到2024-2025年间各大高校和期刊对AI生成论文的审查标准越来越严格。许多研究者发现&am…

作者头像 李华
网站建设 2026/7/30 12:11:21

2022开源安全平台选型指南:Wazuh、ELK、Graylog、Loki深度横评与实战部署

1. 项目缘起&#xff1a;为什么我们需要重新审视开源安全平台 去年年底&#xff0c;我负责的一个内部安全项目到了选型阶段&#xff0c;核心需求是构建一套能够覆盖威胁感知、攻击监控和日志分析的平台。预算有限&#xff0c;商业方案动辄百万的授权费让我们望而却步&#xff0…

作者头像 李华