文章目录
1. ASM 是什么
ASM 是一个操作 Java 字节码的类库
- ASM 所操作的对象是字节码 (ByteCode) 数据
- ASM 处理字节码 (ByteCode) 的方式是"拆分-修改-合并"
- 将.class 文件拆分成多个部分
- 对某一个部分的信息进行修改
- 将多个部分重新组织成一个新的.class 文件
2. ASM 能够做什么
父类: 修改成一个新的父类
接口: 添加一个新的接口, 删除已有的接口
字段: 添加一个新的字段, 删除已有的字段
方法: 添加一个新的方法, 删除已有的方法, 修改已有的方法
public class HelloWorld extends Object implements Cloneable {
public int intValue;
public String strValue;
public int add(int a, int b) {
return a + b;
}
public int sub(int a, int b) {
return a - b;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
3. 为什么要学习 ASM
ASM 就是一处位于“Java 语言的世界”边界上的一扇大门, 通过这扇大门, 我们可以前往“字节码的世界”, 在“字节码的世界”里, 我们会看到不一样的“风景”, 能够解决不一样的“问题”
ASM 往往在一些框架的底层起着重要的作用, 介绍两个关于 ASM 的应用场景
- Spring
- JDK
3.1 Spring 当中的 ASM
Spring 框架的 AOP 是依赖于 ASM 的. 具体来说, Spring 的 AOP, 可以通过 JDK 的动态代理来实现, 也可以通过 CGLIB 实现. 其中, CGLib (Code Generation Library) 是在 ASM 的基础上构建起来的, 所以, Spring AOP 是间接的使用了 ASM
3.2 JDK 当中的 ASM
JDK 当中的 Lambda 表达式, 允许把方法作为参数进行传递, 它能够使代码变的更加简洁紧凑, Lambda 表达式的调用是通过 ASM 来实现的
在 rt.jar 文件的 jdk.internal.org.objectweb.asm
包当中, 就包含了 JDK 内置的 ASM 代码. 在 JDK 8 版本当中, 它所使用的 ASM 5.0 版本
跟踪 Lambda 表达式的编码实现, 就会找到 InnerClassLambdaMetafactory.spinInnerClass() 方法. 在这个方法当中, 我们就会看到: JDK 会使用 jdk.internal.org.objectweb.asm.ClassWriter
来生成一个类, 将 lambda 表达式的代码包装起来
// 第一步, 找到这个方法
LambdaMetafactory.metafactory()
// 第二步, 找到这个方法
---> InnerClassLambdaMetafactory.buildCallSite()
// 第三步, 找到这个方法
---> InnerClassLambdaMetafactory.spinInnerClass()
4. ASM 的两个组成部分
从组成结构上来说, ASM 分成两部分
- Core API 包括 asm.jar, asm-util.jar 和 asm-commons.jar
- Tree API 包括 asm-tree.jar 和 asm-analysis.jar
从两者的关系来说, Core API 是基础, 而 Tree API 是在 Core API 的这个基础上构建起来的
4.1 Core API
ASM Core API 包括 asm.jar, asm-util.jar 和 asm-commons.jar
4.1.1 asm.jar
在 asm.jar 文件中, 一共包含了 30 多个类, 重点其中 10 个类, 剩下的 20 多个类是辅助的作用
从无到有生成一个新的类, 其中会涉及到 ClassVisitor, ClassWriter, FieldVisitor, FieldWriter, MethodVisitor, MethodWriter, Label 和 Opcodes 类
修改已有的类, 使之内容发生改变, 其中会涉及到 Cla***eader 和 Type 类
在这 10 个类当中, 最重要的是三个类 Cla***eader, ClassVisitor 和 ClassWriter 类, 这三个类的关系如下
这三个类的作用, 可以简单理解成这样
- Cla***eader 类, 负责读取.class 文件里的内容, 然后拆分成各个不同的部分
- ClassVisitor 类, 负责对.class 文件中某一部分里的信息进行修改
- ClassWriter 类, 负责将各个不同的部分重新组合成一个完整的.class 文件
4.1.2 asm-util.jar
asm-util.jar 主要包含的是一些工具类, 在下图当中可以看到 asm-util.jar 里面包含的具体类文件, 这些类主要分成两种类型
- 以 Check 开头的类, 主要负责检查 (Check) 生成的.class 文件内容是否正确
- 以 Trace 开头的类, 主要负责将.class 文件的内容打印成文字输出, 根据输出的文字信息, 可以探索或追踪 (Trace) .class 文件的内部信息
4.1.3 asm-commons.jar
asm-commons.jar 主要包含的是一些常用功能类, 在下图当中, 可以看到 asm-commons.jar 里面包含的具体类文件
asm-util.jar 与 asm-commons.jar 有什么区别呢?
- 在 asm-util.jar 里, 它提供的是通用性的功能, 没有特别明确的应用场景
- 在 asm-commons.jar 里, 它提供的功能, 都是为解决某一种特定场景中出现的问题而提出的解决思路
5. 搭建 ASM 开发环境
5.1 pom 文件信息
修改 pom.xml
新建一个 maven 项目, 取名为 asm-maven, 修改其中的 pom.xml 文件, 添加 ASM 的 Jar 包依赖. 打开 pom.xml 文件, 并添加如下内容
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<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>
<build>
<plugins>
<!-- Java Compiler -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<fork>true</fork>
<compilerArgs>
<arg>-g</arg>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
5.2 使用 ASM
对 ASM 的使用有一个初步的认识, 为了验证 ASM 的开发环境是能够正常使用的
5.2.1 预期目标
我们的预期目标是, 生成一个 HelloWorld 类, 它对应的 Java 代码如下
public class HelloWorld {
@Override
public String toString() {
return "This is a HelloWorld object.";
}
}
注意, 我们不需要去写这样一个 sample/HelloWorld.java 文件, 只是生成的 HelloWorld 类和这里的 Java 代码是一样的效果
5.2.2 编码实现
public class HelloWorldDump implements Opcodes {
public static byte[] dump() {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cw.visit(V1_8, ACC_PUBLIC | ACC_SUPER, "sample/HelloWorld", 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();
}
{
MethodVisitor mv2 = cw.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
mv2.visitCode();
mv2.visitLdcInsn("This is a HelloWorld object.");
mv2.visitInsn(ARETURN);
mv2.visitMaxs(1, 1);
mv2.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
5.2.3 验证结果
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
if ("sample.HelloWorld".equals(name)) {
byte[] bytes = HelloWorldDump.dump();
Class<?> clazz = defineClass(name, bytes, 0, bytes.length);
return clazz;
}
throw new ClassNotFoundException("Class Not Found: " + name);
}
}
public class HelloWorldRun {
public static void main(String[] args) throws Exception {
MyClassLoader classLoader = new MyClassLoader();
Class<?> clazz = classLoader.loadClass("sample.HelloWorld");
Object instance = clazz.newInstance();
System.out.println(instance);
}
}
运行之后的输出结果 This is a HelloWorld object