Javassist 字节码编程入门:用 ClassPool 动态生成 HelloWorld 类的完整实战(CodeGuide 字节码编程专栏)
2026/9/23 11:07:55 网站建设 项目流程
  • 文档
  • 教程
  • 后端

【免费下载链接】CodeGuide

:books: 本代码库是作者小傅哥多年从事一线互联网 Java 开发的学习历程技术汇总,旨在为大家提供一个清晰详细的学习教程,侧重点更倾向编写Java核心内容。如果本仓库能为您提供帮助,请给予支持(关注、点赞、分享)!

项目地址:https://gitcode.com/gh_mirrors/code/CodeGuide
点击查看免费下载

本文基于 CodeGuide 仓库「字节码编程」专栏的Javassist 篇一编写,围绕ClassPoolCtClassCtMethodCtConstructor四个核心 API,完整演示如何通过 Javassist 在运行时动态生成一个带有main方法和无参构造函数的类,并输出HelloWorld。读完本文,你将掌握 Javassist 字节码编程的基本语法骨架,理解「类池 → 类 → 方法/构造器 → 输出/加载」的完整流程,为后续的字节码插桩监控、热加载替换等实战场景打下基础。

一、为什么需要字节码编程:三大框架概览

在 Java 生态中,对.class字节码进行操作的常见框架主要有三个:ASMByte-buddyJavassist。它们都能对字节码进行操作,区别在于操作方式与控制粒度:

  • ASM更偏向底层,使用者需要了解 JVM 虚拟机规范,以及局部变量表、操作数栈等相关知识。编写起来相对繁琐,但它是性能最好、功能最强的字节码操作框架,常见于 CGLIB 动态代理,以及一些非入侵的探针监控场景。
  • Byte-buddyJavassist都提供了强大的高级 API,操作使用上更容易控制。虽然与 ASM 相比性能上会略逊一筹,但并非性能不好,同样在大量监控场景中被广泛使用——你甚至可以在自己工程的 jar 包里搜索到它们的身影。

这部分技术内容在 CRUD 日常开发中并不常用,但随着自动化测试、非入侵监控的大量使用,掌握字节码编程逐渐成为不少研发岗位的进阶技能。CodeGuide 仓库为此专门开设了「字节码编程」专栏,从JavassistASM逐步成体系地组织文章,本文即是其中的第一篇入门案例。同一专栏还收录了 JavaAgent 全链路监控系列 与 ASM 字节码编程系列,可作为后续深入学习的素材。

二、开发环境准备

本文案例的开发环境如下:

  1. JDK 1.8.0
  2. 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 概念:

  1. 创建 ClassPoolClassPool.getDefault()获取默认的类池,它是一个基于 HashMap 实现的CtClass对象容器,负责管理所有被 Javassist 操作的类对象,并维护类的搜索路径。
  2. 使用 CtClass 创建类信息pool.makeClass("org.itstack.demo.javassist.HelloWorld")指定类的全限定路径与名称,返回一个CtClass对象,后续所有针对该类的操作(加方法、加构造器、输出字节码)都围绕它展开。
  3. 给类添加方法:通过CtMethod构造器依次指定方法的返回类型CtClass.voidType)、方法名称"main")、入参类型数组new CtClass[]{pool.get(String[].class.getName())},即String[])以及所属类ctClass)。再用setModifiers(Modifier.PUBLIC + Modifier.STATIC)设置访问修饰符——这里用"加号组合"的方式同时声明publicstatic,最后用setBody("{...}")写入方法体内容,通过addMethod挂载到类上。
  4. 创建无参构造函数CtConstructor的入参类型数组为空,方法体为"{}"。每一个 Java 类在编译后都会生成这样一个默认构造函数,因此动态生成的类也需要显式补齐。
  5. 输出类内容ctClass.writeFile()将生成好的类字节码输出到工程目录下,可以直观看到 Javassist 生成出来的.class文件内容。
  6. 反射调用验证:先通过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.doubleTypeCtClass.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代表异常对象)。此外,通过MethodInfoCodeAttributeLocalVariableAttribute等可以读取方法的描述符、入参名称与类型;判断静态方法时需通过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核心内容。如果本仓库能为您提供帮助,请给予支持(关注、点赞、分享)!

项目地址:https://gitcode.com/gh_mirrors/code/CodeGuide
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询