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. exe图标消失的解决方案

    步骤 win + r组合键打开运行窗口 输入cmd,回车 在终端窗口右键粘贴即可 taskkill /im explorer.exe /f cd /d %userprofile%\appdata\lo ...

  2. 【UE4 C++】资源烘焙与UE4Editor.exe启动

    资源烘焙 虚幻引擎以内部使用的特定格式存储内容资源,将内容从内部格式转换为特定于平台的格式的过程 称为 烘焙((Cooking) 从编辑器烘焙资源 FIle → Cook Content for Wi ...

  3. mybatis中的#和$的区别 以及 防止sql注入

    声明:这是转载的. mybatis中的#和$的区别 1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号.如:order by #user_id#,如果传入的值是111,那么解析成sq ...

  4. [no code][scrum meeting] Alpha 11

    项目 内容 会议时间 2020-04-17 会议主题 OCR紧急技术风险分析 会议时长 30min 参会人员 PM+OCR组成员 $( "#cnblogs_post_body" ) ...

  5. dice_game攻防世界进阶区

    dice_game XCTF 4th-QCTF-2018 前言,不得不说,虽然是个简单题但是还是要记录一下,来让自己记住这些东西. 考察的知识点是: 1.cdll_loadlibrary加载对应库使得 ...

  6. 设计的MOS管三极管简单开关电路驱动能力不够2

    设计的MOS管三极管简单开关电路驱动能力不够 [复制链接]     lxizj 9 主题 454 帖子 1783 积分 四级会员(40) 积分 1783 发消息 16#   发表于 2012-4-23 ...

  7. 阿里P7面试官:请你简单说一下类加载机制的实现原理?

    面试题:类加载机制的原理 面试官考察点 考察目标: 了解面试者对JVM的理解,属于面试八股文系列. 考察范围: 工作3年以上. 技术背景知识 在回答这个问题之前,我们需要先了解一下什么是类加载机制? ...

  8. sum-root-to-leaf-numbers leetcode C++

    Given a binary tree containing digits from0-9only, each root-to-leaf path could represent a number. ...

  9. Django settings.py设置 DEBUG=False后静态文件无法加载解决

    解决办法: settings.py 文件 DEBUG = False STATIC_ROOT = os.path.join(BASE_DIR,'static') #新增 urls.py文件(项目的) ...

  10. clnt_create: RPC: Port mapper failure - Unable to receive: errno 111 (Connection refused)

    clnt_create: RPC: Port mapper failure - Unable to receive: errno 111 (Connection refused) 关闭防火墙,可以连, ...