Tips: ASM使用访问者模式,学会访问者模式再看ASM更加清晰
什么是ASM
ASM是一个操作Java字节码的类库
学习这个类库之前,希望大家对Java 基本IO和字节码有一定的了解。
高版本的ASM库可以操作它所支持的最高JAVA版本及其以下的字节码
ASM版本 | Java版本 |
---|---|
2.0 | 5 |
3.2 | 6 |
4.0 | 7 |
5.0 | 8 |
6.0 | 9 |
6.1 | 10 |
7.0 | 11 |
7.1 | 13 |
8.0 | 14 |
9.0 | 16 |
9.1 | 17 |
ASM的功能
- 从零生成一个类的字节码
- 分析已存在的字节码
- 对已存在的字节码进行修改
以上是通俗解释,实际上ASM能做的还有很多,需要大家慢慢去探索,著名的Spring在内部就使用了ASM。
ASM如何生成一个类的字节码
在从0生成一个类的字节码的过程中,主要是ClassVisitor和ClassWriter两个类在起作用
ClassVisitor
ClassVisitor是一个抽象类,比较常见的子类有ClassWriter类(Core API)和ClassNode类(Tree API)
fields
public abstract class ClassVisitor {
protected final int api;
protected ClassVisitor cv;
}
- api: 指明当前使用的ASM的版本,在Opcodes有相应的常量供选择,如Opcodes.ASM9
- cv: 用于把ClassVisitor连接起来,就像链表一样
methods
在ASM中使用了访问者模式,这个类中有很多的visitxxxx方法,我们重点理解其中的四个(visit/visitField/visitMethod/visitEnd),也是构成class文件的核心
这些方法用于构建字节码,遵循一定的调用顺序如下
visit
[visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]
(
visitAnnotation |
visitTypeAnnotation |
visitAttribute
)*
(
visitNestMember |
visitInnerClass |
visitRecordComponent |
visitField |
visitMethod
)*
visitEnd
visit() 用于填充类基本信息
public void visit(
int version, //java版本
int access, //访问修饰符
String name, //类名
String signature, //泛型参数,没有泛型为null
String superName, //直接父类
String[] interfaces) //接口集合
visitField() 用于填充类的字段表信息
public FieldVisitor visitField(
final int access, //字段的访问修饰符
final String name, //字段的简单名称
final String descriptor, //字段的描述符
final String signature, //字段的泛型,没有泛型为null
final Object value) //字段的值(static 不是final的会自动添加到<clinit>方法 已证实)
visitMethod() 用于填充类的方法表信息
public MethodVisitor visitMethod(
final int access, //方法的访问修饰符
final String name, //方法的简单名称
final String descriptor, //方法的描述符
final String signature,// 方法的泛型
final String[] exceptions) //方法的额外信息(代码、异常)
visitEnd() 用来表示工作完成了,不再调用visit方法了
public void visitEnd()
ClassWriter
fields
字段基本与class文件的组成一致
public class ClassWriter extends ClassVisitor {
public static final int COMPUTE_MAXS = 1;
public static final int COMPUTE_FRAMES = 2;
private int version;
private final SymbolTable symbolTable;
private int accessFlags;
private int thisClass;
private int superClass;
private int interfaceCount;
private int[] interfaces;
private FieldWriter firstField;
private FieldWriter lastField;
private MethodWriter firstMethod;
private MethodWriter lastMethod;
...
}
methods
构造方法
public ClassWriter(int flags) { //COMPUTE_MAXS或者COMPUTE_FRAMES
this((ClassReader)null, flags);
}
参数解析:
- ClassWriter.COMPUTE_MAXS。ASM会自动计算max stacks和max locals
- ClassWriter.COMPUTE_FRAMES(常用)。ASM会自动计算max stacks、max locals和stack map frames
其他visitxxxx方法参考其父类
public byte[] toByteArray() //用于输出字节码
简单示范
/**
* 生成字节码
* @return
*/
public static byte[] generateClass() {
// 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// 向ClassWriter对象添加内容,也就是调用visitxxx方法
cw.visit();
cw.visitField();
cw.visitMethod();
cw.visitEnd();
// 取出ClassWriter对象的结果
return cw.toByteArray();
}
详细设置字段和方法
如果我们只是定义接口的话,前面的方法已经足够使用了,但如果我们需要更多定义,比如给字段加注解、方法体等等,就需要对visitField返回的FieldVisitor和visitMethod返回的MethodVisitor做进一步操作了。这两个类和之前的ClassVisitor类似
FieldVisitor
也是个抽象类,属性也没啥特别的,和ClassVisitor类似的字段,方法也是visitxxx方法,同样有执行顺序
(
visitAnnotation |
visitTypeAnnotation |
visitAttribute
)*
visitEnd
一般情况基本上是用不到这个,但如果你接收了FieldVisitor,就一定要记得visitEnd()
{
FieldVisitor fv = cw.visitField(
Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC,
"TEST",
"I",
null,
1 //常量值
);
fv.visitEnd();
}
MethodVisitor
也是个抽象类,属性也没啥特别的,和ClassVisitor类似的字段,方法也是visitxxx方法,同样有执行顺序
(visitParameter)*
[visitAnnotationDefault]
(visitAnnotation | visitAnnotableParameterCount | visitParameterAnnotation | visitTypeAnnotation | visitAttribute)*
[
visitCode
(
visitFrame |
visitXxxInsn |
visitLabel |
visitInsnAnnotation |
visitTryCatchBlock |
visitTryCatchAnnotation |
visitLocalVariable |
visitLocalVariableAnnotation |
visitLineNumber
)*
visitMaxs
]
visitEnd
这里给出一个空构造方法定义的实例
{
MethodVisitor mv1 = cw.visitMethod(
Opcodes.ACC_PUBLIC,
"<init>",
"()V",
null,
null
);
// 标志方法体开始
mv1.visitCode();
// 用于执行字节码的一系列方法
mv1.visitVarInsn(Opcodes.ALOAD, 0);
mv1.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv1.visitInsn(Opcodes.RETURN);
// 标志方法体结束,并设定max stacks和max locals,前面对ClassWriter设置了ClassWriter.COMPUTE_FRAMES或者ClassWriter.COMPUTE_MAXS可以乱写这个方法的参数,但不能不调用这个方法
mv1.visitMaxs(1, 1);
//标志结束
mv1.visitEnd();
}
实操生成一个简单类
目标类如下:
public class TestClass {
public int content;
public String result;
public static final boolean flag = true;
public TestClass() {}
}
ASM代码如下
package example.generate;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class Test {
public static byte[] generateClass() {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cw.visit(
Opcodes.V1_8, // Java8
Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, // 访问修饰符
"example/sample/TestClass", // 全限定名
null,
"java/lang/Object", // 直接父类
null
);
{
FieldVisitor fv = cw.visitField( // 不接收返回值效果一样
Opcodes.ACC_PUBLIC,
"content",
"I",
null,
0
);
fv.visitEnd();
}
{
FieldVisitor fv = cw.visitField(
Opcodes.ACC_PUBLIC,
"result",
"Ljava/lang/String;", // 分号不要忘
null,
""
);
fv.visitEnd();
}
{
FieldVisitor fv = cw.visitField(
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL,
"flag",
"Z",
null,
true
);
fv.visitEnd();
}
{
MethodVisitor mv = cw.visitMethod(
Opcodes.ACC_PUBLIC,
"<init>", // 构造方法在字节码里的名字
"()V",
null,
null
);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
测试代码:
@Test
void fun4() {
try {
Class cls = Class.forName("example.sample.TestClass");
Object obj = cls.newInstance();
Field f1 = cls.getField("content");
f1.set(obj, 10);
System.out.println(f1.get(obj));
System.out.println(f1.getName());
} catch (Exception e) {
e.printStackTrace();
}
}