Class Generation

对于字节码的generation,主要是ClassVisitor和ClassWriter的参与,ClassReader将在transform部分介绍。

ClassVisitor

ClassVisitor是一个抽象类,不能直接new,它有两个常用的子类分别是ClassWriter(Core API)和ClassNode(Tree API)

/*
A ClassVisitor that generates a corresponding ClassFile structure, as defined in the Java Virtual Machine Specification (JVMS). It can be used alone, to generate a Java class "from scratch", or with one or more ClassReader and adapter ClassVisitor to generate a modified class from one or more existing Java classes.
*/
public class ClassWriter extends ClassVisitor
// A node that represents a class.
public class ClassNode extends ClassVisitor

ClassVisitor定义了两个字段apicv

public abstract class ClassVisitor {
    /**
   * The ASM API version implemented by this visitor. The value of this field must be one of Opcodes
   */
    protected final int api;

    /** The class visitor to which this visitor must delegate method calls. May be null. */
    protected ClassVisitor cv;

    public ClassVisitor(final int api, final ClassVisitor classVisitor) {
        this.api = api;
        this.cv = classVisitor;
    }
}
  • api指定当前使用的ASM版本,取值为Opcodes.ASM4 ~ Opcodes.ASM9,这里的版本是ASM9

  • cv也是一个ClassVisitor,用于连接多个ClassVisitor

ClassVisitor中定义了很多visitXxx方法,这里使用了访问者模式。主要看四个方法:visit()visitField()visitMethod()visitEnd()

这些方法的参数实际上都和字节码文件的结构相对应

  • version – the class version

  • access – the class's access flags

  • name – the internal name of the class

  • signature – the signature of this class. May be null if the class is not a generic one, and does not extend or implement generic classes or interfaces

  • superName – the internal of name of the super class

  • interfaces – the internal names of the class's interfaces

  • access – the field's access flags

  • name – the field's name

  • descriptor – the field's descriptor

  • signature – the field's signature. May be null if the field's type does not use generic types

  • value – the field's initial value

  • access – the method's access flags

  • name – the method's name

  • descriptor – the method's descriptor

  • signature – the method's signature. May be null if the method parameters, return type and exceptions do not use generic types

  • exceptions – the internal names of the method's exception classes

在字节码中,描述符是对类型的简单化描述

  • 对于字段,描述符即字段本身的类型

  • 对于方法,描述符为方法接受的参数类型和返回值的类型

Java 类型
ClassFile 描述符

boolean

Z(Z 表示 Zero,零表示 false,非零表示 true

byte

B

char

C

double

D

float

F

int

I

long

J

short

S

void

V

non-array reference

L<InternalName>;

array reference

[

String

Ljava/lang/String;

Object

Ljava/lang/Object;

void

V

另外,对于visitMethod的第二个参数name,若我们要生成构造方法,这里固定为<init>;若要生成静态代码块,name<clinit>signature()V

这四个方法内部都是调用的cv.visitXxx()

image-20230918115433708

这些visitXxx方法是有调用顺序的

  • []表示最多调用一次

  • *表示调用0次或多次

  • ()|表示可以任选一个方法,不分先后顺序

由于我们只关注四个visitXxx方法,上面的调用可以简化为

ClassWriter

  • COMPUTE_MAXS: A flag to automatically compute the maximum stack size and the maximum number of local variables of methods. If this flag is set, then the arguments of the MethodVisitor.visitMaxs method of the MethodVisitor returned by the visitMethod method will be ignored, and computed automatically from the signature and the bytecode of each method.

  • COMPUTE_FRAMES: A flag to automatically compute the stack map frames of methods from scratch. If this flag is set, then the calls to the MethodVisitor.visitFrame method are ignored, and the stack map frames are recomputed from the methods bytecode. The arguments of the MethodVisitor.visitMaxs method are also ignored and recomputed from the bytecode. In other words, COMPUTE_FRAMES implies COMPUTE_MAXS.

COMPUTE_FRAMES包含了COMPUTE_MAXS的功能,不仅计算max stack sizemax local variables,还计算stack map frames,所以一般都使用COMPUTE_FRAMES

注意:虽然这个字段能够自动计算,但代码中仍要调用visitMaxs,否则会报错

使用ClassWriter创建一个字节码文件,分为三步:

  • 创建ClassWriter对象

  • 调用ClassWriter#visitXxx方法

  • 调用ClassWriter#toByteArray方法

Best Practice

尝试生成如下类

注意这里visit的第三个参数name是Internal Name的格式,Java源码中使用类名的形式是Fully Qualified Class Name,如java.lang.String,编译成class文件后类名的格式会变成Internal Name.变成/,如java/lang/String

对于比较复杂的类,可以用IDEA的插件ASM Bytecode Viewer去生成(网上推荐的ASM Bytecode Outline不兼容高版本的IDEA)

FieldVisitor

ClassVisitor#visitField返回了一个FieldVisitor对象,和ClassVistor类很像

FieldVisitor调用visitXxx也遵循一定的调用顺序

FieldWriter

ClassVisitor#visitField的实现就是通过FieldWriter(父类为FieldVisitor)来实现的

toByteArray方法中也用到了FieldWriter的两个方法computeFieldInfoSize()putFieldInfo()

MethodVisitor

ClassVisitor#visitMethod返回了一个MethodVisitor对象,和ClassVistor类很像。

visitCode()visitMaxs()之间的方法主要负责当前方法的方法体中opcode内容

MethodWriter

ClassVisitor#visitMethod的实现就是通过MethodWriter(父类为MethodVisitor)来实现的,这时候初始化了方法头的信息

同样toByteArray()中用到了MethodWriter的两个方法computeMethodInfoSize()putMethodInfo()

Best Practice

  • challenge1:

  • challenge2:

假设有一个Dog

生成如下类:

  • challenge 3 :

Label

Java程序中有三种基本控制结构:顺序、选择和循环,但转化为字节码后,只存在顺序和跳转两种指令

这时候就要用到Label类了

以下面程序为例

javap -verbose Test查看字节码

image-20230919095703068

ifle表示当栈顶int类型数值小于等于0时跳转(判断条件为假),跳转的位置即后面跟的数字6,6这里表示相对偏移量。

如果判断条件为假,即栈顶int类型数值小于等于0,则跳转到6,iconst_0表示把0这个常量压入operand stack,最后ireturn返回栈顶的int数据;如果判断条件为真,即栈顶int类型数值大于0,则顺序执行4、5,返回1。

Label类的bytecodeOffset即对应上面的相对偏移量

通过调用MethodVisitor#visitLabel(Label)来标记跳转目标

Best Practice

If

Switch

看一眼字节码

image-20230919111907840

这里用到了lookupswitch指令,即将栈顶元素和查找表比较,根据匹配值跳转到不同的语句。

visitLookupSwitchInsn第一个参数为default标签,第二个参数为case匹配的值数组(这里为0和1),第三个为case标签数组,和第二个参数一一对应。

也可以用visitTableSwitchInsn

visitTableSwitchInsn要求case的值是连续的,即max-min+1=labels.length

For

image-20230919120526509
  • iconst_0:0整型常量入栈,作为i的初始值

  • istore_1:栈顶元素存入局部变量表,即0存入locals[1]

  • iload_1:locals[1]入栈

  • bipush 10:10整型入栈

  • if_icmple:比较栈顶两个值的大小,即比较locals[1]和10的大小,若10≤locals[1],即i≥10,跳转到21return

  • iinc 1, 1:对locals[1]进行+1操作。在局部变量表中完成,无需压入操作数栈

Try-Catch

image-20230919130235050

多了个Exception table,其中from to规定try的代码块,使用visitTryCatchBlock来构建

Last updated

Was this helpful?