Javassist 字节码操作教程:方法内省与定制化
引言
Javassist 是一个强大的 Java 字节码操作库,它提供了两种级别的 API:源代码级别和字节码级别。本文将重点介绍如何使用 Javassist 进行方法级别的内省和定制化操作。
CtClass 内省功能
CtClass 类提供了丰富的方法内省功能,这些功能与 Java 反射 API 兼容。主要的内省方法包括:
getName():获取类名getSuperclass():获取父类getMethods():获取所有方法getDeclaredMethod():获取特定方法
方法修改基础
方法表示
在 Javassist 中,方法由 CtMethod 对象表示。每个方法声明都对应一个 CtMethod 对象。需要注意的是,如果方法是从父类继承而来而未被子类覆盖,那么父类和子类中的该方法将由同一个 CtMethod 对象表示。
方法修改限制
Javassist 有一些修改限制需要注意:
-
不能直接删除方法或字段,但可以通过重命名并设置为私有来"移除"方法:
ctMethod.setName("oldMethod_deprecated"); ctMethod.setModifiers(Modifier.PRIVATE); -
不能为现有方法添加额外参数,解决方案是添加新方法:
// 原始方法 void move(int newX, int newY) { x = newX; y = newY; } // 添加带额外参数的新方法 void move(int newX, int newY, int newZ) { // 处理 newZ move(newX, newY); }
方法体操作
基本插入操作
Javassist 提供了几种在方法体中插入代码的方式:
insertBefore(String code):在方法开始处插入代码insertAfter(String code):在方法返回前插入代码addCatch(String code, CtClass exceptionType):添加 catch 块insertAt(int lineNum, String code):在指定行号插入代码
代码片段格式
插入的代码可以是:
- 单条语句(以分号结尾)
- 代码块(用花括号包围)
示例:
System.out.println("Hello");
{ System.out.println("Hello"); }
if (i < 0) { i = -i; }
特殊变量
Javassist 提供了一系列以 $ 开头的特殊变量:
| 变量 | 描述 |
|---|---|
$0 | 相当于 this(静态方法中不可用) |
$1, $2, ... | 方法参数 |
$args | 参数数组(Object[] 类型) |
$$ | 所有参数逗号分隔的列表 |
$cflow | 控制流深度 |
$r | 返回类型,用于强制类型转换 |
$w | 包装类型,用于强制类型转换 |
$_ | 方法返回值 |
$sig | 参数类型数组(Class[]) |
$type | 返回类型(Class) |
$class | 当前类(Class) |
参数访问示例
假设有 Point 类:
class Point {
int x, y;
void move(int dx, int dy) { x += dx; y += dy; }
}
要在 move() 方法调用时打印参数:
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
CtMethod m = cc.getDeclaredMethod("move");
m.insertBefore("{ System.out.println($1); System.out.println($2); }");
cc.writeFile();
修改后的方法相当于:
void move(int dx, int dy) {
{ System.out.println(dx); System.out.println(dy); }
x += dx; y += dy;
}
控制流示例
对于递归方法:
int fact(int n) {
if (n <= 1) return n;
else return n * fact(n - 1);
}
只在第一次调用时打印参数:
CtMethod cm = ...;
cm.useCflow("fact");
cm.insertBefore("if ($cflow(fact) == 0)"
+ " System.out.println(\"fact \" + $1);");
返回值处理
使用 $_ 访问返回值:
CtMethod cm = ...;
cm.insertAfter("System.out.println(\"Result: \" + $_);");
类型转换
返回类型转换 ($r)
Object result = ...;
$_ = ($r)result; // 根据方法返回类型进行转换
包装类型转换 ($w)
Integer i = ($w)5; // 基本类型转包装类型
运行时支持
使用特殊变量(如 $0, $1 等)修改的类需要在运行时包含 javassist.runtime 包。如果不使用这些特殊变量,则不需要任何 Javassist 运行时支持。
总结
Javassist 提供了强大的方法修改能力,通过本文介绍的技术,开发者可以:
- 在方法前后插入自定义代码
- 添加异常处理逻辑
- 访问和修改方法参数
- 处理返回值
- 监控递归调用深度
这些功能使得 Javassist 成为动态代码生成和修改的强大工具,特别适用于 AOP、动态代理等场景。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



