写在前面:

ASM 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。

简单点说,通过 javac 将 .java 文件编译成 .class 文件,.class 文件中的内容虽然不同,但是它们都具有相同的格式,ASM 通过使用访问者(visitor)模式,按照 .class 文件特有的格式从头到尾扫描一遍 .class 文件中的内容,在扫描的过程中,就可以对 .class 文件做一些操作了

一,java字节码

提到 Java 字节码,可能很多人都不是很熟悉,大概都知道使用 javac 可以将 .java 文件编译成 .class 文件,.class 文件中存放的就是该 .java 文件对应的字节码内容,比如如下一段 DemoClass.java 代码很简单:

package com.equaker.demo.asm;public class DemoClass {private int m;private static final String userName = "@EQuaker";private Integer age = 25;public int inc(){return ++m;}public static final String getUserName(){return userName;}public Integer getAge(){return age;}private String hi(String name){return "hi" + name;}public static final void hello(){System.out.println("hello");System.out.println("hello2");}public String dd(){return "dd";}}

通过 javac 编译生成对应的 Demo.class 文件,使用纯文本文件打开 Demo.class,其中的内容是以 8 位字节为基础单位的二进制流,表面来看就是由十六进制符号组成的,这一段十六进制符号组成的长串是遵守 Java 虚拟机规范的。字符这里就不贴了。我们使用 javap -verbose DemoClass.class来看些我们勉强能看懂的吧。

F:\demo>javap -verbose DemoClass.class
Classfile /F:/demo/DemoClass.classLast modified 2021-1-14; size 1155 bytesMD5 checksum 46133fe2e9d345b28f020039cdfb039aCompiled from "DemoClass.java"
public class com.equaker.demo.asm.DemoClassminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref          #17.#41        // java/lang/Object."<init>":()V#2 = Methodref          #42.#43        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;#3 = Fieldref           #5.#44         // com/equaker/demo/asm/DemoClass.age:Ljava/lang/Integer;#4 = Fieldref           #5.#45         // com/equaker/demo/asm/DemoClass.m:I#5 = Class              #46            // com/equaker/demo/asm/DemoClass#6 = String             #47            // @EQuaker#7 = Class              #48            // java/lang/StringBuilder#8 = Methodref          #7.#41         // java/lang/StringBuilder."<init>":()V#9 = String             #35            // hi#10 = Methodref          #7.#49         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#11 = Methodref          #7.#50         // java/lang/StringBuilder.toString:()Ljava/lang/String;#12 = Fieldref           #51.#52        // java/lang/System.out:Ljava/io/PrintStream;#13 = String             #37            // hello#14 = Methodref          #53.#54        // java/io/PrintStream.println:(Ljava/lang/String;)V#15 = String             #55            // hello2#16 = String             #38            // dd#17 = Class              #56            // java/lang/Object#18 = Utf8               m#19 = Utf8               I#20 = Utf8               userName#21 = Utf8               Ljava/lang/String;#22 = Utf8               ConstantValue#23 = Utf8               age#24 = Utf8               Ljava/lang/Integer;#25 = Utf8               <init>#26 = Utf8               ()V#27 = Utf8               Code#28 = Utf8               LineNumberTable#29 = Utf8               inc#30 = Utf8               ()I#31 = Utf8               getUserName#32 = Utf8               ()Ljava/lang/String;#33 = Utf8               getAge#34 = Utf8               ()Ljava/lang/Integer;#35 = Utf8               hi#36 = Utf8               (Ljava/lang/String;)Ljava/lang/String;#37 = Utf8               hello#38 = Utf8               dd#39 = Utf8               SourceFile#40 = Utf8               DemoClass.java#41 = NameAndType        #25:#26        // "<init>":()V#42 = Class              #57            // java/lang/Integer#43 = NameAndType        #58:#59        // valueOf:(I)Ljava/lang/Integer;#44 = NameAndType        #23:#24        // age:Ljava/lang/Integer;#45 = NameAndType        #18:#19        // m:I#46 = Utf8               com/equaker/demo/asm/DemoClass#47 = Utf8               @EQuaker#48 = Utf8               java/lang/StringBuilder#49 = NameAndType        #60:#61        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#50 = NameAndType        #62:#32        // toString:()Ljava/lang/String;#51 = Class              #63            // java/lang/System#52 = NameAndType        #64:#65        // out:Ljava/io/PrintStream;#53 = Class              #66            // java/io/PrintStream#54 = NameAndType        #67:#68        // println:(Ljava/lang/String;)V#55 = Utf8               hello2#56 = Utf8               java/lang/Object#57 = Utf8               java/lang/Integer#58 = Utf8               valueOf#59 = Utf8               (I)Ljava/lang/Integer;#60 = Utf8               append#61 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;#62 = Utf8               toString#63 = Utf8               java/lang/System#64 = Utf8               out#65 = Utf8               Ljava/io/PrintStream;#66 = Utf8               java/io/PrintStream#67 = Utf8               println#68 = Utf8               (Ljava/lang/String;)V
{public com.equaker.demo.asm.DemoClass();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: aload_05: bipush        257: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;10: putfield      #3                  // Field age:Ljava/lang/Integer;13: returnLineNumberTable:line 3: 0line 8: 4public int inc();descriptor: ()Iflags: ACC_PUBLICCode:stack=3, locals=1, args_size=10: aload_01: dup2: getfield      #4                  // Field m:I5: iconst_16: iadd7: dup_x18: putfield      #4                  // Field m:I11: ireturnLineNumberTable:line 11: 0public static final java.lang.String getUserName();descriptor: ()Ljava/lang/String;flags: ACC_PUBLIC, ACC_STATIC, ACC_FINALCode:stack=1, locals=0, args_size=00: ldc           #6                  // String @EQuaker2: areturnLineNumberTable:line 15: 0public java.lang.Integer getAge();descriptor: ()Ljava/lang/Integer;flags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: getfield      #3                  // Field age:Ljava/lang/Integer;4: areturnLineNumberTable:line 18: 0public static final void hello();descriptor: ()Vflags: ACC_PUBLIC, ACC_STATIC, ACC_FINALCode:stack=2, locals=0, args_size=00: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #13                 // String hello5: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;11: ldc           #15                 // String hello213: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V16: returnLineNumberTable:line 27: 0line 28: 8line 29: 16public java.lang.String dd();descriptor: ()Ljava/lang/String;flags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: ldc           #16                 // String dd2: areturnLineNumberTable:line 32: 0
}
SourceFile: "DemoClass.java"

从上图中,我们可以看到,.class 文件中主要有常量池、字段表、方法表和属性表等内容。访问控制权限(ACC_PUBLIC,ACC_PRIVATR...)。方法形参与返回值,例如String getName(String name);他的descriptor应该为(Ljava/lang/String;)Ljava/lang/String;方法内的code部分有点像汇编,就是需要加载数据进栈,返回这样的操作。具体可查询别人的文章《认识 .class 文件的字节码结构》。多看看就明白啦。

二,访问者模式 & ASM

ASM 库是一款基于 Java 字节码层面的代码分析和修改工具,那 ASM 和访问者模式有什么关系呢?访问者模式主要用于修改和操作一些数据结构比较稳定的数据,通过前面的学习,我们知道 .class 文件的结构是固定的,主要有常量池、字段表、方法表、属性表等内容,通过使用访问者模式在扫描 .class 文件中各个表的内容时,就可以修改这些内容了

ASM 库是 Visitor 模式的典型应用。

2.1,ASM中的几个重要类

在 ASM 库中存在以下几个重要的类:

  • ClassReader:它将字节数组或者 class 文件读入到内存当中,并以树的数据结构表示,树中的一个节点代表着 class 文件中的某个区域。可以将 ClassReader 看作是 Visitor 模式中的访问者的实现类
  • ClassVisitor(抽象类):ClassReader 对象创建之后,调用 ClassReader#accept() 方法,传入一个 ClassVisitor 对象。在 ClassReader 中遍历树结构的不同节点时会调用 ClassVisitor 对象中不同的 visit() 方法,从而实现对字节码的修改。在 ClassVisitor 中的一些访问会产生子过程,比如 visitMethod 会产生 MethodVisitor 的调用,visitField 会产生对 FieldVisitor 的调用,用户也可以对这些 Visitor 进行自己的实现,从而达到对这些子节点的字节码的访问和修改。
    在 ASM 的访问者模式中,用户还可以提供多种不同操作的 ClassVisitor 的实现,并以责任链的模式提供给 ClassReader 来使用,而 ClassReader 只需要 accept 责任链中的头节点处的 ClassVisitor。
  • ClassWriter:ClassWriter 是 ClassVisitor 的实现类,它是生成字节码的工具类,它一般是责任链中的最后一个节点,其之前的每一个 ClassVisitor 都是致力于对原始字节码做修改,而 ClassWriter 的操作则是老实得把每一个节点修改后的字节码输出为字节数组。

classWrite下面的几个方法:

  • void visitCode():表示 ASM 开始扫描这个方法
  • void onMethodEnter():进入这个方法
  • void onMethodExit():即将从这个方法出去
  • void onVisitEnd():表示方法扫码完毕

2.2,ASM的工作过程

ASM 大致的工作流程是:

  1. ClassReader 读取字节码到内存中,生成用于表示该字节码的内部表示的树,ClassReader 对应于访问者模式中的元素
  2. 组装 ClassVisitor 责任链,这一系列 ClassVisitor 完成了对字节码一系列不同的字节码修改工作,对应于访问者模式中的访问者 Visitor
  3. 然后调用 ClassReader#accept() 方法,传入 ClassVisitor 对象,此 ClassVisitor 是责任链的头结点,经过责任链中每一个 ClassVisitor 的对已加载进内存的字节码的树结构上的每个节点的访问和修改
  4. 最后,在责任链的末端,调用 ClassWriter 这个 visitor 进行修改后的字节码的输出工作

三,实战

讲太多心累,看的迷迷糊糊,那就上代码吧。

3.1,初一级

我们先来个简单点的吧,查看某个类的所有属性与方法。你可能第一个想到的是java.lang.reflect下面的反射工具。但这里我们用的是ASM哦。

首先我们肯定需要一个ClassReader 去读取某个class文件。然后再通过我们的观察者ClassVistor 去 。我们这里还是使用DemoClass.class 为例。

Asm_Test.java:

package com.equaker.demo.asm;import org.objectweb.asm.*;import java.io.FileInputStream;
import java.io.IOException;public class Asm_Test {public static void main(String[] args) throws IOException {// 读取class文件ClassReader classReader = new ClassReader(new FileInputStream("F:\\demo\\DemoClass.class"));ClassVisitor classVisitor = new MyClassVisitor(Opcodes.ASM4);classReader.accept(classVisitor, ClassReader.SKIP_DEBUG);System.out.println(classReader.getAccess());}}class MyClassVisitor extends ClassVisitor{public MyClassVisitor(int i) {super(i);}// 重写了visitMethod方法。@Overridepublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {MethodVisitor methodVisitor = super.visitMethod(access, name, desc, signature, exceptions);System.out.println("visitMethod- methodname: " + name + "; desc: " + desc);return methodVisitor;}
// 重写了visitField方法。@Overridepublic FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {FieldVisitor fieldVisitor = super.visitField(access, name, descriptor, signature, value);System.out.println("visitField- fieldname: " + name + "; descriptor: " + descriptor);return fieldVisitor;}
}

运行结果如下:

visitField- fieldname: m; descriptor: I
visitField- fieldname: userName; descriptor: Ljava/lang/String;
visitField- fieldname: age; descriptor: Ljava/lang/Integer;
visitMethod- methodname: <init>; desc: ()V
visitMethod- methodname: inc; desc: ()I
visitMethod- methodname: getUserName; desc: ()Ljava/lang/String;
visitMethod- methodname: getAge; desc: ()Ljava/lang/Integer;
visitMethod- methodname: hi; desc: (Ljava/lang/String;)Ljava/lang/String;
visitMethod- methodname: hello; desc: ()V
visitMethod- methodname: dd; desc: ()Ljava/lang/String;
33

3.2,初二级

既然我们使用classReader和classVisitor的观察者模式可以读取方法,变量等。而且当我们重写visitField和visitMethod时,发现他是需要返回的。这也就意味着我们可以改变这个类。

现在我们一次干两件事。修改m的控制权限为public,删除inc()方法。这里我们需要用到的就是ClassAdapter。这个类实现了ClassVisitor接口。功能预期差不多,尽管在ASM 4系列就已经去掉了ClassAdapter,把其功能融合到ClassVisitor里了。但是我们就写写啦,这些都不重要。

EditParamClassAdapter.java(修改属性控制权限):
package com.equaker.demo.asm;import com.sun.xml.internal.ws.org.objectweb.asm.ClassAdapter;
import com.sun.xml.internal.ws.org.objectweb.asm.ClassVisitor;
import com.sun.xml.internal.ws.org.objectweb.asm.FieldVisitor;
import com.sun.xml.internal.ws.org.objectweb.asm.Opcodes;/*** 将 m 的访问权限 改为 public**/
public class EditParamClassAdapter extends ClassAdapter {public EditParamClassAdapter(ClassVisitor cv) {super(cv);}@Overridepublic FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {System.out.println("visitField- fieldname: " + name + "; desc: " + desc);//如果属性名称为m,则控制昂问权限为public。if("m".equalsIgnoreCase(name)){access = Opcodes.ACC_PUBLIC;}FieldVisitor fieldVisitor = super.visitField(access, name, desc, signature, value);return fieldVisitor;}
}
DeleteMethodClassAdapter.java(删除inc方法):
package com.equaker.demo.asm;import com.sun.xml.internal.ws.org.objectweb.asm.ClassAdapter;
import com.sun.xml.internal.ws.org.objectweb.asm.ClassVisitor;
import com.sun.xml.internal.ws.org.objectweb.asm.MethodVisitor;/**** 删除 inc() 方法**/
public class DeleteMethodClassAdapter extends ClassAdapter {public DeleteMethodClassAdapter(ClassVisitor cv) {super(cv);}@Overridepublic MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {System.out.println("visitMethod- methodname: " + name + "; desc: " + desc);//直接返回null就行了。if("inc".equalsIgnoreCase(name)){return null;}return super.visitMethod(access, name, desc, signature, exceptions);}
}

这里我们主要完成了在字节码文件的遍历过程中,操作字节码。下面我们试着生成新的class文件。

WriteAsm_Test.java:
package com.equaker.demo.asm;import com.sun.xml.internal.ws.org.objectweb.asm.*;import java.io.*;
import java.lang.reflect.Method;public class WriteAsm_Test implements Opcodes {public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {ClassReader classReader = new ClassReader(new FileInputStream("F:\\demo\\DemoClass.class"));//写class文件ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);ClassAdapter editParamClassAdapter = new EditParamClassAdapter(classWriter);ClassAdapter deleteMethodClassAdapter = new DeleteMethodClassAdapter(editParamClassAdapter);classReader.accept(deleteMethodClassAdapter, ClassReader.SKIP_DEBUG);byte[] bs = classWriter.toByteArray();OutputStream outputStream = new FileOutputStream("F:\\demo\\DemoClass2.class");outputStream.write(bs);outputStream.flush();}
}

这里有个东西需要注意一下。我么声明了两个Adapter去设计字节码的修改。这里面存在一个引用链的问题。读者可以仔细看一下我的声明顺序以及利用构造器进行依赖传递。

运行结果:我们把生成的class文件放在idea里面 就可以看到结果了。不出意外的话应该没问题。

3.3,初三级

你有没有想过从无到有生成一个Class?希望你想过。。。

我们先来看看我们的目标类吧。

TestClass.java:

package com.equaker.demo.asm;public class TestClass {public static final String userName = "@EQuaker";public Integer age;public static final String getUserName() {System.out.println("this is getUserName() method");System.out.println("this is hello World");return "这是静态将变量(在类加载解析阶段由符号引用转为直接引用,所以不牵扯 getfield操作)";}public Integer getAge() {return this.age;}
}

当我写到这里的时候,我犹豫了半天,该咋写,因为东西实在是太多了。大家可以看到我定义的两个成员变量类型的区别。其实光是这里都大有文章。慢慢来,慢慢来,慢慢来。。。

我们利用ASM框架生成字节码class的时候主要用的就是ClassWrite的visitMethod 和visitFiled。这个可以帮助我们去构建。代码里面细说。

WriteAsm_Test.java:
package com.equaker.demo.asm;import com.sun.xml.internal.ws.org.objectweb.asm.*;import java.io.*;
import java.lang.reflect.Method;public class WriteAsm_Test implements Opcodes {public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {ClassReader classReader = new ClassReader(new FileInputStream("F:\\demo\\DemoClass.class"));//声明一个ClassWriterClassWriter classWriter2 = new ClassWriter(ClassWriter.COMPUTE_MAXS);//classWriter2.newClass("TestClass");//classWriter2.newMethod("TestClass", "testMethod", "()V", false);//参数依次为:JDK版本52(1.8),类控制访问权限为public,继承Object, 没有泛型, 不是接口classWriter2.visit(52, Opcodes.ACC_PUBLIC|Opcodes.ACC_SUPER, "com/equaker/demo/asm/TestClass", null, "java/lang/Object", null);//新增一个构造器 <init> ()V 表示狗构造器
//        MethodVisitor constructMethodVisitor = classWriter2.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
//        constructMethodVisitor.visitCode();
//        constructMethodVisitor.visitEnd();//新增一个public static final String userName = "@EQuaker" 字段FieldVisitor nameFieldVisitor = classWriter2.visitField(Opcodes.ACC_PUBLIC|Opcodes.ACC_STATIC|Opcodes.ACC_FINAL, "userName", "Ljava/lang/String;", null, "@EQuaker");nameFieldVisitor.visitEnd();//新增一个public Integer age 字段FieldVisitor ageFieldVisitor = classWriter2.visitField(Opcodes.ACC_PUBLIC, "age", "Ljava/lang/Integer;", null, 25);ageFieldVisitor.visitEnd();//新增一个static final string getUserName方法MethodVisitor testMethodVisitor = classWriter2.visitMethod(Opcodes.ACC_PUBLIC|Opcodes.ACC_STATIC|Opcodes.ACC_FINAL, "getUserName", "()Ljava/lang/String;", null, null);testMethodVisitor.visitCode();//新增方法内代码 System.out.Println("this is getUserName() method")testMethodVisitor.visitFieldInsn(Opcodes.GETSTATIC,"java/lang/System", "out","Ljava/io/PrintStream;");testMethodVisitor.visitLdcInsn("this is getUserName() method");testMethodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");//新增方法内代码 System.out.Println("this is hello World")testMethodVisitor.visitFieldInsn(Opcodes.GETSTATIC,"java/lang/System", "out","Ljava/io/PrintStream;");testMethodVisitor.visitLdcInsn("this is hello World");testMethodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");//新增方法内代码 return userName;//由于usernam 是 静态最终型的,所以再编译期间已经由符号引用转为直接引用// 这一点需要和实例变量区分testMethodVisitor.visitLdcInsn("这是静态将变量(在类加载解析阶段由符号引用转为直接引用,所以不牵扯 getfield操作)");testMethodVisitor.visitInsn(Opcodes.ARETURN);//        testMethodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
//        testMethodVisitor.visitFieldInsn(Opcodes.GETFIELD, "com/equaker/demo/asm/TestClass", "userName", "Ljava/lang/String;");//参数一次为: 最大栈大小, 最大本地变量个数。想都不用想 1》2testMethodVisitor.visitMaxs(2,1);testMethodVisitor.visitEnd();// 新增 getAge()方法MethodVisitor agetMethodVisitor = classWriter2.visitMethod(Opcodes.ACC_PUBLIC, "getAge", "()Ljava/lang/Integer;", null, null);agetMethodVisitor.visitVarInsn(Opcodes.ALOAD, 0);agetMethodVisitor.visitFieldInsn(Opcodes.GETFIELD, "com/equaker/demo/asm/TestClass", "age", "Ljava/lang/Integer;");//testMethodVisitor.visitLdcInsn(22);agetMethodVisitor.visitInsn(Opcodes.ARETURN);agetMethodVisitor.visitMaxs(2, 1);agetMethodVisitor.visitEnd();byte[] bs2 = classWriter2.toByteArray();OutputStream outputStream2 = new FileOutputStream("F:\\soyuan_workspace_study\\equaker-demo\\src\\test\\java\\com\\equaker\\demo\\asm\\TestClass.class");outputStream2.write(bs2);outputStream2.flush();}}

大家看到这里,不知道有没有一丝眼熟,有没有想起我开篇介绍的通过javap -verbose DemoClass.class文件结构。现在可以去看看两者之间的关系了。无论是声明变量还是声明方法。我们使用的都是全路径。这里我们挑几个说说吧。

加入方法是public static final String getName(String name){}。那我们的access应该就是:Opcodes.ACC_PUBLIC|Opcodes.ACC_STATIC|Opcodes.ACC_FINAL。方法名:getName。方法描述descriptor:

(Ljava/lang/String;)Ljava/lang/String;。。。。。应该不难理解。

还有关于业务代码的问题比如System.out.println("");看似简单一行,实际上我们需要先声明一个java/io/PrintStream类型的变量,然后在创建变量(即打印内容),最后在执行反射执行代码。意会。。。

我们再来看一个问题,大家再仔细看看我上面DemoClass.class 通过javap贴出来的内容。为了方便,我再贴一遍。

F:\demo>javap -verbose DemoClass.class
Classfile /F:/demo/DemoClass.classLast modified 2021-1-14; size 1155 bytesMD5 checksum 46133fe2e9d345b28f020039cdfb039aCompiled from "DemoClass.java"
public class com.equaker.demo.asm.DemoClassminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref          #17.#41        // java/lang/Object."<init>":()V#2 = Methodref          #42.#43        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;#3 = Fieldref           #5.#44         // com/equaker/demo/asm/DemoClass.age:Ljava/lang/Integer;#4 = Fieldref           #5.#45         // com/equaker/demo/asm/DemoClass.m:I#5 = Class              #46            // com/equaker/demo/asm/DemoClass#6 = String             #47            // @EQuaker#7 = Class              #48            // java/lang/StringBuilder#8 = Methodref          #7.#41         // java/lang/StringBuilder."<init>":()V#9 = String             #35            // hi#10 = Methodref          #7.#49         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#11 = Methodref          #7.#50         // java/lang/StringBuilder.toString:()Ljava/lang/String;#12 = Fieldref           #51.#52        // java/lang/System.out:Ljava/io/PrintStream;#13 = String             #37            // hello#14 = Methodref          #53.#54        // java/io/PrintStream.println:(Ljava/lang/String;)V#15 = String             #55            // hello2#16 = String             #38            // dd#17 = Class              #56            // java/lang/Object#18 = Utf8               m#19 = Utf8               I#20 = Utf8               userName#21 = Utf8               Ljava/lang/String;#22 = Utf8               ConstantValue#23 = Utf8               age#24 = Utf8               Ljava/lang/Integer;#25 = Utf8               <init>#26 = Utf8               ()V#27 = Utf8               Code#28 = Utf8               LineNumberTable#29 = Utf8               inc#30 = Utf8               ()I#31 = Utf8               getUserName#32 = Utf8               ()Ljava/lang/String;#33 = Utf8               getAge#34 = Utf8               ()Ljava/lang/Integer;#35 = Utf8               hi#36 = Utf8               (Ljava/lang/String;)Ljava/lang/String;#37 = Utf8               hello#38 = Utf8               dd#39 = Utf8               SourceFile#40 = Utf8               DemoClass.java#41 = NameAndType        #25:#26        // "<init>":()V#42 = Class              #57            // java/lang/Integer#43 = NameAndType        #58:#59        // valueOf:(I)Ljava/lang/Integer;#44 = NameAndType        #23:#24        // age:Ljava/lang/Integer;#45 = NameAndType        #18:#19        // m:I#46 = Utf8               com/equaker/demo/asm/DemoClass#47 = Utf8               @EQuaker#48 = Utf8               java/lang/StringBuilder#49 = NameAndType        #60:#61        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#50 = NameAndType        #62:#32        // toString:()Ljava/lang/String;#51 = Class              #63            // java/lang/System#52 = NameAndType        #64:#65        // out:Ljava/io/PrintStream;#53 = Class              #66            // java/io/PrintStream#54 = NameAndType        #67:#68        // println:(Ljava/lang/String;)V#55 = Utf8               hello2#56 = Utf8               java/lang/Object#57 = Utf8               java/lang/Integer#58 = Utf8               valueOf#59 = Utf8               (I)Ljava/lang/Integer;#60 = Utf8               append#61 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;#62 = Utf8               toString#63 = Utf8               java/lang/System#64 = Utf8               out#65 = Utf8               Ljava/io/PrintStream;#66 = Utf8               java/io/PrintStream#67 = Utf8               println#68 = Utf8               (Ljava/lang/String;)V
{public com.equaker.demo.asm.DemoClass();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: aload_05: bipush        257: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;10: putfield      #3                  // Field age:Ljava/lang/Integer;13: returnLineNumberTable:line 3: 0line 8: 4public int inc();descriptor: ()Iflags: ACC_PUBLICCode:stack=3, locals=1, args_size=10: aload_01: dup2: getfield      #4                  // Field m:I5: iconst_16: iadd7: dup_x18: putfield      #4                  // Field m:I11: ireturnLineNumberTable:line 11: 0public static final java.lang.String getUserName();descriptor: ()Ljava/lang/String;flags: ACC_PUBLIC, ACC_STATIC, ACC_FINALCode:stack=1, locals=0, args_size=00: ldc           #6                  // String @EQuaker2: areturnLineNumberTable:line 15: 0public java.lang.Integer getAge();descriptor: ()Ljava/lang/Integer;flags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: getfield      #3                  // Field age:Ljava/lang/Integer;4: areturnLineNumberTable:line 18: 0public static final void hello();descriptor: ()Vflags: ACC_PUBLIC, ACC_STATIC, ACC_FINALCode:stack=2, locals=0, args_size=00: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #13                 // String hello5: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;11: ldc           #15                 // String hello213: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V16: returnLineNumberTable:line 27: 0line 28: 8line 29: 16public java.lang.String dd();descriptor: ()Ljava/lang/String;flags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: ldc           #16                 // String dd2: areturnLineNumberTable:line 32: 0
}
SourceFile: "DemoClass.java"

你有没有发现关于getUsername和getAge在code部分的代码不太一样。在getAge里面我们先Aload一个栈空间,然后通过getField操作获取age变量,最后执行返回。而在getUserName里面缺没有涉及aload和getfield操作,而是直接声明了一个本地栈变量(ldc)。如果发现了,说明你很棒哦,,,

其实这里面的知识点是关于类加载方面的。我们都知道类加载器的过程主要有五步:加载,验证,准备,解析,初始化。而在初始化的时候进行的操作就是符号引用转变为直接应用。由于是静态最终型,所以在方法体内直接引入内容@EQuaker 而不是地址引用。具体参考我的另一篇文章《Java类加载,垃圾收集》

3.4,初三留级-复读

既然都有字节码了,你有没有想过去执行这个class里面的某个方法???你可能一脸懵逼,我TM连代码都没有。先抛出我的答案:类加载器+反射

大家可能都知道类加载器是用来加载类的,可是真的需要在什么时候使用呢?这不,机会来了。。。

MyClassLoader.java:

package com.equaker.demo.asm;import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;public class MyCLassLoader extends ClassLoader {public MyCLassLoader(){super();}@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {return super.loadClass(name);}//全路径名需要换成自己的,我这里省事了。。。public Class<?> defineMyClass(byte[] b, int off, int len){return super.defineClass("com.equaker.demo.asm.TestClass", b, off, len);}}

反射代码我们直接在上一步的WriteAsm_Test.java 追加。

package com.equaker.demo.asm;import com.sun.xml.internal.ws.org.objectweb.asm.*;import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class WriteAsm_Test implements Opcodes {public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {//。。。代码在上面MyCLassLoader classLoader = new MyCLassLoader();Class clazz = classLoader.defineMyClass(bs2, 0, bs2.length);Method[] methods = clazz.getDeclaredMethods();for(Method method : methods){System.out.println("方法名称: "+method.getName());if("getUserName".equalsIgnoreCase(method.getName())){Object ret = method.invoke(clazz, null);System.out.println("ret:" + ret);}}}}

as you see:

visitField- fieldname: m; desc: I
visitField- fieldname: userName; desc: Ljava/lang/String;
visitField- fieldname: age; desc: Ljava/lang/Integer;
visitMethod- methodname: <init>; desc: ()V
visitMethod- methodname: inc; desc: ()I
visitMethod- methodname: getUserName; desc: ()Ljava/lang/String;
visitMethod- methodname: getAge; desc: ()Ljava/lang/Integer;
visitMethod- methodname: hi; desc: (Ljava/lang/String;)Ljava/lang/String;
visitMethod- methodname: hello; desc: ()V
visitMethod- methodname: dd; desc: ()Ljava/lang/String;
方法名称: getAge
方法名称: getUserName
this is getUserName() method
this is hello World
ret:这是静态将变量(在类加载解析阶段由符号引用转为直接引用,所以不牵扯 getfield操作)

讲完这些,初中该学的东西差不多了,大家感到困惑的点可能就是 javap里面的那个代码该咋写。推荐个神器 ASMifer。 idea可以安装插件 bytecode outline。

截两张图给大家。

图一,

图二,

你没看错,他帮你写好代码了。实际开发中我们参考api就可以了。

字节码操控框架ASM - 初识相关推荐

  1. asm(Java字节码操控框架)和 CGlib(Code Generation Library)

    asm概述 asm 是一个 Java 字节码操控框架. 它能够以二进制形式修改已有类或者动态生成类.ASM 可以直接产生二进制 class 文件,也可以在类被加载入Java 虚拟机之前动态改变类行为. ...

  2. java- ASM 字节码操控框架

    我们知道Java是静态语言,而python.ruby是动态语言,Java程序一旦写好很难在运行时更改类的行为,而python.ruby可以. 不过基于bytecode层面上我们可以做一些手脚,来使Ja ...

  3. asm java字节码操控工具学习

    #ASM技术研究 ##ASM是什么 ASM 是一个 Java 字节码操控框架.它能被用来动态生成类或者增强既有类的功能.ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟 ...

  4. aop 获取方法入参出参_ASM字节码编程 | JavaAgent+ASM字节码插桩采集方法名称及入参和出参结果并记录方法耗时...

    作者:小傅哥 博客:bugstack.cn ❝ 沉淀.分享.成长,让自己和他人都能有所收获! ❞ 一.前言 在我们实际的业务开发到上线的过程中,中间都会经过测试.那么怎么来保证测试质量呢?比如:提交了 ...

  5. ASM字节码编程 | JavaAgent+ASM字节码插桩采集方法名称以及入参和出参结果并记录方法耗时

    作者:小傅哥 博客:bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 在我们实际的业务开发到上线的过程中,中间都会经过测试.那么怎么来保证测试质量呢?比如:提交了多少代码 ...

  6. Java字节码技术(二)字节码增强之ASM、JavaAssist、Agent、Instrumentation

    文章目录 前言 从AOP说起 静态代理 动态代理 JavaProxy CGLIB 字节码增强实现AOP ASM JavaAssist 运行时类加载 Instrumentation接口 JavaAgen ...

  7. java 字节码增强之ASM

    ASM系列之一:初探ASM 一.什么是ASM ASM是一个JAVA字节码分析.创建和修改的开源应用框架.在ASM中提供了诸多的API用于对类的内容进行字节码操作的方法.与传统的BCEL和SERL不同, ...

  8. 深入解析Java字节码和字节码操作类库ASM源码解析

    导语 在非黑即白的静态编译语言和动态脚本语言的分类方法中,java的立场显得很尴尬.首先java是静态强类型语言,所以java源代码是需要编译的.但是javac编译后的产出物并不是和传统的编译语言一样 ...

  9. Java Agent与ASM字节码介绍

    Java Agent Java Agent是jdk1.5以后引入的,也叫做Java代理. javaAgent是运行方法之前的拦截器.我们利用javaAgent和ASM字节码技术,在JVM加载class ...

最新文章

  1. 创客集结号_你知道单机片和Arduino之间的区别吗
  2. 《微信跳一跳》安卓手机刷分软件搭建及攻略
  3. SqlServer 跨服务器查询
  4. OpenCV计算机视觉编程之三种图像像素的遍历方法
  5. HTML5 基础知识(四)
  6. 组合模式_设计模式结构性:组合模式(CompositePattern)
  7. php5.6.30源码下载,PHP 5.6.30 正式发布,安全漏洞修复
  8. go 执行sh失败_为容器化的 Go 程序搭建 CI
  9. 通过简单的 ResourceManager 管理 XNA 中的资源,WPXNA(二)
  10. . SQL多条件查询存储过程
  11. 前端学习(2123):知识回顾
  12. 脉位调制解调 matlab,基于matlab的am调制解调
  13. python之将python代码编译成.so
  14. 我的日程安排系列问题(区间重叠问题)
  15. Chrome 地址栏 Google 搜索错误处理 隐私设置错误 您的连接不是私密连接
  16. python对缩进太敏感...a_a
  17. Webgl实现的天气效果(下雨、下雪)
  18. selenium 反爬虫之跳过滑块验证
  19. 用python画宇宙_快来用Python和Blender超简单绘制你的宇宙飞船!
  20. Windows 7系统中的彩蛋“God Mode”

热门文章

  1. Python----方法返回None值报错 TypeError NoneType object is not callable
  2. 正斜杠和反斜杠的区别
  3. 练习Vue烘培坊项目
  4. 字符类别判断(C语言)
  5. android 支付密码,支付密码怎么设置?支付密码设置的方法[多图]
  6. 织梦CMS粘贴图片自动上传到服务器(Java版)
  7. 为什么不同的计算机有不同的IP地址,我的电脑的IP地址和网上显示的为什么不一样 – 手机爱问...
  8. 2022年移动应用程序开发的最佳后端框架
  9. logstash5.X 时差8小时问题
  10. 微信加入“微社区” Discuz!发力移动社交