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
<