Java-ASM框架学习-从零构建类的字节码
Tips: ASM使用访问者模式,学会访问者模式再看ASM更加清晰
什么是ASM
ASM是一个操作Java字节码的类库
学习这个类库之前,希望大家对Java 基本IO和字节码有一定的了解。
高版本的ASM库可以操作它所支持的最高JAVA版本及其以下的字节码
ASM版本 | Java版本 |
---|---|
2.0 | 5 |
3.2 | 6 |
4.0 | 7 |
5.0 | 8 |
6.0 | 9 |
6.1 | 10 |
7.0 | 11 |
7.1 | 13 |
8.0 | 14 |
9.0 | 16 |
9.1 | 17 |
ASM的功能
- 从零生成一个类的字节码
- 分析已存在的字节码
- 对已存在的字节码进行修改
以上是通俗解释,实际上ASM能做的还有很多,需要大家慢慢去探索,著名的Spring在内部就使用了ASM。
ASM如何生成一个类的字节码
在从0生成一个类的字节码的过程中,主要是ClassVisitor和ClassWriter两个类在起作用
ClassVisitor
ClassVisitor是一个抽象类,比较常见的子类有ClassWriter类(Core API)和ClassNode类(Tree API)
fields
public abstract class ClassVisitor {
protected final int api;
protected ClassVisitor cv;
}
- api: 指明当前使用的ASM的版本,在Opcodes有相应的常量供选择,如Opcodes.ASM9
- cv: 用于把ClassVisitor连接起来,就像链表一样
methods
在ASM中使用了访问者模式,这个类中有很多的visitxxxx方法,我们重点理解其中的四个(visit/visitField/visitMethod/visitEnd),也是构成class文件的核心
这些方法用于构建字节码,遵循一定的调用顺序如下
visit
[visitSource][visitModule][visitNestHost][visitPermittedSubclass][visitOuterClass]
(
visitAnnotation |
visitTypeAnnotation |
visitAttribute
)*
(
visitNestMember |
visitInnerClass |
visitRecordComponent |
visitField |
visitMethod
)*
visitEnd
visit() 用于填充类基本信息
public void visit(
int version, //java版本
int access, //访问修饰符
String name, //类名
String signature, //泛型参数,没有泛型为null
String superName, //直接父类
String[] interfaces) //接口集合
visitField() 用于填充类的字段表信息
public FieldVisitor visitField(
final int access, //字段的访问修饰符
final String name, //字段的简单名称
final String descriptor, //字段的描述符
final String signature, //字段的泛型,没有泛型为null
final Object value) //字段的值(static 不是final的会自动添加到<clinit>方法 已证实)
visitMethod() 用于填充类的方法表信息
public MethodVisitor visitMethod(
final int access, //方法的访问修饰符
final String name, //方法的简单名称
final String descriptor, //方法的描述符
final String signature,// 方法的泛型
final String[] exceptions) //方法的额外信息(代码、异常)
visitEnd() 用来表示工作完成了,不再调用visit方法了
public void visitEnd()
ClassWriter
fields
字段基本与class文件的组成一致
public class ClassWriter extends ClassVisitor {
public static final int COMPUTE_MAXS = 1;
public static final int COMPUTE_FRAMES = 2;
private int version;
private final SymbolTable symbolTable;
private int accessFlags;
private int thisClass;
private int superClass;
private int interfaceCount;
private int[] interfaces;
private FieldWriter firstField;
private FieldWriter lastField;
private MethodWriter firstMethod;
private MethodWriter lastMethod;
...
}
methods
构造方法
public ClassWriter(int flags) { //COMPUTE_MAXS或者COMPUTE_FRAMES
this((ClassReader)null, flags);
}
参数解析:
- ClassWriter.COMPUTE_MAXS。ASM会自动计算max stacks和max locals
- ClassWriter.COMPUTE_FRAMES(常用)。ASM会自动计算max stacks、max locals和stack map frames
其他visitxxxx方法参考其父类
public byte[] toByteArray() //用于输出字节码
简单示范
/**
* 生成字节码
* @return
*/
public static byte[] generateClass() {
// 创建ClassWriter对象
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
// 向ClassWriter对象添加内容,也就是调用visitxxx方法
cw.visit();
cw.visitField();
cw.visitMethod();
cw.visitEnd();
// 取出ClassWriter对象的结果
return cw.toByteArray();
}
详细设置字段和方法
如果我们只是定义接口的话,前面的方法已经足够使用了,但如果我们需要更多定义,比如给字段加注解、方法体等等,就需要对visitField返回的FieldVisitor和visitMethod返回的MethodVisitor做进一步操作了。这两个类和之前的ClassVisitor类似
FieldVisitor
也是个抽象类,属性也没啥特别的,和ClassVisitor类似的字段,方法也是visitxxx方法,同样有执行顺序
(
visitAnnotation |
visitTypeAnnotation |
visitAttribute
)*
visitEnd
一般情况基本上是用不到这个,但如果你接收了FieldVisitor,就一定要记得visitEnd()
{
FieldVisitor fv = cw.visitField(
Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_STATIC,
"TEST",
"I",
null,
1 //常量值
);
fv.visitEnd();
}
MethodVisitor
也是个抽象类,属性也没啥特别的,和ClassVisitor类似的字段,方法也是visitxxx方法,同样有执行顺序
(visitParameter)*
[visitAnnotationDefault]
(visitAnnotation | visitAnnotableParameterCount | visitParameterAnnotation | visitTypeAnnotation | visitAttribute)*
[
visitCode
(
visitFrame |
visitXxxInsn |
visitLabel |
visitInsnAnnotation |
visitTryCatchBlock |
visitTryCatchAnnotation |
visitLocalVariable |
visitLocalVariableAnnotation |
visitLineNumber
)*
visitMaxs
]
visitEnd
这里给出一个空构造方法定义的实例
{
MethodVisitor mv1 = cw.visitMethod(
Opcodes.ACC_PUBLIC,
"<init>",
"()V",
null,
null
);
// 标志方法体开始
mv1.visitCode();
// 用于执行字节码的一系列方法
mv1.visitVarInsn(Opcodes.ALOAD, 0);
mv1.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv1.visitInsn(Opcodes.RETURN);
// 标志方法体结束,并设定max stacks和max locals,前面对ClassWriter设置了ClassWriter.COMPUTE_FRAMES或者ClassWriter.COMPUTE_MAXS可以乱写这个方法的参数,但不能不调用这个方法
mv1.visitMaxs(1, 1);
//标志结束
mv1.visitEnd();
}
实操生成一个简单类
目标类如下:
public class TestClass {
public int content;
public String result;
public static final boolean flag = true;
public TestClass() {}
}
ASM代码如下
package example.generate;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class Test {
public static byte[] generateClass() {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cw.visit(
Opcodes.V1_8, // Java8
Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, // 访问修饰符
"example/sample/TestClass", // 全限定名
null,
"java/lang/Object", // 直接父类
null
);
{
FieldVisitor fv = cw.visitField( // 不接收返回值效果一样
Opcodes.ACC_PUBLIC,
"content",
"I",
null,
0
);
fv.visitEnd();
}
{
FieldVisitor fv = cw.visitField(
Opcodes.ACC_PUBLIC,
"result",
"Ljava/lang/String;", // 分号不要忘
null,
""
);
fv.visitEnd();
}
{
FieldVisitor fv = cw.visitField(
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL,
"flag",
"Z",
null,
true
);
fv.visitEnd();
}
{
MethodVisitor mv = cw.visitMethod(
Opcodes.ACC_PUBLIC,
"<init>", // 构造方法在字节码里的名字
"()V",
null,
null
);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
测试代码:
@Test
void fun4() {
try {
Class cls = Class.forName("example.sample.TestClass");
Object obj = cls.newInstance();
Field f1 = cls.getField("content");
f1.set(obj, 10);
System.out.println(f1.get(obj));
System.out.println(f1.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
Java-ASM框架学习-从零构建类的字节码的更多相关文章
- 从Java进程里dump出类的字节码文件
想要查看一些被增强过的类的字节码,或者一些AOP框架的生成类,就需要dump出运行时的Java进程里的字节码. 从运行的java进程里dump出运行中的类的class文件的方法: 用agent att ...
- 浅谈Java反射机制 之 获取类的字节码文件 Class.forName("全路径名") 、getClass()、class
另一个篇:获取 类 的 方法 和 属性(包括构造函数) 先贴上Java反射机制的概念: AVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法: 对于任意一个对象,都能够调用它 ...
- Java代理全攻略【有瑕疵:字节码生成部分没看到,最后两节没仔细看,累了】
Java代理 1.代理模式 定义:给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象. 其实就是委托.聚合.中间人. 为了保持行为的 ...
- [JavaCore] 取得类的字节码、取得类的装载器
三种方式取得类的字节码: 1. 类名.class BranchInfoService.class 2. 对象名.getClass() branchInfoService.getClass() 3. C ...
- Java集合框架学习(一)List
先附一张Java集合框架图. 从上面的集合框架图可以看到,Java集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射.Coll ...
- Java集合框架学习
集合框架 集合框架的目标 该框架必须是高性能的.基本集合(动态数组,链表,树,哈希表)的实现必须是高效的. 该框架允许 不同类型的集合,以类似的方式工作,具有高度的互操作性. 对一个集合的扩展和适应必 ...
- Java-ASM框架学习-修改类的字节码
Tips: ASM使用访问者模式,学会访问者模式再看ASM更加清晰 ClassReader 用于读取字节码,父类是Object 主要作用: 分析字节码里各部分内容,如版本.字段等等 配合其他Visit ...
- java reflect 初始学习 动态加载类
首先要理解Class类: 在java 的反射中,Class.forName("com.lilin.Office") 使用类的全名,这样获取,不仅仅表示了类的类类型,同时还代表着类的 ...
- Java的动态编译、动态加载、字节码操作
想起来之前做的一个项目:那时候是把需要的源代码通过文件流输出到一个.java文件里,然后调用sun的Comipler接口动态编译成.class文件,然后再用专门写的一个class loader加载这个 ...
随机推荐
- Python笔记_1语法总结
前言导读 本章知识点是我在最初期听python视频教程的时候整理总结的笔记 对python语法的认识对以后代码的解读有着很大的帮助. 1 新建python命名规则 新建项目名 :数字编号 项目名称 新 ...
- 最新.NET MAUI有什么惊喜?
.NET 6 Preview 7 现已发布啦,我们为 .NET 多平台应用程序 UI (MAUI) 引入了所有的新布局.这是性能和可靠性的重大变化.我们很高兴我们还增加了一些关于accessibili ...
- Probius+Prometheus通过API集成POD监控
上一篇文章Probius+Kubernetes任务系统如虎添翼讲了我们把Kubernetes集成进了任务系统Probius,上线后小伙伴反馈虽然摆脱了Kubernetes-Dashboard,但还是得 ...
- Shadertoy 教程 Part 1 - 介绍
Note: This series blog was translated from Nathan Vaughn's Shaders Language Tutorial and has been au ...
- Coursera Deep Learning笔记 改善深层神经网络:超参数调试 Batch归一化 Softmax
摘抄:https://xienaoban.github.io/posts/2106.html 1. 调试(Tuning) 超参数 取值 #学习速率:\(\alpha\) Momentum:\(\bet ...
- MySQL:怒刷牛客网“sql实战”
MySQL:怒刷牛客网"sql实战" 在对MySQL有一定了解后,抽空刷了一下 牛客网上的 数据库SQL 实战,在此做一点小小的记录 SQL1 查找最晚入职员工的所有信息 sele ...
- stm32学习笔记之GPIO功能框图分析
GPIO 是通用输入输出端口的简称,简单来说就是STM32 可控制的引脚,STM32 芯片的GPIO 引脚与外部设备连接起来,从而实现与外部通讯.控制以及数据采集的功能.STM32 芯片的GPIO被分 ...
- Spring Security:简单的保护一个SpringBoot应用程序(总结)
Spring Security 在 Java类中的配置 在 Spring Security 中使用 Java配置,可以轻松配置 Spring Security 而无需使用 XML . 在Spring ...
- P2472 [SCOI2007]蜥蜴(最大流)
P2472 [SCOI2007]蜥蜴 自己第一道独立做题且一遍AC的网络流题纪念... 看到这道题我就想到网络流建图的方式了... 首先根据每个高度,我们将每个点拆成两个点限流.之后根据跳的最大距离, ...
- GDI+图形图像技术1
System.Drawing命名空间提供了对GDI+基本图形功能的访问,其中一些子命名空间中提供了更高级的功能. GDI+由GDI发展而来,是Windows图形显示程序与实际物理设备之间的桥梁. GD ...