Java-ASM框架学习-从零构建类的字节码

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的功能

  1. 从零生成一个类的字节码
  2. 分析已存在的字节码
  3. 对已存在的字节码进行修改

以上是通俗解释,实际上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();
}
}
上一篇:HDU3853:LOOPS


下一篇:深入理解linux网络技术-P179