1. 简介
本文将介绍如何使用ASM库对现有Java类进行字节码操作,包括添加字段、添加方法以及修改现有方法的行为。ASM作为Java字节码操作领域的瑞士军刀,能让你绕过源码直接操作底层字节码,实现各种骚操作。
2. 依赖配置
首先在pom.xml中添加ASM依赖:
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>6.0</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>6.0</version>
</dependency>
最新版本可在Maven中央仓库获取:asm 和 asm-util。
3. ASM API核心概念
ASM提供两种操作Java类的方式:事件驱动模型和树形模型。
3.1 事件驱动API
该API基于访问者模式实现,类似XML解析中的SAX模型。核心组件包括:
- ClassReader – 读取类文件的入口,是类转换的起点
- ClassVisitor – 提供修改类结构的方法集合
- ClassWriter – 输出转换后的字节码
⚠️ 注意:ClassVisitor的方法调用必须遵循严格顺序,否则会生成错误的字节码:
visit
visitSource?
visitOuterClass?
( visitAnnotation | visitAttribute )*
( visitInnerClass | visitField | visitMethod )*
visitEnd
3.2 树形API
该API更接近面向对象风格,类似XML处理中的JAXB模型。它在事件驱动API基础上引入了ClassNode根类,作为类结构的入口点。
4. 事件驱动API实战
我们将修改java.lang.Integer类作为演示。核心思想是:通过继承ClassVisitor并重写特定方法实现类改造。
先搭建基础框架:
public class CustomClassWriter {
static String className = "java.lang.Integer";
static String cloneableInterface = "java/lang/Cloneable";
ClassReader reader;
ClassWriter writer;
public CustomClassWriter() {
reader = new ClassReader(className);
writer = new ClassWriter(reader, 0);
}
}
4.1 字段操作
创建添加字段的适配器:
public class AddFieldAdapter extends ClassVisitor {
private String fieldName;
private String fieldDefault;
private int access = org.objectweb.asm.Opcodes.ACC_PUBLIC;
private boolean isFieldPresent;
public AddFieldAdapter(
String fieldName, int fieldAccess, ClassVisitor cv) {
super(ASM4, cv);
this.cv = cv;
this.fieldName = fieldName;
this.access = fieldAccess;
}
}
重写visitField方法检查字段是否存在,并转发方法调用(否则字段不会被写入):
@Override
public FieldVisitor visitField(
int access, String name, String desc, String signature, Object value) {
if (name.equals(fieldName)) {
isFieldPresent = true;
}
return cv.visitField(access, name, desc, signature, value);
}
在visitEnd方法中执行字段添加逻辑:
@Override
public void visitEnd() {
if (!isFieldPresent) {
FieldVisitor fv = cv.visitField(
access, fieldName, fieldType, null, null);
if (fv != null) {
fv.visitEnd();
}
}
cv.visitEnd();
}
✅ 关键点:所有ASM组件必须来自org.objectweb.asm包,避免IDE自动引入其他版本。
集成适配器完成字段添加:
public class CustomClassWriter {
AddFieldAdapter addFieldAdapter;
//...
public byte[] addField() {
addFieldAdapter = new AddFieldAdapter(
"aNewBooleanField",
org.objectweb.asm.Opcodes.ACC_PUBLIC,
writer);
reader.accept(addFieldAdapter, 0);
return writer.toByteArray();
}
}
4.2 方法操作
完整生成方法涉及大量字节码操作,这里演示修改方法可见性的简单场景。将toUnsignedString0
方法改为public:
public class PublicizeMethodAdapter extends ClassVisitor {
public PublicizeMethodAdapter(int api, ClassVisitor cv) {
super(ASM4, cv);
this.cv = cv;
}
public MethodVisitor visitMethod(
int access,
String name,
String desc,
String signature,
String[] exceptions) {
if (name.equals("toUnsignedString0")) {
return cv.visitMethod(
ACC_PUBLIC + ACC_STATIC,
name,
desc,
signature,
exceptions);
}
return cv.visitMethod(
access, name, desc, signature, exceptions);
}
}
集成方法修改器:
public byte[] publicizeMethod() {
pubMethAdapter = new PublicizeMethodAdapter(writer);
reader.accept(pubMethAdapter, 0);
return writer.toByteArray();
}
4.3 类操作
通过重写visit方法添加接口实现,使Integer类实现Cloneable:
public class AddInterfaceAdapter extends ClassVisitor {
public AddInterfaceAdapter(ClassVisitor cv) {
super(ASM4, cv);
}
@Override
public void visit(
int version,
int access,
String name,
String signature,
String superName, String[] interfaces) {
String[] holding = new String[interfaces.length + 1];
holding[holding.length - 1] = cloneableInterface;
System.arraycopy(interfaces, 0, holding, 0, interfaces.length);
cv.visit(V1_8, access, name, signature, superName, holding);
}
}
5. 使用修改后的类
5.1 使用TraceClassVisitor验证
TraceClassVisitor可打印字节码修改结果,用于验证变更:
PrintWriter pw = new PrintWriter(System.out);
public PublicizeMethodAdapter(ClassVisitor cv) {
super(ASM4, cv);
this.cv = cv;
tracer = new TraceClassVisitor(cv,pw);
}
public MethodVisitor visitMethod(
int access,
String name,
String desc,
String signature,
String[] exceptions) {
if (name.equals("toUnsignedString0")) {
System.out.println("Visiting unsigned method");
return tracer.visitMethod(
ACC_PUBLIC + ACC_STATIC, name, desc, signature, exceptions);
}
return tracer.visitMethod(
access, name, desc, signature, exceptions);
}
public void visitEnd(){
tracer.visitEnd();
System.out.println(tracer.p.getText());
}
⚠️ 注意:最新版ASM中直接打印可能失效,需手动获取文本内容。
5.2 使用Java Instrumentation
通过Instrumentation实现运行时类替换,步骤:
- 创建带premain方法的代理类
- 实现ClassFileTransformer接口
public class Premain {
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(
ClassLoader l,
String name,
Class c,
ProtectionDomain d,
byte[] b)
throws IllegalClassFormatException {
if(name.equals("java/lang/Integer")) {
CustomClassWriter cr = new CustomClassWriter(b);
return cr.addField();
}
return b;
}
});
}
}
配置MANIFEST.MF:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>
com.baeldung.examples.asm.instrumentation.Premain
</Premain-Class>
<Can-Retransform-Classes>
true
</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
启动应用时加载代理:
java YourClass -javaagent:"/path/to/theAgentJar.jar"
6. 总结
本文演示了ASM的基础操作,实际应用中可以链式组合多个适配器实现复杂转换。除了基本操作,ASM还支持注解、泛型和内部类处理。
Spring、AspectJ等知名框架都在底层使用ASM实现各种"黑科技"。掌握ASM能让你突破源码限制,直接操作字节码实现各种酷炫功能。
完整代码示例见GitHub项目。