什么是 ASM ?
ASM 是一款直接操作字节码(即 class 文件)的框架,可以都已生成好的字节码进行改动或者生成。类似框架有 javassist 相对 ASM 简单许多。
谁在用 ASM ?
大名鼎鼎的 FastJSON & CGLIB
说在前面
官网 API 文档
网上关于 ASM 都是一些零散的资料,框架本事并不是很难。但是要彻底学习需要一段时间。建议理解就好。日常工作基本上不会使用。
字节码操作
众所周知 .java
文件经过,javac
的编译后会生成一个后缀为 .class
的字节码文件。而这个文件才是 JVM
要真正读取运行的文件。而这个 .class
文件是一个可编辑的文件。你使用任何一个文本编辑都可以看到以下内容
你可以通过开头的 cafe babe
认定他是一个 .class
文件。关于字节码的解读网上资料较多你可以百度一下
手动修改字节码
上方字节码源码如下:
public class Hello{
public static void main(String[] args) {
int i = 8888;
System.out.print(i);
}
}
我们都知道如果使用 java Hello
运行上方编译过后的 .class
文件会输出 8888
我们回过头再是观察上方字节码会发现 .class
文件是由 16进制
组成的。我们通过 进制转换工具 将 8888
转换成16进制就是 22b8
然后在编译后的文件找到
22b8
替换成
270f(转换为10进制为 9999 )
再使用 java Hello
运行(不要再使用 javac 编译)得出结果:
手动修改字节码码就成功了。
通过 ASM 修改字节码。
- ASM 有两种修改字节码的方式
流 和 树。
流:一边读取class
文件一边修改。
树:全部读取出来,选择任意节点修改。
- ASM 中的部分修改并不是真正的修改。例如:
你打算通过 ASM 修改一个方法的返回值。ASM 会重建这个方法。把之前的删除掉。
- ASM 有三个重要的类
ClassReader:读取
class
文件转换成的byte
数组
ClassVisitor & ClassNode:分别对应 流 & 树 中字节码操作类
ClassWriter:将处理好的class
文件转换成byte
数组
演示,因为代码有点多。完整的代码前往 github
ASM 中创建一个方法
我们先要获取这个类的 class
文件,然后转成 byte
数组
package com.annie.util;
import com.annie.App;
import java.io.*;
public class FileUtil {
// 将 class 文件转成 byte 数组(就是下方 MyMain 实体类编译后的 class 文件位置)
public static byte[] File2Byte() throws IOException {
// 获取 class 文件所在的位置
String path = App.getMyMainPath();
File file = new File(path);
byte[] bytes = new byte[(int) file.length()];
try (FileInputStream fileInputStream = new FileInputStream(file);) {
fileInputStream.read(bytes);
}
return bytes;
}
// 将 byte 数组写进 class 文件
public static boolean Byte2File(byte[] bytes) throws IOException {
String path = App.getMyMainPath();
File file = new File(path);
try (FileOutputStream fileOutputStream = new FileOutputStream(file);) {
fileOutputStream.write(bytes);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
}
新建一个实体类
// 类中有 两个参数 五个方法
class MyMain {
int i = 1;
int j = 2;
public void t01() {
}
public void t02(String str) {
}
public String t03() {
return "hello";
}
public String t04(String str) {
return str;
}
public int t05(int i) {
return i;
}
}
编写添加属性的方法
public static void createField() throws IOException {
byte[] bytes = FileUtil.File2Byte();
// 读取 byte 数组
ClassReader cr = new ClassReader(bytes);
// 创建一个树节点
ClassNode cn = new ClassNode();
// 将 cr 转换好的字节码放进 cn 树中
// SKIP_DEBUG 跳过类文件中的调试信息,比如行号信息(LineNumberTable)等
// SKIP_CODE:跳过方法体中的 Code 属性(方法字节码、异常表等)
cr.accept(cn, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG);
// 新建一个属性
// 参数作用域,参数名称,参数类型,参数签名,参数初始值
FieldNode fn = new FieldNode(Opcodes.ACC_PUBLIC, "code", "Ljava/lang/String;", null, null);
// 将属性添加到 cn 参数节点中
cn.fields.add(fn);
// 再 JVM 存在,数栈和局部变量表
// new ClassWriter(0):表示我们手动设置。(这里我们用不到)
ClassWriter cw = new ClassWriter(0);
// 将新生成的 'class' 放进 cw 中
cn.accept(cw);
// cw 将新的 'class' 处理成byte覆盖之前原始的class文件
FileUtil.Byte2File(cw.toByteArray());
}
然后通过观察项目中的 MyMain.class
文件会发现。
你会发现多了一个
code
属性。我这是使用的是 idea 进行的反编译。只将 .class
文件拖进 idea 中就行。使用 javap -l MyMain.class
一样可以达到效果。
说在后面的话,
再博客中可能演示的不是那么仔细。如果有兴趣可以去github上下载一分代码看一下。关键的还是要多看官网 API 文档说白了。ASM 也就是一个类而已,和我们平时用的 HTTP IO 也差不了多少。只是领域不同。
ASM 能做什么?
Java 序列的软件破解。JavaAgent 技术等。
网友评论