包定义了基于事件的API,并提供了类分析器和写入器组件。它们包含在 asm.jar 中。
包,位于asm-util.jar中,提供各种基于核心 API 的工具,可以在开发和调试 ASM 应用程序时使用。
包提供了几个很有用的预定义类转换器,它们大多是基于核心 API 的。这个包包含在 asm-commons.jar中。
包,位于asm-tree.jar 存档文件中,定义了基于对 象的 API,并提供了一些工具,用于在基于事件和基于对象的表示方法之间进行转换。
包提供了一个类分析框架和几个预定义的 类 分析器,它们以树 API 为基础。这个包包含在 asm-analysis.jar 文件中。
可以看出来1.2
描述的几个包和1.1
仓库是基本对应的。
但是除了asm-all
和asm-parent
,与asm-debug
等。
asm-all包含了asm-parent,asm-parent 包含了所有的依赖。
一般来讲,使用者希望导入一个总的jar,其中包含子项目的jar。但是asm-all目前只停留在version 5.2
,而最新子项目版本已经是8.1
,不推荐使用。
<project>
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.ow2.asmgroupId>
<artifactId>asm-parentartifactId>
<version>5.2version>
parent>
<name>ASM Allname>
<groupId>org.ow2.asmgroupId>
<artifactId>asm-allartifactId>
<packaging>jarpackaging>
project>
asm-parent
的pom
<dependencyManagement>
<dependencies>
<dependency>
<artifactId>asmartifactId>
<groupId>${project.groupId}groupId>
<version>${project.version}version>
dependency>
<dependency>
<artifactId>asm-treeartifactId>
<groupId>${project.groupId}groupId>
<version>${project.version}version>
dependency>
<dependency>
<artifactId>asm-analysisartifactId>
<groupId>${project.groupId}groupId>
<version>${project.version}version>
dependency>
<dependency>
<artifactId>asm-commonsartifactId>
<groupId>${project.groupId}groupId>
<version>${project.version}version>
dependency>
<dependency>
<artifactId>asm-utilartifactId>
<groupId>${project.groupId}groupId>
<version>${project.version}version>
dependency>
<dependency>
<artifactId>asm-xmlartifactId>
<groupId>${project.groupId}groupId>
<version>${project.version}version>
dependency>
dependencies>
dependencyManagement>
并不推荐使用1.3
导入全部,因为版本太老了
这里开发者需要这么引入。asm-commons
和asm-util
都包含了,基本的 core api ,tree api, 和analysis
<properties>
<asm.version>8.0.1asm.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<artifactId>asmartifactId>
<groupId>org.ow2.asmgroupId>
<version>${asm.version}version>
dependency>
<dependency>
<artifactId>asm-treeartifactId>
<groupId>org.ow2.asmgroupId>
<version>${asm.version}version>
dependency>
<dependency>
<artifactId>asm-analysisartifactId>
<groupId>org.ow2.asmgroupId>
<version>${asm.version}version>
dependency>
<dependency>
<artifactId>asm-commonsartifactId>
<groupId>org.ow2.asmgroupId>
<version>${asm.version}version>
dependency>
<dependency>
<artifactId>asm-utilartifactId>
<groupId>org.ow2.asmgroupId>
<version>${asm.version}version>
dependency>
<dependency>
<artifactId>asm-xmlartifactId>
<groupId>org.ow2.asmgroupId>
<version>${asm.version}version>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<artifactId>asm-commonsartifactId>
<groupId>org.ow2.asmgroupId>
<version>${asm.version}version>
dependency>
<dependency>
<artifactId>asm-utilartifactId>
<groupId>org.ow2.asmgroupId>
<version>${asm.version}version>
dependency>
dependencies>
装两个插件。
ASM Bytecode outline
与hexview
仅仅是以16进制显示.class文件
这个是真正的字节码,而asm插件中的字节码,是以人类可读的方式处理过。类似javap
demo源码
这一步门槛比较高,需要对java 字节码的知识。和ASM 有全面的了解。
《深入理解jvm 字节码》
和jvm 规范
比对着看《深入理解jvm 字节码》
中的例子,使用基于事件的core api
和基于文档模型的tree API
。如果具备字节码结构的知识、字节码指令知识、栈帧的模型的知识,去理解下面的例子非常容易,因为对类的加工,本质是增删改查字节码内容而已。
源码
package com;
public class Application {
public int a = 0;
public int b = 1;
public void test01() {
}
public void test02() {
}
}
字节码
javap -verbose Application.class
编译后的类不包含package和import信息。
Classfile /Users/sunqiyuan/Desktop/Work/mycode/Javadetail/BYTECODE/bytecode/target/classes/com/Application.class
Last modified 2020-6-30; size 464 bytes
MD5 checksum 66668ae198d146acc35018549c757af7
Compiled from "Application.java"
public class com.Application
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#20 // java/lang/Object."":()V
#2 = Fieldref #4.#21 // com/Application.a:I
#3 = Fieldref #4.#22 // com/Application.b:I
#4 = Class #23 // com/Application
#5 = Class #24 // java/lang/Object
#6 = Utf8 a
#7 = Utf8 I
#8 = Utf8 b
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = Utf8 Code
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lcom/Application;
#16 = Utf8 test01
#17 = Utf8 test02
#18 = Utf8 SourceFile
#19 = Utf8 Application.java
#20 = NameAndType #9:#10 // "":()V
#21 = NameAndType #6:#7 // a:I
#22 = NameAndType #8:#7 // b:I
#23 = Utf8 com/Application
#24 = Utf8 java/lang/Object
{
public int a;
descriptor: I
flags: ACC_PUBLIC
public int b;
descriptor: I
flags: ACC_PUBLIC
public com.Application();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field a:I
9: aload_0
10: iconst_1
11: putfield #3 // Field b:I
14: return
LineNumberTable:
line 3: 0
line 4: 4
line 5: 9
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 this Lcom/Application;
public void test01();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/Application;
public void test02();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/Application;
}
SourceFile: "Application.java"
接受编译过的Application.class
的byte[]。ClassVisitor中覆盖FieldVisitor
和MethodVisitor
方法,进行了方法和field的打印。
本类中ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG
跳过对debug信息和方法Code属性的访问。
package com;
import org.objectweb.asm.*;
import static jdk.internal.org.objectweb.asm.Opcodes.ASM5;
/**
* 访问类的方法和区域
*/
public class B_visitContent extends ClassLoader {
public void visitByCoreAPI() throws Exception {
ClassReader cr = Tool.getClassReader();
// 使用 new ClassWriter(0) 时,不会自动计算任何东西。必须自行计算帧、局部变量与操作数栈的大小
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
System.out.println("field in visitor: " + name);
return super.visitField(access, name, descriptor, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
System.out.println("method in visitor: " + name);
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
};
/*
ClassReader.SKIP_DEBUG 跳过类文件中的调试信息,比如行号信息(LineNumberTable)
ClassReader.SKIP_CODE 跳过方法体中的Code属性,比如(方法字节码、异常表等信息)
ClassReader.SKIP_DEBUG 展开StackMapTable属性
ClassReader.SKIP_DEBUG 跳过StackMapTable属性
*/
cr.accept(cv, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
}
public static void main(String[] args) throws Exception{
new B_visitContent().visitByCoreAPI();
}
}
打印结果
field in visitor: a
field in visitor: b
method in visitor:
method in visitor: test01
method in visitor: test02
等同于上面的例子
package com;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
import java.util.List;
import static jdk.internal.org.objectweb.asm.Opcodes.ASM5;
/**
* 访问类的方法和区域
*/
public class B_visitContent extends ClassLoader {
public void visitByTreeAPI() throws Exception {
ClassReader cr = Tool.getClassReader();
// 使用 new ClassWriter(0) 时,不会自动计算任何东西。必须自行计算帧、局部变量与操作数栈的大小
ClassNode cn = new ClassNode();
/*
ClassReader.SKIP_DEBUG 跳过类文件中的调试信息,比如行号信息(LineNumberTable)
ClassReader.SKIP_CODE 跳过方法体中的Code属性,比如(方法字节码、异常表等信息)
ClassReader.SKIP_DEBUG 展开StackMapTable属性
ClassReader.SKIP_DEBUG 跳过StackMapTable属性
*/
cr.accept(cn, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
List<FieldNode> fields = cn.fields;
for (int i = 0; i <fields.size() ; i++) {
FieldNode fn = fields.get(i);
System.out.println("field in visitor: " + fn.name);
}
List<MethodNode> methods = cn.methods;
for (int i = 0; i <methods.size() ; i++) {
MethodNode mn = methods.get(i);
System.out.println("method in visitor: " + mn.name);
}
// 写,非必要
ClassWriter cw = new ClassWriter(0);
cr.accept(cw,0);
byte[] bytesModified = cw.toByteArray();
}
public static void main(String[] args) throws Exception{
new B_visitContent().visitByTreeAPI();
}
}
打印
field in visitor: a
field in visitor: b
method in visitor: <init>
method in visitor: test01
method in visitor: test02
为Application.class
添加一个public String name= "demo";
字段。通过打印class文件,来展示修改
核心的操作是visitField()
+visitEnd()
package com;
import org.objectweb.asm.*;
import static org.objectweb.asm.Opcodes.ASM5;
public class C_addField {
public void addFieldByCoreAPI() throws Exception {
ClassReader cr = Tool.getClassReader();
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
@Override
public void visitEnd() {
// visitEnd是访问类,访问的最后一个事件。在这个事件前添加变量就可以
super.visitEnd();
/**
* field的模板 public List list = null;
*
* Opcodes.ACC_PUBLIC public 权限标志
* "name" 变量名
* "Ljava/lang/String" 变量返回值。字节码中,类型都是以L开头+类的全名(用/代替.)
* null 泛型的标识
* "demo" 值
*/
FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC, "name", "Ljava/lang/String;", null, "demo");
if (fv != null) {
System.out.println("add");
fv.visitEnd();
}
}
};
//cr.accept(cv,ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG);
cr.accept(cv,0);
byte[] bytesModifield = cw.toByteArray();
Tool.save(this.getClass(),Application.class,bytesModifield);
}
public void addFiledByTreeAPI() {
}
public static void main(String[] args) throws Exception {
new C_addField().addFieldByCoreAPI();
}
}
字节码
javap -verbose C_addField_Application.class
可以看到有name字段出现
Classfile /Users/sunqiyuan/Desktop/Work/mycode/Javadetail/BYTECODE/bytecode/target/classes/com/C_addField_Application.class
Last modified 2020-7-1; size 3150 bytes
MD5 checksum 906f08c3db7beb47033fa2b8c176d9c7
Compiled from "Application.java"
public class com.Application
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Utf8 com/Application
#2 = Class #1 // com/Application
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 Application.java
#6 = Utf8 a
#7 = Utf8 I
#8 = Utf8 b
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = NameAndType #9:#10 // "":()V
#12 = Methodref #4.#11 // java/lang/Object."":()V
#13 = NameAndType #6:#7 // a:I
#14 = Fieldref #2.#13 // com/Application.a:I
#15 = NameAndType #8:#7 // b:I
#16 = Fieldref #2.#15 // com/Application.b:I
#17 = Utf8 this
#18 = Utf8 Lcom/Application;
#19 = Utf8 test01
#20 = Utf8 test02
#21 = Utf8 name
#22 = Utf8 Ljava/lang/String;
#23 = Utf8 demo
#24 = String #23 // demo
#25 = Utf8 ConstantValue
#26 = Utf8 Code
#27 = Utf8 LineNumberTable
#28 = Utf8 LocalVariableTable
#29 = Utf8 SourceFile
{
public int a;
descriptor: I
flags: ACC_PUBLIC
public int b;
descriptor: I
flags: ACC_PUBLIC
// 就是里
public java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC
ConstantValue: String demo
public com.Application();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #12 // Method java/lang/Object."":()V
4: aload_0
5: iconst_0
6: putfield #14 // Field a:I
9: aload_0
10: iconst_1
11: putfield #16 // Field b:I
14: return
LineNumberTable:
line 3: 0
line 4: 4
line 5: 9
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 this Lcom/Application;
public void test01();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/Application;
public void test02();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/Application;
}
SourceFile: "Application.java"
使用起来更简单,输出的字节码等同上面。
package com;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import static org.objectweb.asm.Opcodes.ASM5;
public class C_addField {
public void addFiledByTreeAPI() throws Exception {
ClassReader cr = Tool.getClassReader();
ClassNode cn = new ClassNode();
cr.accept(cn,ASM5);
// 新的field
FieldNode fn = new FieldNode(Opcodes.ACC_PUBLIC,"name","Ljava/lang/String;",null,"demo");
cn.fields.add(fn);
//
ClassWriter cw =new ClassWriter(0);
cn.accept(cw);
byte[] bytesModified = cw.toByteArray();
Tool.save(this.getClass(),Application.class,bytesModified);
}
public static void main(String[] args) throws Exception {
new C_addField().addFiledByTreeAPI();
}
}
字节码
等同于core
为Application.java
添加一个方法public void hello(int age, int name)
。
核心的方法是visitMethod
package com;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import static org.objectweb.asm.Opcodes.ASM5;
public class D_addMethod {
public void addMethodByCoreAPI() throws Exception {
ClassReader cr = Tool.getClassReader();
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
@Override
public void visitEnd() {
// visitEnd是访问类,访问的最后一个事件。在这个事件前添加变量就可以
super.visitEnd();
/**
* method的模板 public List hello(int i );
*
* Opcodes.ACC_PUBLIC public 权限标志
* "hello" 变量名
* "(ILjava/lang/String;)V" 方法类型, ()代表参数:I int 'Ljava/lang/String;' String ; V代表返回值void
* null 泛型的标识
* null 异常数组
*/
MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "hello", "(ILjava/lang/String;)V", null, null);
if (mv != null) {
mv.visitEnd();
}
}
};
//cr.accept(cv,ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG);
cr.accept(cv, 0);
byte[] bytesModifield = cw.toByteArray();
Tool.save(this.getClass(), Application.class, bytesModifield);
}
public static void main(String[] args) throws Exception {
new D_addMethod().addMethodByCoreAPI();
}
}
字节码
新增了hello方法
Classfile /Users/sunqiyuan/Desktop/Work/mycode/Javadetail/BYTECODE/bytecode/target/classes/com/D_addMethod_Application.class
Last modified 2020-7-1; size 505 bytes
MD5 checksum 9ce9e7b87f84dd4a8df95f4e4407eea5
Compiled from "Application.java"
public class com.Application
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Utf8 com/Application
#2 = Class #1 // com/Application
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 Application.java
#6 = Utf8 a
#7 = Utf8 I
#8 = Utf8 b
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = NameAndType #9:#10 // "":()V
#12 = Methodref #4.#11 // java/lang/Object."":()V
#13 = NameAndType #6:#7 // a:I
#14 = Fieldref #2.#13 // com/Application.a:I
#15 = NameAndType #8:#7 // b:I
#16 = Fieldref #2.#15 // com/Application.b:I
#17 = Utf8 this
#18 = Utf8 Lcom/Application;
#19 = Utf8 test01
#20 = Utf8 test02
#21 = Utf8 hello
#22 = Utf8 (ILjava/lang/String;)V
#23 = Utf8 Code
#24 = Utf8 LineNumberTable
#25 = Utf8 LocalVariableTable
#26 = Utf8 SourceFile
{
public int a;
descriptor: I
flags: ACC_PUBLIC
public int b;
descriptor: I
flags: ACC_PUBLIC
public com.Application();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #12 // Method java/lang/Object."":()V
4: aload_0
5: iconst_0
6: putfield #14 // Field a:I
9: aload_0
10: iconst_1
11: putfield #16 // Field b:I
14: return
LineNumberTable:
line 3: 0
line 4: 4
line 5: 9
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 this Lcom/Application;
public void test01();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/Application;
public void test02();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/Application;
// hello
public void hello(int, java.lang.String);
descriptor: (ILjava/lang/String;)V
flags: ACC_PUBLIC
}
SourceFile: "Application.java"
package com;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import static org.objectweb.asm.Opcodes.ASM5;
public class D_addMethod {
public void addMethodByTreeAPI() throws Exception {
ClassReader cr = Tool.getClassReader();
ClassNode cn = new ClassNode();
cr.accept(cn, ASM5);
// 新的field
MethodNode fn = new MethodNode(Opcodes.ACC_PUBLIC, "hello", "(ILjava/lang/String;)V", null, null);
cn.methods.add(fn);
// 打印
ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
byte[] bytesModified = cw.toByteArray();
Tool.save(this.getClass(), Application.class, bytesModified);
}
public static void main(String[] args) throws Exception {
new D_addMethod().addMethodByCoreAPI();
}
}
字节码
等同于core
删除public int a = 0
和public void test01()
。直接返回null
即可。
package com;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import static org.objectweb.asm.Opcodes.ASM5;
public class E_removeContent {
public void removeContentByCoreAPI() throws Exception {
ClassReader cr = Tool.getClassReader();
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
if (name.equals("a")) {
return null;
}
return super.visitField(access, name, descriptor, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
if (name.equals("test01")) {
return null;
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
};
//cr.accept(cv,ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG);
cr.accept(cv, 0);
byte[] bytesModifield = cw.toByteArray();
Tool.save(this.getClass(), Application.class, bytesModifield);
}
public static void main(String[] args) throws Exception {
new E_removeContent().removeContentByCoreAPI();
}
}
字节码
Classfile /Users/sunqiyuan/Desktop/Work/mycode/Javadetail/BYTECODE/bytecode/target/classes/com/E_removeContent_Application.class
Last modified 2020-7-1; size 390 bytes
MD5 checksum 3ad649468cd26da2758944804ce99b0b
Compiled from "Application.java"
public class com.Application
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Utf8 com/Application
#2 = Class #1 // com/Application
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 Application.java
#6 = Utf8 b
#7 = Utf8 I
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = NameAndType #8:#9 // "":()V
#11 = Methodref #4.#10 // java/lang/Object."":()V
#12 = Utf8 a
#13 = NameAndType #12:#7 // a:I
#14 = Fieldref #2.#13 // com/Application.a:I
#15 = NameAndType #6:#7 // b:I
#16 = Fieldref #2.#15 // com/Application.b:I
#17 = Utf8 this
#18 = Utf8 Lcom/Application;
#19 = Utf8 test02
#20 = Utf8 Code
#21 = Utf8 LineNumberTable
#22 = Utf8 LocalVariableTable
#23 = Utf8 SourceFile
{
public int b;
descriptor: I
flags: ACC_PUBLIC
public com.Application();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #11 // Method java/lang/Object."":()V
4: aload_0
5: iconst_0
6: putfield #14 // Field a:I
9: aload_0
10: iconst_1
11: putfield #16 // Field b:I
14: return
LineNumberTable:
line 3: 0
line 4: 4
line 5: 9
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 this Lcom/Application;
public void test02();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/Application;
}
SourceFile: "Application.java"
package com;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
import java.util.List;
import static org.objectweb.asm.Opcodes.ASM5;
public class E_removeContent {
public void removeContentByTreeAPI() throws Exception {
ClassReader cr = Tool.getClassReader();
ClassNode cn = new ClassNode();
cr.accept(cn, ASM5);
// 删除 a
List<FieldNode> fields = cn.fields;
for (int i = 0; i < fields.size(); i++) {
FieldNode fn = fields.get(i);
if("a".equals(fn.name)){
fields.remove(fn);
}
}
// 删除 method
List<MethodNode> methods = cn.methods;
for (int i = 0; i < methods.size(); i++) {
MethodNode mn = methods.get(i);
if("test01".equals(mn.name)){
methods.remove(mn);
}
}
// 打印
ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
byte[] bytesModified = cw.toByteArray();
Tool.save(this.getClass(), Application.class, bytesModified);
}
public static void main(String[] args) throws Exception {
new E_removeContent().removeContentByTreeAPI();
}
}
把
public void test01()
修改为
public int test01(int n) {
test02();
return a + b + n ;
}
注意修改方法的区别是,Code
属性中包含字节码指令,还要计算操作数栈栈深,和本地变量。所以Code属性
以visitCode
开始,和visitEnd
是结束,调用visitMaxs
会自动计算栈大小。
这里困难的是Code属性的操作,不过不用担心,前面的插件有用。
package com;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
import java.util.List;
import static org.objectweb.asm.Opcodes.ASM5;
public class F_modifyMethod {
public void modifyMethodByCoreAPI() throws Exception {
ClassReader cr = Tool.getClassReader();
ClassWriter cw = new ClassWriter(0);
ClassVisitor cv = new ClassVisitor(ASM5, cw) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
if (name.equals("test01")) {
MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, name, "(I)I", null, exceptions);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/Application", "test02", "()V", false);
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, "com/Application", "a", "I");
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, "com/Application", "b", "I");
mv.visitInsn(Opcodes.IADD);
mv.visitVarInsn(Opcodes.ILOAD, 1);
mv.visitInsn(Opcodes.IADD);
mv.visitInsn(Opcodes.IRETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
return mv;
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
};
//cr.accept(cv,ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG);
cr.accept(cv, 0);
byte[] bytesModifield = cw.toByteArray();
Tool.save(this.getClass(), Application.class, bytesModifield);
}
public static void main(String[] args) throws Exception {
new F_modifyMethod().modifyMethodByCoreAPI();
}
}
字节码
Classfile /Users/sunqiyuan/Desktop/Work/mycode/Javadetail/BYTECODE/bytecode/target/classes/com/F_modifyMethod_Application.class
Last modified 2020-7-1; size 497 bytes
MD5 checksum 9791aab52aff864e3abc02c4d8fb65b3
Compiled from "Application.java"
public class com.Application
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Utf8 com/Application
#2 = Class #1 // com/Application
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 Application.java
#6 = Utf8 a
#7 = Utf8 I
#8 = Utf8 b
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = NameAndType #9:#10 // "":()V
#12 = Methodref #4.#11 // java/lang/Object."":()V
#13 = NameAndType #6:#7 // a:I
#14 = Fieldref #2.#13 // com/Application.a:I
#15 = NameAndType #8:#7 // b:I
#16 = Fieldref #2.#15 // com/Application.b:I
#17 = Utf8 this
#18 = Utf8 Lcom/Application;
#19 = Utf8 test01
#20 = Utf8 (I)I
#21 = Utf8 test02
#22 = NameAndType #21:#10 // test02:()V
#23 = Methodref #2.#22 // com/Application.test02:()V
#24 = Utf8 Code
#25 = Utf8 LineNumberTable
#26 = Utf8 LocalVariableTable
#27 = Utf8 SourceFile
{
public int a;
descriptor: I
flags: ACC_PUBLIC
public int b;
descriptor: I
flags: ACC_PUBLIC
public com.Application();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #12 // Method java/lang/Object."":()V
4: aload_0
5: iconst_0
6: putfield #14 // Field a:I
9: aload_0
10: iconst_1
11: putfield #16 // Field b:I
14: return
LineNumberTable:
line 3: 0
line 4: 4
line 5: 9
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 this Lcom/Application;
public int test01(int);
descriptor: (I)I
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=2
0: aload_0
1: invokevirtual #23 // Method test02:()V
4: aload_0
5: getfield #14 // Field a:I
8: aload_0
9: getfield #16 // Field b:I
12: iadd
13: iload_1
14: iadd
15: ireturn
16: return
LineNumberTable:
line 9: 16
LocalVariableTable:
Start Length Slot Name Signature
16 1 0 this Lcom/Application;
public void test02();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/Application;
}
SourceFile: "Application.java"
用反编译软件查看,符合预期。
package com;
public class Application {
public int a = 0;
public int b = 1;
public Application() {
}
public int test01(int var1) {
this.test02();
return this.a + this.b + var1;
}
public void test02() {
}
}
package com;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import java.util.List;
import static org.objectweb.asm.Opcodes.ASM5;
public class F_modifyMethod {
public void modifyMethodByTreeAPI() throws Exception {
ClassReader cr = Tool.getClassReader();
ClassNode cn = new ClassNode();
cr.accept(cn, ASM5);
//new 方法
MethodNode newmn = new MethodNode(Opcodes.ACC_PUBLIC, "test01", "(I)I", null, null);
newmn.visitCode();
newmn.visitVarInsn(Opcodes.ALOAD, 0);
newmn.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/Application", "test02", "()V", false);
newmn.visitVarInsn(Opcodes.ALOAD, 0);
newmn.visitFieldInsn(Opcodes.GETFIELD, "com/Application", "a", "I");
newmn.visitVarInsn(Opcodes.ALOAD, 0);
newmn.visitFieldInsn(Opcodes.GETFIELD, "com/Application", "b", "I");
newmn.visitInsn(Opcodes.IADD);
newmn.visitVarInsn(Opcodes.ILOAD, 1);
newmn.visitInsn(Opcodes.IADD);
newmn.visitInsn(Opcodes.IRETURN);
newmn.visitMaxs(2, 2);
newmn.visitEnd();
// 替换
List<MethodNode> methods = cn.methods;
for (int i = 0; i < methods.size(); i++) {
MethodNode mn = methods.get(i);
if ("test01".equals(mn.name)) {
methods.set(i,newmn);
break;
}
}
// 打印
ClassWriter cw = new ClassWriter(0);
cn.accept(cw);
byte[] bytesModified = cw.toByteArray();
Tool.save(this.getClass(), Application.class, bytesModified);
}
public static void main(String[] args) throws Exception {
new F_modifyMethod().modifyMethodByTreeAPI();
}
}
AdviceAdapter是一个抽象类,继承自MethodVisitor,可以很方便的在方法前和方法后插入代码。核心的两个方法如下
比如opcode == Opcodes.ATHROW
现在就是为方法调用添加进入和退出方法时的打印。
原始方法
public void test01() {
System.out.println("mid");
}
修改后
public void test01() {
System.out.println("enter method");
System.out.println("mid");
System.out.println("out method -- normal ");
}
package com;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import java.util.List;
import static org.objectweb.asm.Opcodes.ASM5;
import static org.objectweb.asm.Opcodes.ASM7;
public class G_adviceMethod {
public void adviceMethodByCoreAPI() throws Exception {
ClassReader cr = Tool.getClassReader();
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = new ClassVisitor(ASM7, 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 (!"test01".equals(name)) {
return mv;
}
// 修改test01 进入方法
return new AdviceAdapter(ASM7, mv, access, name, descriptor) {
@Override
protected void onMethodEnter() {
super.onMethodEnter();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("enter method");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
// 退出方法
@Override
protected void onMethodExit(int opcode) {
super.onMethodExit(opcode);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
// 异常
if (opcode == Opcodes.ATHROW) {
mv.visitLdcInsn("out method -- exception");
} else {
mv.visitLdcInsn("out method -- normal ");
}
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
};
}
};
//cr.accept(cv,ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG);
cr.accept(cv, 0);
byte[] bytesModifield = cw.toByteArray();
Tool.save(this.getClass(), Application.class, bytesModifield);
}
public static void main(String[] args) throws Exception {
G_adviceMethod advice = new G_adviceMethod();
// 修改方法
advice.adviceMethodByCoreAPI();
// 调用类
new Application().test01();
}
}
bytecode
开始和结束加入了额外的方法
Classfile /Users/sunqiyuan/Desktop/Work/mycode/Javadetail/BYTECODE/bytecode/target/classes/com/G_adviceMethod_Application.class
Last modified 2020-7-1; size 773 bytes
MD5 checksum 0700ead97bf0b298541bce4618de4e4e
Compiled from "Application.java"
public class com.Application
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Utf8 com/Application
#2 = Class #1 // com/Application
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 Application.java
#6 = Utf8 a
#7 = Utf8 I
#8 = Utf8 b
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = NameAndType #9:#10 // "":()V
#12 = Methodref #4.#11 // java/lang/Object."":()V
#13 = NameAndType #6:#7 // a:I
#14 = Fieldref #2.#13 // com/Application.a:I
#15 = NameAndType #8:#7 // b:I
#16 = Fieldref #2.#15 // com/Application.b:I
#17 = Utf8 this
#18 = Utf8 Lcom/Application;
#19 = Utf8 test01
#20 = Utf8 java/lang/System
#21 = Class #20 // java/lang/System
#22 = Utf8 out
#23 = Utf8 Ljava/io/PrintStream;
#24 = NameAndType #22:#23 // out:Ljava/io/PrintStream;
#25 = Fieldref #21.#24 // java/lang/System.out:Ljava/io/PrintStream;
#26 = Utf8 enter method
#27 = String #26 // enter method
#28 = Utf8 java/io/PrintStream
#29 = Class #28 // java/io/PrintStream
#30 = Utf8 println
#31 = Utf8 (Ljava/lang/String;)V
#32 = NameAndType #30:#31 // println:(Ljava/lang/String;)V
#33 = Methodref #29.#32 // java/io/PrintStream.println:(Ljava/lang/String;)V
#34 = Utf8 mid
#35 = String #34 // mid
#36 = Utf8 out method -- normal
#37 = String #36 // out method -- normal
#38 = Utf8 test02
#39 = Utf8 Code
#40 = Utf8 LineNumberTable
#41 = Utf8 LocalVariableTable
#42 = Utf8 SourceFile
{
public int a;
descriptor: I
flags: ACC_PUBLIC
public int b;
descriptor: I
flags: ACC_PUBLIC
public com.Application();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #12 // Method java/lang/Object."":()V
4: aload_0
5: iconst_0
6: putfield #14 // Field a:I
9: aload_0
10: iconst_1
11: putfield #16 // Field b:I
14: return
LineNumberTable:
line 3: 0
line 4: 4
line 5: 9
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 this Lcom/Application;
public void test01();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #27 // String enter method
5: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
11: ldc #35 // String mid
13: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
16: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
19: ldc #37 // String out method -- normal
21: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
24: return
LineNumberTable:
line 8: 8
line 9: 16
LocalVariableTable:
Start Length Slot Name Signature
8 17 0 this Lcom/Application;
public void test02();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/Application;
}
SourceFile: "Application.java"
ring;)V
40: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
43: ldc #27 // String enter method
45: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
48: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
51: ldc #27 // String enter method
53: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
56: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
59: ldc #35 // String mid
61: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
64: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
67: ldc #37 // String out method -- normal
69: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
72: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
75: ldc #37 // String out method -- normal
77: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
80: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
83: ldc #37 // String out method -- normal
85: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
88: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
91: ldc #37 // String out method -- normal
93: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
96: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
99: ldc #37 // String out method -- normal
101: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
104: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
107: ldc #37 // String out method -- normal
109: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
112: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
115: ldc #37 // String out method -- normal
117: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
120: return
LineNumberTable:
line 8: 56
line 9: 64
LocalVariableTable:
Start Length Slot Name Signature
56 65 0 this Lcom/Application;
public void test02();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/Application;
}
SourceFile: "Application.java"
涉及MethodVisitor和MethodNode的相互转换。比较麻烦coreAPI比较简单
很显然上一个小节的代码无法在代码抛出未捕获异常时输出err quit,比如把foo代码做细微修改,如下所示。
public void test01() {
System.out.println("step 1");
int a = 1 / 0;
System.out.println("step 2");
}
经过上个例子的字节码改写以后,执行的结果输出如下所示。
java -cp . Application
enter method
step 1
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Application.test01(Application.java:24)
at Application.main(Application.java:8)
可以看到并没有如期输出“error exit foo”,因为在字节码中并没有出现显式的ATHROW指令抛出异常,自然无法添加相应的输出语句。为了达到这个效果,需要把方法体用try/finally语句块包裹起来。
这里需要介绍ASM的Label类,与它的英文含义一样,可以给字节码指令地址打标签,标记特定的字节码位置,用于后续跳转等。新增一个Label
可以用MethodVisitor的visitLabel
方法,如下所示。
Label startLabel = new Label();
mv.visitLabel(startLabel);
前面章节介绍过,JVM的异常处理是通过异常表来实现的,try-catch-finally
语句块实际上是标定了异常处理的范围。ASM中可以用visitTryCatchBlock
方法来给一段代码块增加异常表,它的方法签名如下所示。
public void visitTryCatchBlock(Label start, Label end, Label handler, String type)
其中start、end表示异常表开始和结束的位置,handler
表示异常发生后需要跳转到哪里继续执行,可以理解为catch语句块开始的位置,type是异常的类型。
为了给整个方法体包裹try-catch
语句,start Label
应该放在方法visitCode
之后,end Label
则放在visitMaxs
调用之前,代码如下所示。
源码
public void test01() {
System.out.println("mid");
}
结果
public void test01() {
try {
System.out.println("enter method");
System.out.println("mid");
System.out.println("normal exit method");
} catch (Throwable var2) {
System.out.println("error exit method");
throw var2;
}
}
实现
package com;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;
import static org.objectweb.asm.Opcodes.ASM7;
public class H_addTrycatch {
public void addTrycatchByCoreAPI() throws Exception {
ClassReader cr = Tool.getClassReader();
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = new ClassVisitor(ASM7, 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 (!"test01".equals(name)) {
return mv;
}
// 开始的标签
// 修改test01 进入方法
return new AdviceAdapter(ASM7, mv, access, name, descriptor) {
Label startLabel = new Label();
Label endLable = new Label();
@Override
protected void onMethodEnter() {
super.onMethodEnter();
// mark start
mv.visitLabel(startLabel);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("enter method");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
// // mark end
Label endLable = new Label();
// /**
// * startLabel 开始的标签
// * endLable 结束的标签
// * endLable 跳转的位置
// */
mv.visitTryCatchBlock(startLabel, endLable, endLable, null);
mv.visitLabel(endLable);
// // 打印异常
finallyBlock(Opcodes.ATHROW);
// // 抛出异常
mv.visitInsn(ATHROW);
super.visitMaxs(maxStack, maxLocals);
}
private void finallyBlock(int opcode) {
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
if (opcode == Opcodes.ATHROW) {
mv.visitLdcInsn("error exit method");
} else {
mv.visitLdcInsn("normal exit method");
}
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
// 退出方法
@Override
protected void onMethodExit(int opcode) {
super.onMethodExit(opcode);
finallyBlock(opcode);
}
};
}
};
//cr.accept(cv,ClassReader.SKIP_CODE|ClassReader.SKIP_DEBUG);
cr.accept(cv, 0);
byte[] bytesModifield = cw.toByteArray();
Tool.save(this.getClass(), Application.class, bytesModifield);
}
public static void main(String[] args) throws Exception {
H_addTrycatch tc = new H_addTrycatch();
// 修改方法
tc.addTrycatchByCoreAPI();
// 调用类
new Application().test01();
}
}
字节码
Classfile /Users/sunqiyuan/Desktop/Work/mycode/Javadetail/BYTECODE/bytecode/target/classes/com/H_addTrycatch_Application.class
Last modified 2020-7-1; size 767 bytes
MD5 checksum d0368db93d0ee61fa5b689cc76356c0a
Compiled from "Application.java"
public class com.Application
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Utf8 com/Application
#2 = Class #1 // com/Application
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 Application.java
#6 = Utf8 a
#7 = Utf8 I
#8 = Utf8 b
#9 = Utf8 <init>
#10 = Utf8 ()V
#11 = NameAndType #9:#10 // "":()V
#12 = Methodref #4.#11 // java/lang/Object."":()V
#13 = NameAndType #6:#7 // a:I
#14 = Fieldref #2.#13 // com/Application.a:I
#15 = NameAndType #8:#7 // b:I
#16 = Fieldref #2.#15 // com/Application.b:I
#17 = Utf8 this
#18 = Utf8 Lcom/Application;
#19 = Utf8 test01
#20 = Utf8 java/lang/System
#21 = Class #20 // java/lang/System
#22 = Utf8 out
#23 = Utf8 Ljava/io/PrintStream;
#24 = NameAndType #22:#23 // out:Ljava/io/PrintStream;
#25 = Fieldref #21.#24 // java/lang/System.out:Ljava/io/PrintStream;
#26 = Utf8 enter method
#27 = String #26 // enter method
#28 = Utf8 java/io/PrintStream
#29 = Class #28 // java/io/PrintStream
#30 = Utf8 println
#31 = Utf8 (Ljava/lang/String;)V
#32 = NameAndType #30:#31 // println:(Ljava/lang/String;)V
#33 = Methodref #29.#32 // java/io/PrintStream.println:(Ljava/lang/String;)V
#34 = Utf8 mid
#35 = String #34 // mid
#36 = Utf8 normal exit method
#37 = String #36 // normal exit method
#38 = Utf8 error exit method
#39 = String #38 // error exit method
#40 = Utf8 java/lang/Throwable
#41 = Class #40 // java/lang/Throwable
#42 = Utf8 test02
#43 = Utf8 Code
#44 = Utf8 LineNumberTable
#45 = Utf8 LocalVariableTable
#46 = Utf8 StackMapTable
#47 = Utf8 SourceFile
{
public int a;
descriptor: I
flags: ACC_PUBLIC
public int b;
descriptor: I
flags: ACC_PUBLIC
public com.Application();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #12 // Method java/lang/Object."":()V
4: aload_0
5: iconst_0
6: putfield #14 // Field a:I
9: aload_0
10: iconst_1
11: putfield #16 // Field b:I
14: return
LineNumberTable:
line 3: 0
line 4: 4
line 5: 9
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 this Lcom/Application;
public void test01();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #27 // String enter method
5: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
11: ldc #35 // String mid
13: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
16: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
19: ldc #37 // String normal exit method
21: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
24: return
25: getstatic #25 // Field java/lang/System.out:Ljava/io/PrintStream;
28: ldc #39 // String error exit method
30: invokevirtual #33 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
33: athrow
Exception table:
from to target type
0 25 25 any
StackMapTable: number_of_entries = 1
frame_type = 89 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
LineNumberTable:
line 8: 8
line 9: 16
LocalVariableTable:
Start Length Slot Name Signature
8 17 0 this Lcom/Application;
public void test02();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=0, locals=1, args_size=1
0: return
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 1 0 this Lcom/Application;
}
SourceFile: "Application.java"
直接看bytebuddy官网用处不大,介绍文档还是比较简陋的。我是文档和源码比对者读的。
bytebuddy是基于ASM写的一个工具。也是用来对类进行加工的。很多API的模型继承与ASM,直接阅读bytebuddy
官方文档和源码是十分费解的。建议先学习ASM
。
下面用bytebuddy演示一个等价的例子。