- 文档
- 教程
- 后端
【免费下载链接】CodeGuide
:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总,旨在为大家提供一个清晰详细的学习教程,侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助,请给予支持(关注、点赞、分享)!
本文基于 CodeGuide 仓库「字节码编程」专栏的Javassist 篇一编写,围绕
ClassPool、CtClass、CtMethod、CtConstructor四个核心 API,完整演示如何通过 Javassist 在运行时动态生成一个带有main方法和无参构造函数的类,并输出HelloWorld。读完本文,你将掌握 Javassist 字节码编程的基本语法骨架,理解「类池 → 类 → 方法/构造器 → 输出/加载」的完整流程,为后续的字节码插桩监控、热加载替换等实战场景打下基础。
一、为什么需要字节码编程:三大框架概览
在 Java 生态中,对.class字节码进行操作的常见框架主要有三个:ASM、Byte-buddy、Javassist。它们都能对字节码进行操作,区别在于操作方式与控制粒度:
- ASM更偏向底层,使用者需要了解 JVM 虚拟机规范,以及局部变量表、操作数栈等相关知识。编写起来相对繁琐,但它是性能最好、功能最强的字节码操作框架,常见于 CGLIB 动态代理,以及一些非入侵的探针监控场景。
- Byte-buddy与Javassist都提供了强大的高级 API,操作使用上更容易控制。虽然与 ASM 相比性能上会略逊一筹,但并非性能不好,同样在大量监控场景中被广泛使用——你甚至可以在自己工程的 jar 包里搜索到它们的身影。
这部分技术内容在 CRUD 日常开发中并不常用,但随着自动化测试、非入侵监控的大量使用,掌握字节码编程逐渐成为不少研发岗位的进阶技能。CodeGuide 仓库为此专门开设了「字节码编程」专栏,从Javassist到ASM逐步成体系地组织文章,本文即是其中的第一篇入门案例。同一专栏还收录了 JavaAgent 全链路监控系列 与 ASM 字节码编程系列,可作为后续深入学习的素材。
二、开发环境准备
本文案例的开发环境如下:
- JDK 1.8.0
- javassist 3.12.1.GA
在 Maven 工程中引入 Javassist 依赖:
<dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> <version>3.12.1.GA</version> <type>jar</type> </dependency>说明:以上依赖坐标与版本号以仓库专栏文档为准,适用于 JDK 1.8 环境。若你在更高版本 JDK 或 Spring Boot 工程中使用,可自行升级到兼容的 Javassist 版本,但 API 用法基本保持一致。
三、案例目标:用字节码生成一个 HelloWorld 类
抛开实现过程不谈,本案例的目标非常直观:使用javassist输出一行HelloWorld。等价于用普通 Java 编写出下面这个类:
public class HelloWorld { public static void main(String[] args) { System.out.println("javassist hi helloworld by 小傅哥(bugstack.cn)"); } public HelloWorld() { } }接下来,我们要做的就是用 Javassist 的字节码编程技术,在运行时动态"拼装"出这样一个类,而不是手写这段 Java 源码。
四、技术实现:GenerateClazzMethod 完整代码
输出一个 HelloWorld 本身并不复杂,重点在于从这个小案例中学习 Javassist 的基本语法结构,为后续内容建立基础概念。完整实现代码如下:
/** * 基于 Javassist 动态生成 HelloWorld 类 */ public class GenerateClazzMethod { public static void main(String[] args) throws IOException, CannotCompileException, NotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { ClassPool pool = ClassPool.getDefault(); // 创建类 classname:创建类路径和名称 CtClass ctClass = pool.makeClass("org.itstack.demo.javassist.HelloWorld"); // 添加方法 CtMethod mainMethod = new CtMethod(CtClass.voidType, "main", new CtClass[]{pool.get(String[].class.getName())}, ctClass); mainMethod.setModifiers(Modifier.PUBLIC + Modifier.STATIC); mainMethod.setBody("{System.out.println(\"javassist hi helloworld by 小傅哥(bugstack.cn)\");}"); ctClass.addMethod(mainMethod); // 创建无参数构造方法 CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, ctClass); ctConstructor.setBody("{}"); ctClass.addConstructor(ctConstructor); // 输出类内容 ctClass.writeFile(); // 测试调用 Class clazz = ctClass.toClass(); Object obj = clazz.newInstance(); Method main = clazz.getDeclaredMethod("main", String[].class); main.invoke(obj, (Object)new String[1]); } }这段代码可以分为六块内容,每一块都对应 Javassist 的一个核心 API 概念:
- 创建 ClassPool:
ClassPool.getDefault()获取默认的类池,它是一个基于 HashMap 实现的CtClass对象容器,负责管理所有被 Javassist 操作的类对象,并维护类的搜索路径。 - 使用 CtClass 创建类信息:
pool.makeClass("org.itstack.demo.javassist.HelloWorld")指定类的全限定路径与名称,返回一个CtClass对象,后续所有针对该类的操作(加方法、加构造器、输出字节码)都围绕它展开。 - 给类添加方法:通过
CtMethod构造器依次指定方法的返回类型(CtClass.voidType)、方法名称("main")、入参类型数组(new CtClass[]{pool.get(String[].class.getName())},即String[])以及所属类(ctClass)。再用setModifiers(Modifier.PUBLIC + Modifier.STATIC)设置访问修饰符——这里用"加号组合"的方式同时声明public与static,最后用setBody("{...}")写入方法体内容,通过addMethod挂载到类上。 - 创建无参构造函数:
CtConstructor的入参类型数组为空,方法体为"{}"。每一个 Java 类在编译后都会生成这样一个默认构造函数,因此动态生成的类也需要显式补齐。 - 输出类内容:
ctClass.writeFile()将生成好的类字节码输出到工程目录下,可以直观看到 Javassist 生成出来的.class文件内容。 - 反射调用验证:先通过
ctClass.toClass()把动态类加载进 JVM,再用newInstance()实例化,最后通过反射获取main方法并invoke调用,验证输出结果。
五、测试结果
执行程序后,工程文件夹下会多出 Javassist 动态生成的类文件,同时控制台输出测试结果:
javassist hi helloworld by 小傅哥(bugstack.cn) Process finished with exit code 0至此,一个完整的"Javassist 动态生成类并运行"的闭环已经打通:定义类 → 添加方法 → 添加构造器 → 输出字节码 → 加载到 JVM → 反射调用。
六、Javassist 核心能力纵深:从 HelloWorld 走向实战
本篇 HelloWorld 只是 Javassist 的敲门砖。CodeGuide 仓库中的 Javassist 篇二、篇三、篇四、篇五 在此基础上不断深化,这里梳理一下从本案例延伸出去的几个关键知识点:
1. 属性字段与$1/$2入参引用(篇二)
在实际的字节码编程中,除了方法还需要创建字段。使用CtField可以定义属性,并通过Modifier.PRIVATE + Modifier.STATIC + Modifier.FINAL这类组合方式控制访问级别,例如:
CtField ctField = new CtField(CtClass.doubleType, "π", ctClass); ctField.setModifiers(Modifier.PRIVATE + Modifier.STATIC + Modifier.FINAL); ctClass.addField(ctField, "3.14");在方法体中引用入参时,Javassist 使用$1、$2……这样的占位符按位置取值($0代表this)。同时要特别留意:Javassist 不会进行类型的自动装箱和拆箱,需要显式处理。例如方法返回Double对象类型时,方法体内需要手动编写Double.valueOf($1 + $2)这样的转换,否则生成的类在执行时会报类型错误。基本类型入参可直接使用CtClass.doubleType、CtClass.intType等,而对象类型入参则需通过pool.get(Double.class.getName())获取。
2. 运行时热加载替换(篇三)
利用javassist.tools.HotSwapper,可以在程序运行期间动态重写某个正在执行的方法。其前置条件是启用 JPDA(Java 平台调试器体系结构),即启动参数中加入:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000随后通过pool.get(ApiTest.class.getName())获取类、getDeclaredMethod获取方法、setBody重写方法体,最后用hs.reload(ApiTest.class.getName(), ctClass.toBytecode())完成热插拔。这种能力常见于热部署、模型包升级等场景。
3. 字节码插桩监控(篇四)
插桩技术常与JavaAgent结合用于系统非入侵监控,可以采集方法的执行耗时、入参出参与异常信息。围绕本案例的 API 可以延伸出:用ctMethod.addLocalVariable("startNanos", CtClass.longType)声明局部变量、insertBefore在方法前插入计时逻辑、insertAfter在方法后调用外部监控方法($_代表返回值)、addCatch为方法整体包裹 TryCatch($e代表异常对象)。此外,通过MethodInfo、CodeAttribute、LocalVariableAttribute等可以读取方法的描述符、入参名称与类型;判断静态方法时需通过AccessFlag.STATIC与访问标志做与运算,因为静态方法的第一个参数是this,会影响参数名称的获取。
4. 字节码指令级控制(篇五)
Javassist 不仅提供高级 API,还提供了低级 API 直接控制字节码指令。通过MethodInfo.getCodeAttribute()配合CodeIterator可以遍历方法指令(配合Mnemonic.OPCODE输出指令码),通过Bytecode对象可以追加指令,例如addGetstatic(获取静态域压入栈顶)、addInvokevirtual(调用实例方法)、addReturn(返回 double 结果)、addDconst(将 double 型 0 推送至栈顶),最后用methodInfo.setCodeAttribute(bytecode.toCodeAttribute())替换方法体。这一层能力可用于更精细的字节码改造,例如修改方法返回值、生成带自定义注解的类与方法。
七、总结
- Javassist 提供了完整且强大的 API,动态生成类、方法、构造器的使用门槛较低,代码结构也不难理解,非常适合作为字节码编程的入门框架。
- 本文案例覆盖了字节码编程最基础但最核心的 API 闭环:
ClassPool(类池容器)、CtClass(类)、CtMethod(方法)、CtConstructor(构造器)、writeFile(输出字节码)与toClass(加载到 JVM)。从源码结构看,这正是后续所有 Javassist 实战——属性字段、热加载、插桩监控、指令级控制——共同依赖的地基。 - 关于框架选型:Javassist 相对 ASM 封装更友好,多数场景下无需关心 JVM 局部变量与操作数栈的细节;但若对性能与细粒度控制有更高要求,则可以考虑 ASM。
- 后续内容可在本仓库的「字节码编程」专栏中继续跟进:Javassist 篇二(属性与多类型出入参)、篇三(运行时热加载)、篇四(插桩监控采集出入参与异常)、篇五(Bytecode 指令码生成带注解的类),以及 Byte-buddy 系列 与 JavaAgent 全链路监控系列。
- 文档
- 教程
- 后端
【免费下载链接】CodeGuide
:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总,旨在为大家提供一个清晰详细的学习教程,侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助,请给予支持(关注、点赞、分享)!
相关推荐
终极指南:揭秘Java字节码编程ASM与Javassist实战技巧
终极指南:揭秘Java字节码编程ASM与Javassist实战技巧 Java字节码编程是高级Java开发者必须掌握的核心技能之一,而ASM和Javassist正
文档教程后端字节码编程与性能监控:ASM、Javassist、ByteBuddy
字节码编程与性能监控:ASM、Javassist、ByteBuddy 本文深入探讨了Java字节码编程领域的三大主流框架:ASM、Javassist和ByteB
文档教程后端ExplorerPatcher:Windows 11 换回 Win10 界面的完整工具,3 步装好
ExplorerPatcher:Windows 11 换回 Win10 界面的完整工具,3 步装好 刚升级到 Windows 11,任务栏强制居中、开始菜单整个
桌面应用系统编程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考