字节码&ASM-基础

在接触ASM之前不得不去了解一些字节码相关的前置知识,掌握字节码的类型描述、JVM代码执行模型是开启ASM大门的钥匙,掌握这把钥匙开启大门才能走上一条康庄大道通往ASM,否则一路摸爬滚打也许就中途放弃了。

字节码中的类型描述

字节码中类型描述其实就是JAVA中类型对应的描述,即JAVA中基础类型和对象,基础类型大部分采用基础类型首字母,但个别因首字母被赋予了其他特殊含义便使用另外的一个字母表示,而对象采用统一的规则进行表示。

字节码类型描述

JAVA类型类型描述 boolean Z char C byte B short S int I float F long J double D void V Object Ljava/lang/Object; Object[] [Ljava/lang/Object; Object[][] [[Ljava/lang/Object; Object[][][]… [[[…Ljava/lang/Object;

基础类型描述符不在过多赘述,而对象无疑看起来率微复杂些,类描述规则: L + 类全限定名 + ;以大写L标志开始;为结束标志,中间内容为类全限定名,类全限定名规则: 包名以/分隔符 + 类名,例如 Object类全限定名为 java/lang/Object,类描述为 Ljava/lang/Obhect;。数组以 [ + 数组元素类型描述表示,一维数组 [,二维数组 [[,三维数组 [[[…以此类推

字节码方法描述

方法描述在字节码中规则: (&#x6240;&#x6709;&#x53C2;&#x6570;&#x7C7B;&#x578B;&#x63CF;&#x8FF0;)&#x8FD4;&#x56DE;&#x503C;&#x7C7B;&#x578B;&#x63CF;&#x8FF0;,当然其中有两个特殊的需要注意,方法名分别为 <init></init><client></client>,其中 <init></init>代表构造函数, <client></client>静态代码块。例如方法 void makeName(String firstName, String lastName)的方法描述为 (Ljava/lang/String;Ljava/lang/String;)

字节码

众所周知JAVA是一种跨平台语言,JVM为JAVA在不同平台表现一致性,需要注意的是JVM并不是跨平台的,不同平台的JVM帮住JAVA程序员屏蔽了平台间的差异。JVM通过加载和执行同一种于平台无关的字节码,而字节码由不同语言通过编译后得到的产物

字节码&ASM-基础

JVM指令由一个字节的操作码(opcode)和紧随其后的可选操作数(operand)构成,比如: opcode operand1, operand2 而JVM执行指令的过程其实就是不断将 opcodeoperand出栈和入栈的过程,比如 a = 1就是通过将变量 a压入执行栈后再将1压入执行栈后再通过存储指令将 a进行存储

字节码&ASM-基础

字节码名字的由来是因为操作码的大小为一个字节,所以操作码集最多由256个,而目前已经使用了超过200个了,虽然操作码的数量已经相当的庞大,但是其中有着很多类似的操作码,这是因为操作码大多是于类型有关的,比如对变量存储的操作码就有 ISTORELSTOREFSTOREDSTOREASTOREIASTORELASTOREFASTOREDASTOREAASTOREBASTORECASTORESASTORE这13个操作码,不难发现其中一般为 &#x7C7B;&#x578B;&#x63CF;&#x8FF0;&#x7B26; + STORE

JAVA虚拟机栈和栈帧

虚拟机实现方式常见的有两种:基于栈和基于寄存器。基于栈的虚拟机有大名鼎鼎的 Hotspot JVM,基于寄存器的虚拟机有 LuaLua VM和Google的Android虚拟机 DalvikVM 二者有什么不同呢?举一个非常简单的例子: c = a + b

int add (int a, int b) {
    return a + b;
}

JAVA:

0:  iload_1
1:  iload_2
2:  iadd
3:  ireturn

第一行指令加a压入栈顶,为什么是 iload_1而不是 iload_0呢?这是因为非类非静态方法编译后在方法局部变量表中第0索引插入了类实例的指针,所以比如类似Golong在书写结构体方法是第一个需要传入结构体指针,而JAVA并非不用穿只是编译器帮你做了而已,好了有些跑偏了,回归正轨,第二行将b压入栈顶,第三行将操作栈中a、b出栈求和后入栈,第四行将上一步求和压入栈顶的值返回,方法结束。

LUA:

[1] ADD         R2 R0 R1
[2] RETURN      R2 2
[3] RETURN      R0 1

第一行调用 ADD指令将 R0R1中的值求和存储到 R2,第二行返回 R2的值,第三行则为 lua特殊的处理为了防止分支遗漏 return指令

基于栈和基于寄存器架构优缺点:

  • 基于栈移植性好、指令更短、实现简单,但不能随机访问堆栈中元素,完成相同功能比基于寄存器所需要的指令数一般都要多,需要频繁出入栈
  • 基于寄存器速度快,可以充分利用寄存器,操作数需要显示指定,指令较长

帧栈

Hotspot JVM是一款基于栈的虚拟机,每个线程都有一个虚拟机栈用来存储栈帧,每次方法的调用都将伴随着栈帧的创建、销毁。栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,栈帧的存储控件分配在JAVA虚拟机栈中,每个栈帧都拥有自己的局部变量表(Local Variable)、操作数栈、常量池的引用。

字节码&ASM-基础
package com.river.asm;

public class HelloWorld {
    private String word = "hello world";

    public void say() {
        System.out.println(word);
    }

    public static void main(String[] args) {
        new HelloWorld().say();
    }
}

通过 javap -v HelloWorld输出字节码相关信息,这里给出 say方法相关内容

  public void say();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: getfield      #3                  // Field word:Ljava/lang/String;
         7: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        10: return
      LineNumberTable:
        line 7: 0
        line 8: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  this   Lcom/river/asm/HelloWorld;

字节码&ASM-基础

局部变量表

每个栈帧内部都包含一组称为局部变量表的变量列表,局部变量表长度在编译期间确定,对应 ClassFile中的 max_locals,JVM根据 max_locals字段来分配方法执行过程所需要的最大容量,需要注意的是long和double需要占用两个变量槽

操作数栈

每个栈帧内部包含一个称为操作数栈的后进先出栈,大小同样在编译期间确定。JVM提供很多字节码指令用于从局部变量表或者对象实例的字段数值压入到操作数栈中,也有一些指定将操作数栈中的值弹出进行一些计算后在复制到局部变量表或类实力字段中去。调用方法时,操作数栈也需要准备好调用方法的参数和接受方法返回的结果。JVM执行指令的过程就是不断的入栈和出栈达到修改局部变量表或类字段的过程。而在方法执行过程中操作数栈的最大值就是 ClassFile中的 max_stack

存储操作符

存储的操作就是将栈顶元素存入到指定索引的变量表中

操作符名称值含义 ISTORE 54 栈顶 int 型数值存入指定局部变量 LSTORE 55 栈顶 long 型数值存入指定局部变量 FSTORE 56 栈顶 float 型数值存入指定局部变量 DSTORE 57 栈顶 double 型数值存入指定局部变量 ASTORE 58 栈顶 object 型数值存入指定局部变量 IASTORE 79 栈顶 int 型数值存入数组指定索引位置 LASTORE 80 栈顶 long 型数值存入数组指定索引位置 FASTORE 81 栈顶 float 型数值存入数组指定索引位置 DASTORE 82 栈顶 double 型数值存入数组指定索引位置 AASTORE 83 栈顶 object 型数值存入数组指定索引位置 BASTORE 84 栈顶 byte | boolean 型数值存入数组指定索引位置 CASTORE 85 栈顶 char 型数值存入数组指定索引位置 SASTORE 86 栈顶 short 型数值存入数组指定索引位置

加载操作符

加载的操作就是将变量表中指定索引的值压入到栈顶

操作符名称值含义 ILOAD 21 局部变量表指定索引类型为 int 型数值压入栈顶 LLOAD 22 局部变量表指定索引类型为 long 型数值压入栈顶 FLOAD 23 局部变量表指定索引类型为 float 型数值压入栈顶 DLOAD 24 局部变量表指定索引类型为 double 型数值压入栈顶 ALOAD 25 局部变量表指定索引类型为 object 型数值压入栈顶 IALOAD 46 局部变量表指定索引类型为 int[] 的指定索引的值压入栈顶 LALOAD 47 局部变量表指定索引类型为 long[] 的指定索引的值压入栈顶 FALOAD 48 局部变量表指定索引类型为 float[] 的指定索引的值压入栈顶 DALOAD 49 局部变量表指定索引类型为 double[] 的指定索引的值压入栈顶 AALOAD 50 局部变量表指定索引类型为 object[] 的指定索引的值压入栈顶 BALOAD 51 局部变量表指定索引类型为 byte | boolean[] 的指定索引的值压入栈顶 CALOAD 52 局部变量表指定索引类型为 char[] 的指定索引的值压入栈顶 SALOAD 53 局部变量表指定索引类型为 short[] 的指定索引的值压入栈顶

常量类型操作符

常量类型操作符其实和加载一样,也是将某个值压入栈顶,记得上一篇文章学习ClassFile中提到一些变量存储在常量池中,一些以字节码形式内嵌到了CODE_ATTR里面,而内嵌到CODE_ATTR中的就是下面的操作符

操作符名称值含义 ACONST_NULL 1 将null压入栈顶 ICONST_M1 2 将-1压入栈顶 ICONST_0 3 将0压入栈顶 ICONST_1 4 将1压入栈顶 ICONST_2 5 将2压入栈顶 ICONST_3 6 将3压入栈顶 ICONST_4 7 将4压入栈顶 ICONST_5 8 将5压入栈顶 LCONST_0 9 将0L压入栈顶 LCONST_1 10 将1L压入栈顶 FCONST_0 11 将0F压入栈顶 FCONST_1 12 将1F压入栈顶 FCONST_2 13 将2F压入栈顶 DCONST_0 14 将0F压入栈顶 DCONST_1 15 将1F压入栈顶 BIPUSH 16 将单字节常量(-128~127)压入栈顶 SIPUSH 17 将short 常量(-32768~32767)压入栈顶 LDC 18 将常量池中的 int,float,String 型常量取出并压入栈顶

栈操作

操作符名称值含义 POP 87 将栈顶值弹出 POP2 88 将栈顶的一个 long 或 double 值弹出,或弹出 2 个其他类型数值 DUP 89 复制栈顶值并压入栈顶 DUP_X1 90 复制栈顶数值并将两个复制值压入栈顶 DUP_X2 91 复制栈顶数值并将三个或两个复制值压入栈顶 DUP2 92 复制栈顶一个(long 或 double 类型的) 或两个(其它)数值并将复制值压入栈顶 DUP2_X1 93 参考DUP_X1 DUP2_X2 94 参考DUP_X2 SWAP 95 交换栈顶连个值

这里需要注意到的是long和double占用两个变量索引位置,所以尽量就使用 POP2DUP2

运算操作符

操作符名称值含义 IADD 96 将栈顶两个int值相加后压入栈顶 LADD 97 将栈顶两个long值相加后压入栈顶 FADD 98 将栈顶两个float值相加后压入栈顶 DADD 99 将栈顶两个double值相加后压入栈顶 ISUB 100 将栈顶两个int值求差后压入栈顶 LSUB 101 将栈顶两个long值求差后压入栈顶 FSUB 102 将栈顶两个float值求差后压入栈顶 DSUB 103 将栈顶两个double值求差后压入栈顶 IMUL 104 将栈顶两个int值求积后压入栈顶 LMUL 105 将栈顶两个long值求积后压入栈顶 FMUL 106 将栈顶两个float值求积后压入栈顶 DMUL 107 将栈顶两个double值求积后压入栈顶 IDIV 108 将栈顶两个int值求商后压入栈顶 LDIV 109 将栈顶两个long值求商后压入栈顶 FDIV 110 将栈顶两个float值求商后压入栈顶 DDIV 111 将栈顶两个double值求商后压入栈顶 IREM 112 将栈顶两个int值求余后压入栈顶 LREM 113 将栈顶两个long值求余后压入栈顶 FREM 114 将栈顶两个float值求余后压入栈顶 DREM 115 将栈顶两个double值求余后压入栈顶 INEG 116 将栈顶int值求反后压入栈顶 LNEG 117 将栈顶long值求反后压入栈顶 FNEG 118 将栈顶float值求反后压入栈顶 DNEG 119 将栈顶double值求反后压入栈顶 ISHL 120 将栈顶int值左移指定位数后压入栈顶 LSHL 121 将栈顶long值左移指定位数后压入栈顶 ISHR 122 将栈顶int值右移指定位数后压入栈顶 LSHR 123 将栈顶long值右移指定位数后压入栈顶 IUSHR 124 将栈顶int值右移指定位数后压入栈顶,无符号 LUSHR 125 将栈顶long值右移指定位数后压入栈顶,无符号 IAND 126 将栈顶两个int值按位与运算后压入栈顶 LAND 127 将栈顶两个long值按位与运算后压入栈顶 IOR 128 将栈顶两个int值按位或运算后压入栈顶 LOR 129 将栈顶两个long值按位或运算后压入栈顶 IXOR 130 将栈顶两个int值按位异或运算后压入栈顶 LXOR 131 将栈顶两个long值按位异或运算后压入栈顶 IINC 132 将栈顶int值自增指定值后压入栈顶

转换操作符

操作符名称值含义 I2L 133 将栈顶int值强转为long后压入栈顶 I2F 134 将栈顶int值强转为float后压入栈顶 I2D 135 将栈顶int值强转为double后压入栈顶 L2I 136 将栈顶long值强转为int后压入栈顶 L2F 137 将栈顶long值强转为float后压入栈顶 L2D 138 将栈顶long值强转为double后压入栈顶 F2I 139 将栈顶float值强转为int后压入栈顶 F2L 140 将栈顶float值强转为long后压入栈顶 F2D 141 将栈顶float值强转为double后压入栈顶 D2I 142 将栈顶double值强转为int后压入栈顶 D2L 143 将栈顶double值强转为long后压入栈顶 D2F 144 将栈顶double值强转为float后压入栈顶 I2B 145 将栈顶int值强转为byte后压入栈顶 I2C 146 将栈顶int值强转为char后压入栈顶 I2S 147 将栈顶int值强转为short后压入栈顶

比较操作符

操作符名称值含义 LCMP 148 比较栈顶两 long 型数值大小,并将结果(1,0,-1)压入栈顶 FCMPL 149 比较栈顶两 float 型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为 “NaN” 时,将 – 1 压入栈顶 FCMPG 150 比较栈顶两 float 型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为 “NaN” 时,将 1 压入栈顶 DCMPL 151 比较栈顶两 double 型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为 “NaN” 时,将 – 1 压入栈顶 DCMPG 152 比较栈顶两 double 型数值大小,并将结果(1,0,-1)压入栈顶;当其中一个数值为 “NaN” 时,将 1 压入栈顶 IFEQ 153 当栈顶 int 型数值等于 0 时,跳转 IFNE 154 当栈顶 int 型数值不等于 0 时,跳转 IFLT 155 当栈顶 int 型数值小于 0 时,跳转 IFGE 156 当栈顶 int 型数值大于等于 0 时,跳转 IFGT 157 当栈顶 int 型数值大于 0 时,跳转 IFLE 158 当栈顶 int 型数值小于等于 0 时,跳转 IF_ICMPEQ 159 比较栈顶两个 int 型数值,等于 0 时,跳转 IF_ICMPNE 160 比较栈顶两个 int 型数值,不等于 0 时,跳转 IF_ICMPLT 161 比较栈顶两个 int 型数值,小于 0 时,跳转 IF_ICMPGE 162 比较栈顶两个 int 型数值,大于等于 0 时,跳转 IF_ICMPGT 163 比较栈顶两个 int 型数值,大于 0 时,跳转 IF_ICMPLE 164 比较栈顶两个 int 型数值,小于等于 0 时,跳转 IF_ACMPEQ 165 比较栈顶两个 object 型数值,相等时跳转 IF_ACMPNE 166 比较栈顶两个 object 型数值,不相等时跳转 IFNULL 198 为 null 时跳转 IFNONNULL 199 非 null 时跳转

这里需要指出的是最好记住 EQNELTGEGTLE这几个单词的含义。

跳转控制操作符

操作符名称值含义 GOTO 167 无条件分支跳转 JSR 168 跳转至指定16位 offset(bit) 位置,并将 jsr 下一条指令地址压入栈顶 RET 169 返回至局部变量指定的 index 的指令位置 TABLESWITCH 170 用于 switch 条件跳转,case 值连续 LOOKUPSWITCH 171 用于 switch 条件跳转,case 值不连续 IRETURN 172 结束方法,并返回一个 int 类型数据 LRETURN 173 结束方法,并返回一个 long 类型数据 FRETURN 174 结束方法,并返回一个 float 类型数据 DRETURN 175 结束方法,并返回一个 double 类型数据 ARETURN 176 结束方法,并返回一个 object 类型数据 RETURN 177 结束方法,并返回一个 void 类型数据

类成员操作符

操作符名称值含义 GETSTATIC 178 类指定静态成员字段压入栈顶 PUTSTATIC 179 存储栈顶数据至类指定静态成员字段 GETFIELD 180 类成员字段压入栈顶 PUTFIELD 181 存储栈顶数据至类指定成员字段

方法操作符

操作符名称值含义 INVOKEVIRTUAL 182 调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派) INVOKESPECIAL 183 调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法 INVOKESTATIC 184 调用静态方法 INVOKEINTERFACE 185 调用接口方法调,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用 INVOKEDYNAMIC 186 调用动态链接方法(该指令是指令是 Java SE 7 中新加入的)。用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法,前面 4 条调用指令的分派逻辑都固化在 Java 虚拟机内部,而 invokedynamic 指令的分派逻辑是由用户所设定的引导方法决定的

实例化操作符

操作符名称值含义 NEW 187 创建一个对象,并将其引用值压入栈顶 NEWARRAY 188 创建一个指定原始类型(如 int、float、char……)的数组,并将其引用值压入栈顶 ANEWARRAY 189 创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶

数组相关操作符

操作符名称值含义 ARRAYLENGTH 190 获得数组的长度值并压入栈顶 MULTIANEWARRAY 197 创建指定类型和指定维度的多维数组(执行该指令时,操作栈中必须包含各维度的长度值),并将其引用值压入栈顶

异常相关操作符

操作符名称值含义 ATHROW 191 将栈顶的异常直接抛出。Java 程序中显式抛出异常的操作(throw 语句)都由 athrow 指令来实现,并且,在 Java 虚拟机中,处理异常(catch 语句)不是由字节码指令来实现的,而是采用异常表来完成的

类型相关操作符

操作符名称值含义 CHECKCAST 192 检验类型转换,检验未通过将抛出 ClassCastException INSTANCEOF 193 检验对象是否是指定的类的实例,如果是将 1 压入栈顶,否则将 0 压入栈顶

同步锁操作符

操作符名称值含义 MONITORENTER 194 获取对象的 monitor,用于同步块或同步方法 MONITOREXIT 195 释放对象的 monitor,用于同步块或同步方法

这些操作符位于ASM的 org.objectweb.asm.Opcodes中,将操作符按操类型进行分组后非常的清晰,抛去同等意义的操作符其实也就并没有太多了,而每种操作符需要栈顶值的要求这是一个关键,后续会陆陆续续看到相关操作符的实际使用

ASM的启程

操作字节码的框架有很多,但是不论如何我想ASM无疑是绕不开的话题,ASM也许对入口及学习陡度上都不算友好,但是个人认为ASM作为字节码操作框架中于class文件结合的最完美的框架,ASM虽然学习需要比平常用到的框架看着学起来难,但核心是对字节码的掌握,ASM常用的核心类其实并没有几个,在ASM当中我们需要牢牢掌握的类其实就 ClassReaderClassWriterClassVisitorMehotdVisitorFieldVisitorAnnotationVisitor,依次对应输入、输出、类访问器、方法访问器、字段访问器、注解访问器,这几个访问器富含到日常书写的JAVA代码,而其他的类都是在这几个核心类拓展为处理字节码提供便利的,相信看到这里应该会放下对ASM学习的压力了吧

上一篇文章中知道每个class文件可以抽象为一个具体的 ClassFile构,而ASM根据这个结构抽象出一个用于处理 ClassFile的类 ClassVisitor,类中每个方法对应着同名类文件结构部分,而类中方法调用也是按照一定的顺序进行调用,规则如:从左至右,不同符号代表不同访问次数,无符号为必访问一次。以 ClassVisitor为例,首先访问 visit方法,随后 visitSource可能被访问,再然后 visitOuterClass可能被访问,其次 visitAnnotationvisitAttribute根据情况至少一次访问,再其次为 visitInnerClassvisitFieldvisitMethod根据情况至少一次访问,最后 visitEnd被调用,代表着一个Class访问结束。

符号意义 ?

visit
visitSource?

visitOuterClass?

( visitAnnotation | visitAttribute )*
( visitInnerClass | visitField | visitMethod )*
visitEnd

ClassVisitor

作为ASM框架中最为核心的类 ClassVisitor,通过它我们可以访问到类中所有信息,而作为类方法返回值的 AnnotationVisitorFieldVisitorMethodVisitor负责针对注解、字段、方法更为细致的核心类,这三个Visitor的处理和 ClassVisitor的设计逻辑其实是一样的,所以当理解一个核心类的处理流程后可以融会贯通到其他的三个核心类。之前提到ASM的设计模式使用了访问者模式,而一个一个的 Visitor相当于一个一个的管道将 ClassFile的内容流向对应的一个一个的 Visitor中,从而将不同功能的 Visitor拆分并链接再一起使用,而ASM通过构造方法传参的形式将上一个 Visitor关联。

public abstract class ClassVisitor {
    public ClassVisitor(final int api, final ClassVisitor classVisitor)

    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces);

    public void visitSource(String source, String debug);

    public void visitOuterClass(String owner, String name, String descriptor);

    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible);

    public void visitAttribute(Attribute attribute);

    public void visitInnerClass(String name, String outerName, String innerName, int access);

    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value);

    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions);

    public void visitEnd();

}
  • void visit(int version, int access, String name, String signature, String superName, String[] interfaces)

该方法主要提供对java版本,类权限访问符、全限定名、泛型、父类、接口相关信息 – version 由于目前minor_version为0,这个值其实就是major_version

- access
&#x7C7B;&#x8BBF;&#x95EE;&#x6807;&#x8BB0;&#x7B26;

- name
&#x7C7B;&#x5168;&#x9650;&#x5B9A;&#x540D;

- signature
&#x5982;&#x679C;&#x7C7B;&#x5B58;&#x5728;&#x6CDB;&#x578B;&#x5219;&#x6709;&#x503C;&#xFF0C;&#x5426;&#x5219;&#x4E3A;null&#xFF0C;&#x683C;&#x5F0F;&#x5982;<泛型名:泛型类型全限定名> + &#x7236;&#x7C7B;&#x5168;&#x9650;&#x5B9A;&#x540D; + &#x63A5;&#x53E3;&#x5168;&#x9650;&#x5B9A;&#x540D;

- superName
&#x7236;&#x7C7B;&#x5168;&#x9650;&#x5B9A;&#x540D;

- interfaces
&#x5B9E;&#x73B0;&#x63A5;&#x53E3;&#x5168;&#x9650;&#x5B9A;&#x540D;&#x6570;&#x7EC4;
</泛型名:泛型类型全限定名>
  • void visitSource(String source, String debug)
  • source 源码文件类名
  • debug 编译于源码附加调试信息对应关系
  • void visitOuterClass(String owner, String name, String descriptor)
  • owner 局部类位于类的全限定名
  • name 局部类位于类的方法名
  • descriptor 局部类位于类的方法签名
public class OuterClass {
        void someMethod(String s) {
            class InnerClass {}
        }
}

对于读取 InnerClass对应的值依次为:OuterClass、someMethod、(Ljava/lang/String;)V

  • AnnotationVisitor visitAnnotation(String descriptor, boolean visible)

类注解信息,源码级别注解不会调用该方法,该方法需要返回一个 AnnotationVisitor,若我们需要获取注解的详细信息那么我们需要实现一个 AnnotationVisitor的子类进行单独处理 – descriptor 类注解全限定名

- visible
&#x662F;&#x5426;&#x53EF;&#x7528;&#x53CD;&#x5C04;&#x83B7;&#x53D6;&#xFF0C;&#x5373;&#x6CE8;&#x89E3;&#x7684;Retention&#x662F;RetentionPolicy.CLASS&#x4E3A;false&#xFF0C;&#x4E3A;RetentionPolicy.RUNTIME&#x4E3A;true
  • void visitAttribute(Attribute attribute)

访问类的非标准属性,这个方法一般并不会接触到

  • void visitInnerClass(String name, String outerName, String innerName, int access)

访问内部类信息:全限定名、外部类全限定名、类名、访问标记符

  • FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value)

类或接口成员字段信息,由ClassFile中规则知道仅包括当前类或接口声明的成员字段,该方法需要返回 FieldVisitor – access 字段访问标记符

- name
&#x5B57;&#x6BB5;&#x540D;&#x5B57;

- descriptor
&#x5B57;&#x6BB5;&#x7C7B;&#x578B;&#x63CF;&#x8FF0;

- signature
&#x5B57;&#x6BB5;&#x6CDB;&#x578B;&#x4FE1;&#x606F;

- value
&#x5B57;&#x6BB5;&#x521D;&#x59CB;&#x503C;,&#x4F46;&#x4EC5;&#x5728;&#x9759;&#x6001;&#x5B57;&#x6BB5;&#x6709;&#x6548;
  • MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions)

类方法信息,由ClassFile中规则知道仅包括当前类或接口声明的方法,该方法需要返回 MethodVisitor

- access
&#x65B9;&#x6CD5;&#x8BBF;&#x95EE;&#x6807;&#x8BB0;&#x7B26;

- name
&#x65B9;&#x6CD5;&#x540D;&#x5B57;

- descriptor
&#x65B9;&#x6CD5;&#x7B7E;&#x540D;&#x63CF;&#x8FF0;

- signature
&#x65B9;&#x6CD5;&#x76F8;&#x5173;&#x6CDB;&#x578B;&#x4FE1;&#x606F;

- exceptions
&#x65B9;&#x6CD5;&#x5F02;&#x5E38;&#x76F8;&#x5173;&#x4FE1;&#x606F;
  • void visitEnd()

类访问完成接口

ClassReader

ClassReader的主要职责是读取class文件,将class文件内容按照 ClassFile结构转换后传递给 ClassVisitor,除了在生成类这个场景无需使用外其他的场景都需要通过 ClassReader作为数据的输入源

public class ClassReader {
    public ClassReader(final byte[] classFile);

    public ClassReader(final byte[] classFileBuffer,final int classFileOffset,final int classFileLength);

    public ClassReader(final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion);

    public ClassReader(final InputStream inputStream);

    public ClassReader(final String className);

    public int getAccess();

    public String getClassName();

    public String getSuperName();

    public String[] getInterfaces();

    public void accept(final ClassVisitor classVisitor, final int parsingOptions);

}
  • ClassReader(final byte[] classFile)
  • ClassReader(final byte[] classFileBuffer,final int classFileOffset,final int classFileLength)
  • ClassReader(final byte[] classFileBuffer, final int classFileOffset, final boolean checkClassVersion)
  • ClassReader(final InputStream inputStream)
  • ClassReader(final String className)

构造函数通过不同途径读取class文件

  • int getAccess()

获取类问标记符

  • String getClassName()

获取类名

  • String getSuperName()

获取父类名

  • String[] getInterfaces()

获取实现接口

  • void accept(final ClassVisitor classVisitor, final int parsingOptions)

将数据传递给 ClassVisitor – parsingOptions 解析类的选项,值为 ClassReader.SKIP_CODEClassReader.SKIP_DEBUGClassReader.SKIP_FRAMESClassReader.EXPAND_FRAMES – SKIP_CODE 设置后跳过 Code attributes

    - SKIP_DEBUG
    &#x8BBE;&#x7F6E;&#x540E;&#x8DF3;&#x8FC7;SourceFile attributes&#x3001;SourceDebugExtension attributes&#x3001;LocalVariableTable attributes&#x3001;LocalVariableTypeTable attributes&#x3001;LineNumberTable attributes&#x7684;&#x89E3;&#x6790;&#x548C;&#x5904;&#x7406;&#xFF0C;&#x5E76;&#x4E14;ClassVisitor#visitSource,MethodVisitor#visitLocalVariable,MethodVisitor#visitLineNumber&#x5C06;&#x4E0D;&#x518D;&#x88AB;&#x8C03;&#x7528;

    - SKIP_FRAMES
    &#x8BBE;&#x7F6E;&#x540E;&#x8DF3;&#x8FC7;StackMap attributes,StackMapTable attributes&#x89E3;&#x6790;&#x548C;&#x5904;&#x7406;&#xFF0C;&#x5E76;&#x4E14;&#x65B9;&#x6CD5;MethodVisitor#visitFrame&#x4E0D;&#x5728;&#x88AB;&#x8C03;&#x7528;&#x3002;&#x5BF9;&#x4E8E;ClassWriter&#x53EA;&#x6709;&#x8BBE;&#x7F6E;&#x4E86;COMPUTE_FRAMES&#x624D;&#x751F;&#x6548;&#x3002;

    - EXPAND_FRAMES
    &#x8BBE;&#x7F6E;&#x540E;&#x5806;&#x6808;&#x6620;&#x5C04;&#x603B;&#x662F;&#x4EE5;&#x62D3;&#x5C55;&#x5F62;&#x5F0F;&#x8FDB;&#x884C;&#x8BBF;&#x95EE;&#xFF0C;ClassReader&#x548C;ClassWriter&#x9700;&#x8981;&#x989D;&#x5916;&#x7684;&#x538B;&#x7F29;&#x3001;&#x89E3;&#x538B;&#x6B65;&#x9AA4;&#x5BFC;&#x81F4;&#x6027;&#x80FD;&#x964D;&#x4F4E;

掌握这两个核心类后我们可以书写ASM程序的简单的例子了,以打印 HelloWorld类为例学习如何搭配使用打印class相关的字段及方法

package com.river.asm;

public class HelloWorld {
    private String word = "hello world";

    public void say() {
        System.out.println(word);
    }

    public static void main(String[] args) {
        new HelloWorld().say();
    }
}


public class Asm {
    public static void main(String[] args) throws IOException {
        ClassReader classReader = new ClassReader("com/river/asm/HelloWorld");

        classReader.accept(new ClassVisitor(ASM6, null) {

            @Override
            public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
                super.visit(version, access, name, signature, superName, interfaces);

                System.out.println(String.format("class %s {", name));
            }

            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                System.out.println(String.format("\t%s %s", name, descriptor));
                return super.visitMethod(access, name, descriptor, signature, exceptions);
            }

            @Override
            public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
                System.out.println(String.format("\t%s %s", name, descriptor));
                return super.visitField(access, name, descriptor, signature, value);
            }

            @Override
            public void visitEnd() {
                super.visitEnd();
                System.out.println("}");
            }

        }, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
    }
}

代码非常简单,通过 ClassReader读取 HelloWorld并调用其accept方法将读取数据传递给 ClassVisitor,这里选择使用类全限定名作为输入源,代码中匿名 ClassVisitor负责输出类名、成员变量、方法信息。输出信息如下:

class com/river/asm/HelloWorld {
    word Ljava/lang/String;
    <init> ()V
    say ()V
    main ([Ljava/lang/String;)V
}
</init>

这两个类搭配还真的玩不出什么花样来,所以继续踏上征程吧

ClassWriter

ClassWriterClassVisitor的一个子类,主要职责是负责将 ClassVisitor处理好后经过转换生成class文件的byte数组,有了byte数组我们可以选择写入文件或者直接通过类加载器加载后进行使用

public class ClassWriter extends ClassVisitor {

    public ClassWriter(final int flags)

    public ClassWriter(final ClassReader classReader, final int flags)

    public byte[] toByteArray() throws ClassTooLargeException, MethodTooLargeException

}
  • ClassWriter(final int flags)
  • flags 设置后影响对class的默认行为,可选值为0, ClassWriter.COMPUTE_MAXS, ClassWriter.COMPUTE_FRAMES – 0 需要自行计算最大方法变量池长度和最大栈深度
  - COMPUTE_MAXS
  &#x8BBE;&#x7F6E;&#x540E;ASM&#x81EA;&#x52A8;&#x8BA1;&#x7B97;&#x65B9;&#x6CD5;&#x7684;&#x6700;&#x5927;&#x65B9;&#x6CD5;&#x53D8;&#x91CF;&#x6C60;&#x957F;&#x5EA6;&#x548C;&#x6700;&#x5927;&#x6808;&#x6DF1;&#x5EA6;&#xFF0C;&#x9700;&#x8981;&#x4E3B;&#x4E49;&#x7684;&#x662F;&#x8BE5;flag&#x9700;&#x8981;&#x6709;&#x6548;&#x7684;&#x6808;&#x6620;&#x5C04;&#x5E27;&#xFF0C;&#x5426;&#x5219;&#x8BF7;&#x4F7F;&#x7528;COMPUTE_FRAMES&#xFF0C;&#x4F7F;&#x7528;&#x540E;MethodVisitor#visitMaxs&#x65B9;&#x6CD5;&#x65E0;&#x6548;&#xFF0C;&#x65E0;&#x8BBA;&#x7A0B;&#x5E8F;&#x4E2D;&#x5982;&#x4F55;&#x4F20;&#x503C;&#xFF0C;ASM&#x90FD;&#x8D70;&#x81EA;&#x5DF1;&#x7B97;&#x6CD5;&#x8FDB;&#x884C;&#x8BA1;&#x7B97;

  - COMPUTE_FRAMES
  &#x8BBE;&#x7F6E;&#x540E;ASM&#x81EA;&#x52A8;&#x8BA1;&#x7B97;&#x65B9;&#x6CD5;&#x7684;&#x6808;&#x6620;&#x5C04;&#x5E27;

栈映射帧(stack map frames)对应 ClassFile中的 StackMapTable,这个的出现是为了优化对类的加载验证。JAVA要求所有加载的类都必须进行安全验证以维护JVM的安全性,这个是在字节码级别完成的工作。之前的问题是字节码本身并不含有任何类型安全信息,类型通过数据流隐式确定,而帧栈的局部变量表存在复用变量槽的情况,使得以线性处理在遇到跳转指令后也许变量槽的类型已经被修改了,若没有任何辅助信息记录这些,需要通过多次处理字节码,无疑带来的就是对性能的影响,所以为了解决跳转带来的挑战 StackMapTable挺身而出,Oracle要求JAVA7开始的所有类必须携带相关的数据,使得验证器可以一次验证字节码。处于存储空间大小考虑,若简单的在任意点都存储值类型无疑是非常大的开销,所以为了使存储空间更合理Oracle决定仅在使用跳转指令的目标位置记录相关信息。在使用ASM时请避免自己书写相关frames代码,这个东西过于复杂,不建议自己编写,还是交给专业的ASM来处理这些问题,所以个人推荐 ClassWriter.COMPUTE_FRAMES一定要使用

言归正传,下面是通过ASM的 ClassWriter生成一个 HelloWorld类,并通过自定义 ClassLoader通过加载字节将类加载后通过反射实例化后调用 say方法

import org.apache.commons.io.FileUtils;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Asm {
    public static void main(String[] args) throws IOException {
        MethodVisitor mv;

        ClassWriter cw = new ClassWriter(null, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        //&#x751F;&#x6210;&#x7C7B;
        cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "com/river/asm/HelloWorld", null, "java/lang/Object", null);

        //word&#x5B57;&#x6BB5;
        FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE, "word", "Ljava/lang/String;", null, null);
        fv.visitEnd();

        //&#x6784;&#x9020;&#x65B9;&#x6CD5;
        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.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitLdcInsn("hello world");
        mv.visitFieldInsn(Opcodes.PUTFIELD, "com/river/asm/HelloWorld", "word", "Ljava/lang/String;");
        mv.visitMaxs(0, 0);
        mv.visitInsn(Opcodes.RETURN);
        mv.visitEnd();

        //say&#x65B9;&#x6CD5;
        mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "say", "()V", null, null);
        mv.visitCode();
        mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitFieldInsn(Opcodes.GETFIELD, "com/river/asm/HelloWorld", "word", "Ljava/lang/String;");
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        mv.visitMaxs(0, 0);
        mv.visitInsn(Opcodes.RETURN);
        mv.visitEnd();

        //&#x7C7B;&#x7ED3;&#x675F;
        cw.visitEnd();

        FileUtils.writeByteArrayToFile(new File("target\\classes\\com\\river\\asm\\HelloWorld.class"), cw.toByteArray());

        ASMGenClassLoader asmGenClassLoader = new ASMGenClassLoader(cw.toByteArray());
        try {
            Class<?> clazz = asmGenClassLoader.loadClass("com.river.asm.HelloWorld");
            Object helloWorld = clazz.newInstance();
            Method say = clazz.getDeclaredMethod("say");
            say.invoke(helloWorld);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    static class ASMGenClassLoader extends ClassLoader {
        private byte[] bytes;

        public ASMGenClassLoader(byte[] bytes) {
            this.bytes = bytes;
        }

        @Override
        protected Class<?> findClass(String name) {
            return defineClass(name, bytes, 0, bytes.length);
        }
    }
}
</init></init>

FieldVisitor

public abstract class FieldVisitor {
    public FieldVisitor(final int api);

    public FieldVisitor(final int api, final FieldVisitor fieldVisitor);

    public AnnotationVisitor visitAnnotation(final String descriptor, final boolean visible);

    public void visitEnd();
}

FieldVisitor的内容非常的少,因为对代码的处理往往自动声明后都是在方法中使用,当我们重写这个的时候往往是需要对字段的注解进行相关的处理,由于过于简单和重点一般位于 AnnotationVisitor就不用 FieldVisitor举例了,后面介绍 AnnotationVisitor时搭配 FieldVisitor再行举例,这里针对字段日常对Class的处理一般是新增字段:

import org.apache.commons.io.FileUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

import java.io.File;
import java.io.IOException;

public class Asm {
    public static void main(String[] args) throws IOException {
        ClassReader cr = new ClassReader("com/river/asm/HelloWorld");
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        cr.accept(new ClassVisitor(Opcodes.ASM6, cw) {
            @Override
            public void visitEnd() {
                visitField(Opcodes.ACC_PRIVATE, "word2", "Ljava/lang/String;", null, null);
                super.visitEnd();
            }
        }, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);

        FileUtils.writeByteArrayToFile(new File("target\\classes\\com\\river\\asm\\HelloWorldProField.class"), cw.toByteArray());
    }
}

新增字段时需要注意生成的字段名不能重复,所以在 ClassVisitor中挑选合适的方法至关重要,最好不要在多次调用的方法中新增字段,建议在 visitEnd方法,为了保证字段名唯一性可以将字段名命名按照一定复杂规则来,或者通过方法 visitField统计类中字段名后进行避免重复

AnnotationVisitor

AnnotationVisitor的职责也非常简单用于访问注解相关内容,需要注意的是注解策略如果为源码级别的将访问不到的

public abstract class AnnotationVisitor {
    public AnnotationVisitor(int api);

    public AnnotationVisitor(int api, AnnotationVisitor annotationVisitor);

    public void visit(String name, Object value);

    public void visitEnd();
}
  • void visit(String name, Object value);

该方法用于访问注解的键值对 – name 注解键值名称

- value
&#x6CE8;&#x89E3;value&#x503C;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Resource {
    String value();
}

public class HelloWorld {
    @Resource("hello river")
    private String word = "hello world";

    public void say() {
        System.out.println(word);
    }
}

public class Asm {
    public static void main(String[] args) throws IOException {
        ClassReader cr = new ClassReader("com/river/asm/HelloWorld");
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        cr.accept(new ClassVisitor(Opcodes.ASM6, cw) {
            @Override
            public FieldVisitor visitField(int access, final String name, String descriptor, String signature, Object value) {
                FieldVisitor fv = super.visitField(access, name, descriptor, signature, value);
                if (fv != null && name.equals("word")) {
                    fv = new FieldVisitor(Opcodes.ASM6, fv) {
                        @Override
                        public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
                            System.out.println(String.format("filed(%s) visit anno: %s",name, descriptor));

                            AnnotationVisitor av = super.visitAnnotation(descriptor, visible);

                            if (av != null) {
                                av = new AnnotationVisitor(Opcodes.ASM6, av) {
                                    @Override
                                    public void visit(String name, Object value) {
                                        super.visit(name, value);
                                        System.out.println(String.format("name: %s, value: %s", name, value));
                                    }
                                };
                            }

                            return av;
                        }
                    };
                }
                return fv;
            }

            @Override
            public void visitEnd() {
                visitField(Opcodes.ACC_PRIVATE, "word2", "Ljava/lang/String;", null, null);
                super.visitEnd();
            }
        }, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
    }
}

最终输出

filed(word) visit anno: Lcom/river/asm/Resource;
name: value, value: hello river

MethodVisitor

MethodVisitor是ASM中最为重要的类,因为这个类是对帧栈中操作数栈操作的核心类,无论实现什么功能都离不开对操作数栈的操作,而作为最为核心的类当然也是ASM中最为复杂的类,虽然咋一看涉及的方法非常多,当然若熟练掌握字节码后学习 MethodVisitor也是非常轻松的

public abstract class MethodVisitor {
    public MethodVisitor(int api);

    public MethodVisitor(int api, MethodVisitor methodVisitor);

    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible);

    public void visitCode();

    public void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack);

    public void visitInsn(int opcode);

    public void visitIntInsn(int opcode, int operand);

    public void visitVarInsn(int opcode, int var);

    public void visitTypeInsn(int opcode, String type);

    public void visitFieldInsn(int opcode, String owner, String name, String descriptor);

    public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface);

    public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments);

    public void visitJumpInsn(int opcode, Label label);

    public void visitLabel(Label label);

    public void visitLdcInsn(Object value);

    public void visitIincInsn(int var, int increment);

    public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels);

    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels);

    public void visitMultiANewArrayInsn(String descriptor, int numDimensions);

    public void visitTryCatchBlock(Label start, Label end, Label handler, String type);

    public void visitMaxs(int maxStack, int maxLocals);

    public void visitEnd();
}
  • AnnotationVisitor visitAnnotation(String descriptor, boolean visible)

ClassVisitorFieldVisitor一样,用于访问注解信息

- descriptor
&#x6CE8;&#x89E3;&#x5168;&#x9650;&#x5B9A;&#x540D;

- visible
&#x662F;&#x5426;&#x53EF;&#x4EE5;&#x901A;&#x8FC7;&#x53CD;&#x5C04;&#x83B7;&#x53D6;&#x6CE8;&#x89E3;
  • void visitCode()

ClassVisitorFieldVisitor一样,调用该方法后开启对code的操作

  • void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack)

针对跳转时记录的栈映射帧信息,不建议自己编写代码时调用该方法,建议交由ASM进行计算填充

  • void visitInsn(int opcode)

执行零操作指令,意味着操作码后无需跟任何字节的操作数,例如调用 mv.visitInsn(ICONST_1)意味着将 1压入栈中 NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1, FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM, FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR, IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER, MONITOREXIT

  • void visitIntInsn(int opcode, int operand)

执行单个int类型操作数的指令 当操作码为 BIPUSH 时,操作数值应介于 Byte.MIN_VALUEByte.MAX_VALUE 之间。 当操作码为 SIPUSH 时,操作数值应介于 Short.MIN_VALUEShort.MAX_VALUE 之间。 当操作码为 NEWARRAY 时,操作数值应为 T_BOOLEAN, T_CHAR, T_FLOAT, T_DOUBLE, T_BYTE, T_SHORT, T_INT, T_LONG

  • void visitVarInsn(int opcode, int var)

访问局部变量指令,用于加载或存储 加载指令: ILOAD, LLOAD, FLOAD, DLOAD, ALOAD 存储指令: ISTORE, LSTORE, FSTORE, DSTORE, ASTORE, RET

  • void visitTypeInsn(int opcode, String type)

访问类型相关指令,比如将某个类型强转为另外一个类型 NEW, ANEWARRAY, CHECKCAST, INSTANCEOF

  • void visitFieldInsn(int opcode, String owner, String name, String descriptor)

用于访问类字段的指令,更新或者获取 GETSTATIC, PUTSTATIC, GETFIELD, PUTFIELD – owner 所属类的全限定名

- name
&#x6210;&#x5458;&#x5B57;&#x6BB5;&#x540D;&#x5B57;

- descriptor
&#x6210;&#x5458;&#x5B57;&#x6BB5;&#x7C7B;&#x578B;&#x63CF;&#x8FF0;
//&#x8BBF;&#x95EE;&#x9759;&#x6001;&#x5B57;&#x6BB5;
mv.visitFieldInsn(GETSTATIC, "com/river/asm/HelloWorld", "word", "Ljava/lang/String;") //&#x5C06;HelloWorld&#x7684;&#x9759;&#x6001;&#x5B57;&#x6BB5;word&#x538B;&#x5165;&#x6808;&#x9876;

//&#x8D4B;&#x503C;&#x9759;&#x6001;&#x5B57;&#x6BB5;
mv.visitLdcInsn("hello world") //&#x901A;&#x8FC7;LDC&#x6307;&#x4EE4;&#x5C06;&#x5E38;&#x91CF;hello world&#x538B;&#x5165;&#x6808;&#x9876;
mv.visitFieldInsn(PUTSTATIC, "com/river/asm/HelloWorld", "word", "Ljava/lang/String;") //&#x5C06;hello world&#x8D4B;&#x503C;&#x7ED9;HelloWorld&#x7684;&#x9759;&#x6001;&#x5B57;&#x6BB5;word

//&#x8BBF;&#x95EE;&#x6210;&#x5458;&#x5B57;&#x6BB5;
mv.visitVarInsn(ALOAD, 0) //&#x52A0;&#x8F7D;this&#x538B;&#x5165;&#x6808;&#x9876;
mv.visitFieldInsn(GETFIELD, "com/river/asm/HelloWorld", "word", "Ljava/lang/String;") //&#x5C06;HelloWorld&#x7684;&#x6210;&#x5458;&#x5B57;&#x6BB5;word&#x538B;&#x5165;&#x6808;&#x9876;

//&#x590D;&#x5236;&#x6210;&#x5458;&#x5B57;&#x6BB5;
mv.visitVarInsn(ALOAD, 0)
mv.visitLdcInsn("hello world") //&#x901A;&#x8FC7;LDC&#x6307;&#x4EE4;&#x5C06;&#x5E38;&#x91CF;hello world&#x538B;&#x5165;&#x6808;&#x9876;
mv.visitFieldInsn(PUTFIELD, "com/river/asm/HelloWorld", "word", "Ljava/lang/String;") //&#x5C06;hello world&#x8D4B;&#x503C;&#x7ED9;HelloWorld&#x7684;&#x5B57;&#x6BB5;word

  • void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface)

用于访问类方法指令 INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC, INVOKEINTERFACE

mv.visitVarInsn(ALOAD, 0) //&#x52A0;&#x8F7D;this&#x538B;&#x5165;&#x6808;&#x9876;
mv.visitMethodInsn(INVOKEVIRTUAL, "com/river/asm/HelloWorld", "say", "()V", false) //&#x8BBF;&#x95EE;&#x65B9;&#x6CD5;say

mv.visitMethodInsn(INVOKESPECIAL, "com/river/asm/HelloWorld", "<init>", "()V", false) //&#x8BBF;&#x95EE;HelloWorld&#x6784;&#x9020;&#x65B9;&#x6CD5;

mv.visitMethodInsn(INVOKESTATIC, "com/river/asm/HelloWorld", "say", "()V", false) //&#x8BBF;&#x95EE;&#x9759;&#x6001;&#x65B9;&#x6CD5;say
</init>
  • void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object… bootstrapMethodArguments)

用于访问动态句柄指令,非常强大,lambda函数式便是借助这个指令实现的

  • void visitJumpInsn(int opcode, Label label)

用于控制代码跳转的指令 FEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL, IFNONNULL

Label label = new Label()

mv.visitJumpInsn(IFNE, label) //&#x6808;&#x9876;int&#x503C;&#x82E5;&#x7B49;&#x4E8E;0&#x65F6;&#x76F4;&#x63A5;&#x6267;&#x884C;&#x5230;mv.visitLabel
mv.visit...

...

mv.visitLabel(label)
  • void visitLabel(Label label)

用于书写代码跳转标记的方法

  • void visitLdcInsn(Object value)

用于加载的 LDC指令

  • void visitIincInsn(int var, int increment)

用于访问 IINC指令

  • void visitTableSwitchInsn(int min, int max, Label dflt, Label… labels)

用于访问 TABLESWITCH指令

  • void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels)

用于访问 LOOKUPSWITCH指令

  • void visitMultiANewArrayInsn(String descriptor, int numDimensions)

用于访问 MULTIANEWARRAY指令

  • void visitTryCatchBlock(Label start, Label end, Label handler, String type)

用于异常捕获的方法

  • void visitMaxs(int maxStack, int maxLocals)

设置方法的操作数栈的最大深度和最大局部变量池长度

  • void visitEnd()

调用该方法后方法访问结束

这里以在say方法新增一条输出为例:

import org.apache.commons.io.FileUtils;
import org.objectweb.asm.*;

import java.io.File;
import java.io.IOException;

public class Asm {
    public static void main(String[] args) throws IOException {
        ClassReader cr = new ClassReader("com/river/asm/HelloWorld");
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        cr.accept(new ClassVisitor(Opcodes.ASM6, cw) {
            @Override
            public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
                if (mv != null && name.equals("say")) {
                    mv = new MethodVisitor(Opcodes.ASM6, mv) {
                        @Override
                        public void visitInsn(int opcode) {
                            if (opcode == Opcodes.RETURN) {
                                visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
                                visitLdcInsn("hello river");
                                visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
                            }
                            super.visitInsn(opcode);
                        }
                    };
                }
                return mv;
            }
        }, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);

        FileUtils.writeByteArrayToFile(new File("target\\classes\\com\\river\\asm\\HelloWorld.class"), cw.toByteArray());
    }
}

需要注意的时在方法 visitInsn中仅仅判断了 RETURN,在真实方法执行退出插入代码这样写时错误的,但是我们这边仅仅使用 HelloWorld就没有问题了。至此我们对ASM中的关键核心类已经率为有所了解,但是相信想书写什么功能时也无从下手,所以下一篇文章着重寻找一些案例来编写一些真实的场景进一步学习ASM。

Original: https://www.cnblogs.com/pxza/p/16019941.html
Author: 人生的激活码
Title: 字节码&ASM-基础

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/580802/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • ConcurrentHashMap中的get和put源码分析

    get分析 public V get(Object key) { // tab&#xFF1A;&#x6307;&#x5411;&#x6570;&am…

    Java 2023年6月16日
    069
  • 国内三大地图(腾讯、高德、百度)路线规划功能的整合

    写在前面 基于导航到门店的需求,于是有了这一个随笔,例如一些社区团购,自提点导航的功能,同样适用的。 话不多说,开整 一、先定一个目标点(这个通常是通过接口获取的) 建议通过腾讯地…

    Java 2023年6月16日
    0162
  • javaSE 温故而知新

    重温 javaSE 前言:有地基才能有高楼大厦 重温 javaSE 认识java Java基础 1、数据类型 1.1 基本数据类型: 1.2 引用数据类型 1.3 基本数据类型的包…

    Java 2023年6月16日
    070
  • SpringBoot-MVC自动配置原理

    MVC自动配置原理 5.1 官网阅读 在进行项目编写前,我们还需要知道一个东西,就是SpringBoot对我们的SpringMVC还做了哪些配置,包括如何扩展,如何定制。 只有把这…

    Java 2023年6月5日
    0123
  • 终止线程的运行

    终止线程的执行 强制终止用的是stop()方法,因为这种方法会丢失数据,所以一般不采用这种方法。 原理是直接杀死线程,这样的话线程中没有保存的数据就会丢失 /* 在java中强制终…

    Java 2023年6月9日
    0113
  • 使用java随机生成有个性的用户名,LOL地名+水浒传,合计2808个有意思的用户名

    * 随机生成用户名 * 取水浒传108好汉名字 * 取LOL地名26个,组合而成 * 一共可以生成2808个不同特色的用户名 如果你在上网的时候,用户名难取的话,这里有很多可选择的…

    Java 2023年6月9日
    072
  • springboot成功启动后访问controller层报404的问题

    可以看看是不是mybatis依赖版本的问题,可以修改版本后试一试, – 但这都是仅仅供参考 Original: https://www.cnblogs.com/hxbh…

    Java 2023年6月6日
    071
  • Java 确定两个区间范围是否有交集

    java;gutter:true; @Test public void test01() {</p> <pre><code> Double[] …

    Java 2023年5月29日
    080
  • Spring JDBC 数据访问

    Spring JDBC 数据访问Spring JDBC是Spring所提供的持久层技术,它的主要目标是降低使用JDBC API的门槛,以一种更直接,更简介,更简单的方式使用JDBC…

    Java 2023年6月13日
    076
  • Java项目代码是如何分层的

    1、背景 说起应用分层,大部分人都会认为这个不是很简单嘛 就controller,service, mapper三层。看起来简单,很多人其实并没有把他们职责划分开,在很多代码中,c…

    Java 2023年6月5日
    088
  • Spring源码之AOP的使用

    Spring往期精彩文章 Spring源码搭建 Spring源码阅读一 前言 我们都知道Java是一门面向对象(OOP)的语言,所谓万物皆对象。但是它也存在着一些个弊端:当你需要给…

    Java 2023年6月7日
    081
  • 用300行代码手写1个Spring框架,麻雀虽小五脏俱全

    本文节选自《Spring 5核心原理》 1 自定义配置 1.1 配置application.properties文件 为了解析方便,我们用application.propertie…

    Java 2023年6月7日
    066
  • Spring Cloud Alibaba 使用Nacos作为服务注册中心

    为什么需要注册中心? 在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就到这里找到服务的地址,进行调用;服务管理,核心是有个服务注册表,心跳机制动态维护 ; 服务注册 …

    Java 2023年6月5日
    090
  • 各种锁、volatile、synchronized、单例模式

    1、 锁 (1)各种锁 == 可重入锁(递归锁):广义上的可重入锁,并非单指ReentrantLock。指的是同一线程外层函数获得锁后,内层递归函数仍然有获得该锁的代码,但不锁影响…

    Java 2023年6月5日
    050
  • ASP.NET MVC 自定义处理JSON ActionResult类

    1、统一JSON格式处理方式,同时指定ContentType类型,解决低版本浏览器获取json时ContentType为application/json提示下载的问题. publi…

    Java 2023年6月5日
    066
  • 【设计模式】三种工厂模式

    【设计模式】工厂模式 相对来说,写的比较乱,但是看一下实例,其实理解很快 抽象工厂模式(这里主要介绍抽象工厂模式) 核心的工厂类不再负责所有对象的创建,而是将具体的创建工作交给子类…

    Java 2023年6月13日
    063
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球