[](()引入 ASM
好了,下面我们开始正式学习 ASM。
首先我们找到ASM 的官网:
https://asm.ow2.io/
在官网你可以看到目前最新的版本,还有一份详细的 User guide,基本包含了所有 API 的介绍。
看官网上版本迭代目前已经跟新到9.1了,那就试用最新版本吧:
// https://mvnrepository.com/artifact/org.ow2.asm/asm-commons
implementation group: ‘org.ow2.asm’, name: ‘asm-commons’, version: ‘9.1’
[](()尝试分析 Class 文件
从学习的角度来说,在修改 class文件之前,我们可以先学习下怎么读取 class 文件内部的各个部分。
比如我想在编译期间通过编译的*.class的文件,获取其内部的所有方法名称,字段名称。
[](()Tree Api
对于分析class 文件,我们最希望的方式是什么?
肯定是:我给你个 class 文件,然后你给我返回个 ClassNode对象,这个对象最好有个 类似List,List这样的方法或者字段。
恩…想得倒美,ASM 是这么简单的东西吗?
不过,ASM 还真就这么简单,我们来看一个类 ClassNode,这个类上注释是:
A node that represents a class.
用来指代一个 class 文件。
那么我们想要的 class 内部的一切,应该可以通过这个类的 API 直接或者间接的获取。
没错,是的,看一眼:
我们只要通过 class 文件构造这么个 ClassNode 对象,好像就可以为所欲为了。
来看下代码把:
首先我们编写一个 User:
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
然后我们希望获取 User.class 中包含的所有方法以及字段:
public class TreeApiTest {
public static void main(String[] args) throws Exception {
Class clazz = User.class;
String clazzFilePath = Utils.getClassFilePath(clazz);
ClassReader classReader = new ClassReader(new FileInputStream(clazzFilePath));
ClassNode classNode = new ClassNode(Opcodes.ASM5);
classReader.accept(classNode, 0);
List methods = classNode.methods;
List fields = classNode.fields;
System.out.println(“methods:”);
for (MethodNode methodNode : methods) {
System.out.println(methodNode.name + ", " + methodNode.desc);
}
System.out.println(“fields:”);
for (FieldNode fieldNode : fields) {
System.out.println(fieldNode.name + ", " + fieldNode.desc);
}
}
}
上述代码有个辅助类Utils.getClassFilePath方法我贴一下,主要是可以通过Class 对象,找到其在 AS 中具体的路径。
public static String getClassFilePath(Class clazz) {
// file:/Users/zhy/hongyang/repo/BlogDemo/app/build/intermediates/javac/debug/classes/
String buildDir = clazz.getProtectionDomain().getCodeSource().getLocation().getFile();
String fileName = clazz.getSimpleName() + “.class”;
File file = new File(buildDir + clazz.getPackage().getName().replaceAll(“[.]”, “/”) + “/”, fileName);
return file.getAbsolutePath();
}
看下我们代码的流程:
-
首先我们拿到 class 文件的路径;
-
然后交给 ClassReader
-
再构造一个 ClassNode 对象
-
调用 ClassReader.accept()方法完成对 class 遍历,并把相关信息记录到 ClassNode 对象中;
这个时候,我们就能够通过 ClassNode 去拿我们所想要的信息了,看一下输出:
methods:
, ()V
getName, ()Ljava/lang/String;
getAge, ()I
fields:
name, Ljava/lang/String;
age, I
到这里,有没有发现,如果只是读取 class 文件,是不是简单得不能再简单了?
上述的 API,称为 Tree Api,即我们分析完成 class 文件,把信息存储到 ClassNode,然后通过 ClassNode 再读取即可,有点类似 xml 文件解析时,把整个 xml 文件读取到内存中的方式。
[](()Core Api
不过大家如果看博客,其实上述写法在博客中并不多见,更多的博客上书写的还是基于“事件驱动”的 API,即解析class 文件过程中,每遇到一个“节点”,把节点信息交给你,我们类似于监听“节点”的解析事件,我们看下代码:
publi