概述
ASM是一个操作字节码的类库,提到字节码,很多人想到的首先是JAVA字节码。其实Kotlin,Groovy等语言也会生成字节码,并且和Java的字节码一样,都能被虚拟机识别执行,这就意味着ASM也可以用在这些语言上,比如Android现在的主流开发语言Kotlin。就拿JAVA语言来说,我们都知道,我们编写代码的那个文件称为.java文件,经过Java编译器(javac), .java文件会被编译成.class文件,在.class文件中就存储着字节码(bytecode)数据,ASM可以对字节码数据做修改,然后生成一份新的字节码文件,而且ASM还可以在什么都没有的情况下,直接生成一个.class文件。本文的demo就是演示这个场景,除此之外,ASM还能做分析操作,分析操作是指它可以对一份字节码做分析统计的操作,但是不生成新的字节码文件,也就是说它只是单纯分析,不做任何修改。本文使用JAVA语言作为ASM的演示demo。
环境配置
当我们想要去了解一个语言或者是开源库的时候,第一件事都是将他们需要的环境配置好,然后跑起来看下效果。ASM的环境配置比较简单,直接引入相关的库就可以了,ASM在Android中一般是集成到gradle脚本中去使用,本文为了演示,直接使用 IDEA集成开发环境+maven的方式去做演示,后面集成到Android中的时候其实方法也是一样的,引入对应的依赖就可以了。
我们在IDEA集成开发环境中新建一个maven项目,如下图所示,注意红圈中的选择。 建完以后,项目的结构如下所示: 然后我们找到pom.xml文件,导入asm的依赖就可以了,如下:
cpp
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http:// maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>AsmDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</
project.build.sourceEncoding>
<asm.version>9.0</asm.version>
</properties>
<dependencies>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-tree</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-analysis</artifactId>
<version>${asm.version}</version>
</dependency>
</dependencies>
</project>
demo尝鲜
demo会生成一个新的类,并且调用类中的toString方法打印一段日志,为了演示,定义一个ClassLoader类加载器去加载Asm生成的类。
生成的类对应的.java文件如下:
java
public class HelloWorldAsm {
@Override
public String toString() {
return "this is a HelloAsm class obj";
}
}
上面的类我们不用自己写,而是用Asm去生成上面的类对应的字节码文件,然后通过类加载器去加载执行我们生成的类。生成类的代码如下:
cpp
public class HelloWordAsm implements Opcodes {
public static byte[] genHelloWorldClass(){
ClassWriter cw =
new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cw.visit(V1_8,ACC_PUBLIC|ACC_SUPER,"demo/HelloWorldAsm",
null,
"java/lang/Object",
null);
{
// 添加无参构造方法
MethodVisitor mv1 = cw.visitMethod(
ACC_PUBLIC,
"<init>",
"()V",
null,
null
);
mv1.visitCode();
mv1.visitVarInsn(ALOAD,0);
// 调用父类的构造方法
mv1.visitMethodInsn(
INVOKESPECIAL,
"java/lang/Object",
"<init>",
"()V",
false
);
mv1.visitInsn(RETURN);
mv1.visitMaxs(1,1);
mv1.visitEnd();
}
{
// 添加一个toString 方法,返回String
MethodVisitor mv2 = cw.visitMethod(
ACC_PUBLIC,
"toString",
"()Ljava/lang/String;",
null,
null
);
mv2.visitCode();
mv2.visitLdcInsn("this is a HelloAsm class obj");
mv2.visitInsn(ARETURN);
mv2.visitMaxs(1,1);
mv2.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
上面的代码生成一个类后,我们编写一个类加载器去加载我们生成的类
cpp
public class MyClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name) throws
ClassNotFoundException {
System.out.println("MyClassLoader: findClass: name==>"
+ name);
if("demo.HelloWorldAsm".equals(name)){
byte [] bytes = HelloWordAsm.genHelloWorldClass();
Class<?> aClass = defineClass(name, bytes, 0,
bytes.length);
return aClass;
}
return super.findClass(name);
}
}
最后我们编写测试代码验证下生成类的正确性:
cpp
public class TestHelloAsm {
public static void main(String[] args) {
MyClassLoader myClassLoader = new MyClassLoader();
try {
Class<?> aClass =
myClassLoader.loadClass("demo.HelloWorldAsm");
Object instance = aClass.newInstance();
System.out.println("instance: " + instance);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
如上面代码所示,我们使用类加载器去加载我们生成的类,然后创建了一个对象,当我们打印这个对象的时候,就会调用这个对象的toString方法,打印出我们的生成类中的toString方法返回的字符串信息。运行结果如下所示: 运行出上面的结果就证明环境OK了
源码地址
为了让读者能更快的看到效果,在此提供上源码,后续有对应的文章也会同步到这个源码上。源码地址