asm项目结构
1. 项目结构
1.1. asm的仓库
1.2 核心项目
1.2.1 org.objectweb.asm 和 org.objectweb.asm.signature
1.2.2 org.objectweb.asm.util
1.2.3 org.objectweb.asm.commons
1.2.4 org.objectweb.asm.tree
1.2.5 org.objectweb.asm.tree.analysis
1.3 其他项目
1.3.1 asm-all和asm-parent
1.4 maven依赖 asm的配置
2. IDEA的插件
2.1 ASM Bytecode outline 插件
2.1 hexview插件
3. asm的使用示例
3.1 源码 & 字节码
3.2 访问类的方法和字段
core API
tree API
3.3 添加一个字段
core API
tree API
3.4 新增方法
core API
tree API
3.5 删除字段和方法
core API
tree API
3.6 修改方法
core API
tree API
3.7 AdviceAdapter的使用
core API
tree API
3.8 try-catch
core API
4. bytebuddy的示例
1. 项目结构
1.1. asm的仓库
maven仓库
1.2 核心项目
1.2.1 org.objectweb.asm 和 org.objectweb.asm.signature
包定义了基于事件的API,并提供了类分析器和写入器组件。它们包含在 asm.jar 中。
1.2.2 org.objectweb.asm.util
包,位于asm-util.jar中,提供各种基于核心 API 的工具,可以在开发和调试 ASM 应用程序时使用。
1.2.3 org.objectweb.asm.commons
包提供了几个很有用的预定义类转换器,它们大多是基于核心 API 的。这个包包含在 asm-commons.jar中。
1.2.4 org.objectweb.asm.tree
包,位于asm-tree.jar 存档文件中,定义了基于对 象的 API,并提供了一些工具,用于在基于事件和基于对象的表示方法之间进行转换。
1.2.5 org.objectweb.asm.tree.analysis
包提供了一个类分析框架和几个预定义的 类 分析器,它们以树 API 为基础。这个包包含在 asm-analysis.jar 文件中。
1.3 其他项目
可以看出来1.2描述的几个包和1.1仓库是基本对应的。
但是除了asm-all和asm-parent,与asm-debug等。
1.3.1 asm-all和asm-parent
asm-all包含了asm-parent,asm-parent 包含了所有的依赖。
一般来讲,使用者希望导入一个总的jar,其中包含子项目的jar。但是asm-all目前只停留在version 5.2,而最新子项目版本已经是8.1,不推荐使用。
asm-all 的pom
4.0.0modelVersion>
org.ow2.asmgroupId>
asm-parentartifactId>
5.2version>
parent>
ASM Allname>
org.ow2.asmgroupId>
asm-allartifactId>
jarpackaging>
project>
asm-parent的pom
parent包含了子项目的依赖
asmartifactId>
${project.groupId}groupId>
${project.version}version>
dependency>
asm-treeartifactId>
${project.groupId}groupId>
${project.version}version>
dependency>
asm-analysisartifactId>
${project.groupId}groupId>
${project.version}version>
dependency>
asm-commonsartifactId>
${project.groupId}groupId>
${project.version}version>
dependency>
asm-utilartifactId>
${project.groupId}groupId>
${project.version}version>
dependency>
asm-xmlartifactId>
${project.groupId}groupId>
${project.version}version>
dependency>
dependencies>
dependencyManagement>
1.4 maven依赖 asm的配置
并不推荐使用1.3导入全部,因为版本太老了
这里开发者需要这么引入。asm-commons 和asm-util都包含了,基本的 core api ,tree api, 和analysis
8.0.1asm.version>
properties>
asmartifactId>
org.ow2.asmgroupId>
${asm.version}version>
dependency>
asm-treeartifactId>
org.ow2.asmgroupId>
${asm.version}version>
dependency>
asm-analysisartifactId>
org.ow2.asmgroupId>
${asm.version}version>
dependency>
asm-commonsartifactId>
org.ow2.asmgroupId>
${asm.version}version>
dependency>
asm-utilartifactId>
org.ow2.asmgroupId>
${asm.version}version>
dependency>
asm-xmlartifactId>
org.ow2.asmgroupId>
${asm.version}version>
dependency>
dependencies>
dependencyManagement>
asm-commonsartifactId>
org.ow2.asmgroupId>
${asm.version}version>
dependency>
asm-utilartifactId>
org.ow2.asmgroupId>
${asm.version}version>
dependency>
dependencies>
核心的包都具备了,是版本8.0.1
2. IDEA的插件
装两个插件。
ASM Bytecode outline 与hexview
2.1 ASM Bytecode outline 插件
源码&字节码
等价的ASM API
2.1 hexview插件
仅仅是以16进制显示.class文件
这个是真正的字节码,而asm插件中的字节码,是以人类可读的方式处理过。类似javap
3. asm的使用示例
demo源码
这一步门槛比较高,需要对java 字节码的知识。和ASM 有全面的了解。
java 字节码知识推荐 《深入理解jvm 字节码》和jvm 规范比对着看
ASM 推荐阅读官方指导手册,写的很容易懂。博客里提供了中英两版,比对者看。
《深入理解jvm 字节码》中的例子,使用基于事件的core api 和基于文档模型的tree API。如果具备字节码结构的知识、字节码指令知识、栈帧的模型的知识,去理解下面的例子非常容易,因为对类的加工,本质是增删改查字节码内容而已。
3.1 源码 & 字节码
源码
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
#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"
3.2 访问类的方法和字段
core API
接受编译过的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
tree API
等同于上面的例子
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 fields = cn.fields;
for (int i = 0; i
FieldNode fn = fields.get(i);
System.out.println("field in visitor: " + fn.name);
}
List methods = cn.methods;
for (int i = 0; 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:
method in visitor: test01
method in visitor: test02
3.3 添加一个字段
为Application.class添加一个public String name= "demo";字段。通过打印class文件,来展示修改
core API
核心的操作是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 Listlist = 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
#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"
tree API
使用起来更简单,输出的字节码等同上面。
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
3.4 新增方法
为Application.java添加一个方法public void hello(int age, int name)。
core API
核心的方法是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 Listhello(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
#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"
tree API
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
3.5 删除字段和方法
core API
删除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
#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"
tree API
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 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 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();
}
}
3.6 修改方法
把
public void test01()
修改为
public int test01(int n) {
test02();
return a + b + n ;
}
注意修改方法的区别是,Code属性中包含字节码指令,还要计算操作数栈栈深,和本地变量。所以Code属性以visitCode开始,和visitEnd是结束,调用visitMaxs会自动计算栈大小。
这里困难的是Code属性的操作,不过不用担心,前面的插件有用。
core API
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
#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() {
}
}
tree API
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 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();
}
}
3.7 AdviceAdapter的使用
AdviceAdapter是一个抽象类,继承自MethodVisitor,可以很方便的在方法前和方法后插入代码。核心的两个方法如下
onMethodEnter方法开始或者构造器方法中父类的构造器调用以后被回调
onMethodExit:正常退出和异常退出时被调用。正常退出指的是遇到RETURN、ARETURN、LRETURN等方法正常返回的情况。异常退出指的是遇到ATHROW指令,有异常抛出方法返回的情况。比如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 ");
}
core API
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
#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"
tree API
涉及MethodVisitor和MethodNode的相互转换。比较麻烦coreAPI比较简单
3.8 try-catch
很显然上一个小节的代码无法在代码抛出未捕获异常时输出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调用之前,代码如下所示。
core API
源码
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
#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"
4. bytebuddy的示例
直接看bytebuddy官网用处不大,介绍文档还是比较简陋的。我是文档和源码比对者读的。
bytebuddy是基于ASM写的一个工具。也是用来对类进行加工的。很多API的模型继承与ASM,直接阅读bytebuddy官方文档和源码是十分费解的。建议先学习ASM。
下面用bytebuddy演示一个等价的例子。