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的功能

  1. 从零生成一个类的字节码
  2. 分析已存在的字节码
  3. 对已存在的字节码进行修改

以上是通俗解释,实际上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框架学习-从零构建类的字节码的更多相关文章

  1. 从Java进程里dump出类的字节码文件

    想要查看一些被增强过的类的字节码,或者一些AOP框架的生成类,就需要dump出运行时的Java进程里的字节码. 从运行的java进程里dump出运行中的类的class文件的方法: 用agent att ...

  2. 浅谈Java反射机制 之 获取类的字节码文件 Class.forName("全路径名") 、getClass()、class

    另一个篇:获取 类 的 方法 和 属性(包括构造函数) 先贴上Java反射机制的概念: AVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法: 对于任意一个对象,都能够调用它 ...

  3. Java代理全攻略【有瑕疵:字节码生成部分没看到,最后两节没仔细看,累了】

    Java代理 1.代理模式 定义:给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象. 其实就是委托.聚合.中间人. 为了保持行为的 ...

  4. [JavaCore] 取得类的字节码、取得类的装载器

    三种方式取得类的字节码: 1. 类名.class BranchInfoService.class 2. 对象名.getClass() branchInfoService.getClass() 3. C ...

  5. Java集合框架学习(一)List

    先附一张Java集合框架图. 从上面的集合框架图可以看到,Java集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射.Coll ...

  6. Java集合框架学习

    集合框架 集合框架的目标 该框架必须是高性能的.基本集合(动态数组,链表,树,哈希表)的实现必须是高效的. 该框架允许 不同类型的集合,以类似的方式工作,具有高度的互操作性. 对一个集合的扩展和适应必 ...

  7. Java-ASM框架学习-修改类的字节码

    Tips: ASM使用访问者模式,学会访问者模式再看ASM更加清晰 ClassReader 用于读取字节码,父类是Object 主要作用: 分析字节码里各部分内容,如版本.字段等等 配合其他Visit ...

  8. java reflect 初始学习 动态加载类

    首先要理解Class类: 在java 的反射中,Class.forName("com.lilin.Office") 使用类的全名,这样获取,不仅仅表示了类的类类型,同时还代表着类的 ...

  9. Java的动态编译、动态加载、字节码操作

    想起来之前做的一个项目:那时候是把需要的源代码通过文件流输出到一个.java文件里,然后调用sun的Comipler接口动态编译成.class文件,然后再用专门写的一个class loader加载这个 ...

随机推荐

  1. Tracking Analyst Tools(Tracking Analyst 工具)

    Tracking Analyst 工具 # Process: 创建追踪图层 arcpy.MakeTrackingLayer_ta("", 输出图层, "NO_TIME_Z ...

  2. BIBD&SBIBD的矩阵题

    证明不存在 \(01\) 方阵 \(A\) 使得: \(A^TA=\begin{pmatrix}7&2&\dots &2\\2&7&\dots&2\\ ...

  3. SSM框架项目的mvc拦截器

    为了防止用户在不登录的情况下通过并接请求直接访问系统,我们需要通过session和拦截器来防止这样的情况. 拦截器的配置: 为拦截器建立一个包:interceptor,并在包里建立 LoginInte ...

  4. L1-027 出租 (20 分) java题解

    下面是新浪微博上曾经很火的一张图: 一时间网上一片求救声,急问这个怎么破.其实这段代码很简单,index数组就是arr数组的下标,index[0]=2 对应 arr[2]=1,index[1]=0 对 ...

  5. SoapUI入门实例

    一.Soapui介绍 WSDL(Web Services Description Language)就是这样一个基于XML的语言,用于描述Web Service及其函数.参数和返回值.它是WebSer ...

  6. cookie和session和localStorage的区别

    这三个都是保存在浏览器端,而且都是同源的. Session仅在当前浏览器窗口关闭有效,不能持久保存 Localstorage始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据 Cookie只在设置 ...

  7. UltraSoft - DDL Killer - Alpha 项目展示

    团队介绍 CookieLau fmh 王 FUJI LZH DZ Monster PM & 后端 前端 前端 前端 后端 后端 软件介绍 项目简介 项目名称:DDLKiller 项目描述:&q ...

  8. 【二食堂】Alpha - Scrum Meeting 1

    Scrum Meeting 1 例会时间:4.10 8:00 - 8:30 进度情况 组员 上周进度 明日任务 李健 1. 在Anaconda3中搭建了python和django的环境issue1. ...

  9. Spring 5 中函数式web开发中的swagger文档

    Spring 5 中一个非常重要的更新就是增加了响应式web开发WebFlux,并且推荐使用函数式风格(RouterFunction和 HandlerFunction)来开发WebFlux.对于之前主 ...

  10. 【做题记录】[NOI2008] 假面舞会—有向图上的环与最长链

    luogu 1477 [NOI2008] 假面舞会 容易发现: 如果图中没有环,那么面具种数一定是所有联通块内最长链之和,最少为 \(3\) . 如果有环,则面具种数一定是所有环的大小的最大公约数. ...