org.springframework.asm 是 Spring Framework 内部使用的字节码处理包。它来源于 ASM,Spring 将 ASM 重新打包到自己的命名空间中,用于直接读取、分析和生成 JVM .class 文件。
在 Spring 源码中,ASM 一个非常重要的用途是:
在不加载 Java 类的情况下,直接读取
.class文件中的类、方法、注解等元数据。
理解这个包不需要一开始研究所有实现类。首先建立下面这条主线即可:
读取:
.class
↓
ClassReader
↓
ClassVisitor
├─ FieldVisitor
├─ MethodVisitor
└─ AnnotationVisitor
写入:
ClassVisitor / MethodVisitor
↓
ClassWriter
↓
byte[]
↓
.class
1. 为什么 Spring 需要 ASM
假设项目中存在:
@Component
public class UserService {
@Autowired
private UserRepository repository;
public User findById(Long id) {
return repository.findById(id);
}
}
Spring 在启动过程中进行 classpath 扫描时,需要知道:
这个类叫什么?
它是不是接口?
它是不是抽象类?
它有哪些注解?
有没有 @Component?
有哪些方法?
方法上有哪些注解?
一种方式是使用 Java 反射:
Class<?> clazz = Class.forName(
"com.example.UserService"
);
Component component =
clazz.getAnnotation(Component.class);
但这意味着首先需要让 JVM 加载这个类。
Spring 在扫描 classpath 时可能面对成千上万个 .class 文件。很多类最终根本不会成为 Bean,因此没有必要为了读取少量元数据就把所有候选类都加载进 JVM。
ASM 提供了另一条路径:
UserService.class
↓
读取二进制 Class File
↓
解析 class 元数据
↓
发现 @Component
整个过程不要求先得到:
Class<UserService>
因此 Spring 可以将:
元数据读取
和:
Java 类加载
分离。
这是理解 Spring ASM 最重要的一点。
2. ASM 操作的对象不是 Java 源代码
ASM 操作的是 JVM Class File。
Java 源代码:
public class UserService {
public int add(int a, int b) {
return a + b;
}
}
经过 javac 编译:
UserService.java
↓
javac
↓
UserService.class
.class 不是简单保存 Java 源代码,而是 JVM 定义的一套二进制结构,大致包含:
ClassFile
├─ magic
├─ version
├─ constant_pool
├─ access_flags
├─ this_class
├─ super_class
├─ interfaces
├─ fields
├─ methods
└─ attributes
ASM 就是围绕这些结构工作的。
所以 ASM 中会出现很多看起来和 Java 编程习惯不一样的概念:
descriptor
internal name
constant pool
opcode
label
frame
attribute
这些概念实际上来自 JVM Class File,而不是 ASM 自己创造的。
3. ASM 的核心设计:Reader + Visitor + Writer
ASM 最核心的设计可以概括为:
Reader
↓
Visitor
↓
Writer
其中:
ClassReader
负责读取 .class。
ClassVisitor
MethodVisitor
FieldVisitor
AnnotationVisitor
负责访问 class 中不同层级的信息。
ClassWriter
MethodWriter
FieldWriter
AnnotationWriter
负责重新生成 class。
这套 API 很像 SAX 解析 XML。
它通常不会要求先把整个 class 转换成一个巨大的对象树,然后再开始处理,而是在解析过程中不断产生访问事件:
开始访问类
↓
visit(...)
发现注解
↓
visitAnnotation(...)
发现字段
↓
visitField(...)
发现方法
↓
visitMethod(...)
发现字节码指令
↓
visitInsn(...)
访问结束
↓
visitEnd()
因此 ASM 的核心 API 非常适合:
快速扫描
字节码分析
字节码转换
Java Agent
框架元数据读取
4. ClassReader:读取 .class
ClassReader 是读取 ASM 的主要入口。
例如:
ClassReader reader =
new ClassReader("com.example.UserService");
然后:
reader.accept(visitor, 0);
整个过程可以理解为:
ClassReader
│
│ 读取 class 二进制结构
↓
ClassVisitor.visit(...)
↓
ClassVisitor.visitAnnotation(...)
↓
ClassVisitor.visitField(...)
↓
ClassVisitor.visitMethod(...)
↓
ClassVisitor.visitEnd()
注意:
reader.accept(...)
并不是简单地“把数据交给 Visitor”。
更准确地说,是:
ClassReader一边解析.class,一边主动调用对应的 Visitor 方法。
也就是典型的 Visitor 模式。
5. ClassVisitor:访问整个类
ClassVisitor 负责 class 级别的信息。
最重要的方法之一:
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces)
例如:
public class UserService
extends BaseService
implements UserApi {
}
ASM 可以得到:
name
com/example/UserService
superName
com/example/BaseService
interfaces
com/example/UserApi
示例:
ClassReader reader =
new ClassReader("com.example.UserService");
reader.accept(
new ClassVisitor(Opcodes.ASM9) {
@Override
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces) {
System.out.println("class = " + name);
System.out.println("super = " + superName);
}
},
0
);
这里很容易遇到 ASM 的第一个重要概念:
Internal Name
Java 中:
java.lang.String
ASM / JVM 中经常表示为:
java/lang/String
也就是:
Java Class Name
java.lang.String
↓
Internal Name
java/lang/String
6. MethodVisitor:访问方法
当 ClassReader 解析到一个方法时,会调用:
visitMethod(...)
例如:
@Override
public MethodVisitor visitMethod(
int access,
String name,
String descriptor,
String signature,
String[] exceptions) {
System.out.println(name);
System.out.println(descriptor);
return super.visitMethod(
access,
name,
descriptor,
signature,
exceptions);
}
假设 Java 方法:
public User findById(long id)
ASM 看到的 descriptor 是:
(J)Lcom/example/User;
这里涉及 JVM 的类型描述符。
7. Type 与 Descriptor
JVM 不直接使用:
int
String
User
long[]
描述类型,而是有自己的 descriptor 格式。
常见类型:
Java Descriptor
void V
boolean Z
byte B
char C
short S
int I
float F
long J
double D
String Ljava/lang/String;
User Lcom/example/User;
long[] [J
String[] [Ljava/lang/String;
方法:
User find(String name, long id)
descriptor:
(Ljava/lang/String;J)Lcom/example/User;
结构是:
(参数类型...)返回类型
因此:
(Ljava/lang/String;J)Lcom/example/User;
│ │ └───────────────── User
│ └──────────────────── long
└────────────────────────────────────── String
ASM 提供 Type 帮助处理 descriptor:
Type[] args =
Type.getArgumentTypes(descriptor);
Type returnType =
Type.getReturnType(descriptor);
因此阅读 ASM 代码时,下面三个概念必须区分:
Java Name
java.lang.String
Internal Name
java/lang/String
Descriptor
Ljava/lang/String;
8. AnnotationVisitor:读取注解
假设:
@Component("userService")
public class UserService {
}
当 ClassReader 发现注解时,会调用:
visitAnnotation(...)
例如:
@Override
public AnnotationVisitor visitAnnotation(
String descriptor,
boolean visible) {
System.out.println(descriptor);
return new AnnotationVisitor(Opcodes.ASM9) {
@Override
public void visit(
String name,
Object value) {
System.out.println(
name + " = " + value
);
}
};
}
可能得到:
Lorg/springframework/stereotype/Component;
value = userService
因此:
@Component("userService")
在 ASM 眼中大致变成:
Annotation
descriptor:
Lorg/springframework/stereotype/Component;
attributes:
value
↓
"userService"
这正是 Spring 注解元数据读取的重要基础。
9. FieldVisitor
字段对应:
visitField(...)
例如:
private String username;
ASM 可以得到:
access
private
name
username
descriptor
Ljava/lang/String;
返回的:
FieldVisitor
还可以继续读取:
字段注解
字段类型注解
字段属性
因此 Visitor 是分层的:
ClassVisitor
│
├─ AnnotationVisitor
│
├─ FieldVisitor
│ └─ AnnotationVisitor
│
└─ MethodVisitor
├─ AnnotationVisitor
└─ bytecode instructions
10. MethodVisitor 不仅能看方法,还能看指令
MethodVisitor 最重要的能力是访问方法内部的 JVM 指令。
例如:
public int add(int a, int b) {
return a + b;
}
编译之后核心字节码类似:
ILOAD 1
ILOAD 2
IADD
IRETURN
含义:
ILOAD 1
↓
把局部变量 1 压入操作数栈
ILOAD 2
↓
把局部变量 2 压入操作数栈
IADD
↓
弹出两个 int,相加,把结果压栈
IRETURN
↓
返回 int
ASM 可以通过:
visitVarInsn(...)
visitInsn(...)
visitMethodInsn(...)
visitFieldInsn(...)
visitJumpInsn(...)
观察这些指令。
例如:
@Override
public void visitInsn(int opcode) {
System.out.println(opcode);
}
因此:
MethodVisitor
已经进入真正的 JVM 字节码层。
11. Opcodes:JVM 指令与标志常量
ASM 中大量使用:
Opcodes
它主要定义三类东西。
第一类是 JVM 指令:
Opcodes.ILOAD
Opcodes.ALOAD
Opcodes.IADD
Opcodes.RETURN
Opcodes.IRETURN
Opcodes.GETFIELD
Opcodes.PUTFIELD
Opcodes.INVOKEVIRTUAL
Opcodes.INVOKESTATIC
Opcodes.NEW
第二类是访问标志:
Opcodes.ACC_PUBLIC
Opcodes.ACC_PRIVATE
Opcodes.ACC_PROTECTED
Opcodes.ACC_STATIC
Opcodes.ACC_FINAL
Opcodes.ACC_ABSTRACT
Opcodes.ACC_INTERFACE
例如:
public static final
本质上对应多个 bit flag:
ACC_PUBLIC
|
ACC_STATIC
|
ACC_FINAL
第三类是版本:
Opcodes.ASM9
Opcodes.V17
Opcodes.V21
注意:
Opcodes.ASM9
表示 ASM API 版本;
Opcodes.V21
表示 Java Class File 版本。
两者不是同一个概念。
12. Label:字节码中的位置
Java:
if (age > 18) {
work();
}
编译之后不会存在一个 JVM if 代码块。
底层更接近:
ILOAD age
IF_ICMPLE L1
INVOKEVIRTUAL work
L1:
RETURN
其中:
L1
就是一个字节码位置。
ASM 使用:
Label label = new Label();
表示这种位置。
然后:
visitJumpInsn(..., label);
最终:
visitLabel(label);
因此:
Label
≈
字节码中的符号化地址
它被大量用于:
if
goto
switch
循环
try-catch
行号
局部变量作用域
StackMapFrame
13. Handle 与 invokedynamic
Handle 用于描述 JVM 的 Method Handle。
它主要出现在:
invokedynamic
Lambda
动态语言支持
Bootstrap Method
例如:
Runnable r = () -> doSomething();
Java 编译器通常不会简单生成一个匿名内部类,而会利用:
invokedynamic
并关联:
LambdaMetafactory
ASM 使用:
Handle
描述对应的方法句柄信息:
owner
name
descriptor
invoke kind
这一部分已经属于 JVM 动态调用机制,阅读 Spring ASM 基础代码时可以暂时跳过。
14. ClassWriter:生成 .class
ClassReader:
byte[] → class 信息
而 ClassWriter:
class 信息 → byte[]
例如:
ClassWriter writer =
new ClassWriter(0);
writer.visit(
Opcodes.V21,
Opcodes.ACC_PUBLIC,
"com/example/Hello",
null,
"java/lang/Object",
null
);
然后定义方法:
MethodVisitor mv =
writer.visitMethod(
Opcodes.ACC_PUBLIC,
"hello",
"()V",
null,
null
);
mv.visitCode();
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(0, 1);
mv.visitEnd();
writer.visitEnd();
最终:
byte[] bytes =
writer.toByteArray();
这些 bytes 就可以构成一个真正的 JVM class。
15. 修改现有 class
ASM 更常见的字节码增强方式是:
原始 class
↓
ClassReader
↓
自定义 Visitor
↓
ClassWriter
↓
新 class
代码结构:
ClassReader reader =
new ClassReader(bytes);
ClassWriter writer =
new ClassWriter(
reader,
ClassWriter.COMPUTE_FRAMES
);
ClassVisitor visitor =
new ClassVisitor(
Opcodes.ASM9,
writer
) {
@Override
public MethodVisitor visitMethod(
int access,
String name,
String descriptor,
String signature,
String[] exceptions) {
MethodVisitor mv =
super.visitMethod(
access,
name,
descriptor,
signature,
exceptions
);
// 可以包装 MethodVisitor
return mv;
}
};
reader.accept(visitor, 0);
byte[] result =
writer.toByteArray();
这里体现了 ASM Visitor API 一个很重要的设计:
ClassReader
↓
Visitor A
↓
Visitor B
↓
Visitor C
↓
ClassWriter
每一层 Visitor 都可以:
观察
修改
删除
增加
转发
事件。
例如删除方法:
@Override
public MethodVisitor visitMethod(
int access,
String name,
String descriptor,
String signature,
String[] exceptions) {
if ("deleteMe".equals(name)) {
return null;
}
return super.visitMethod(
access,
name,
descriptor,
signature,
exceptions
);
}
逻辑是:
ClassReader
↓
发现 deleteMe()
↓
自定义 Visitor
↓
返回 null
↓
事件没有传递给 ClassWriter
↓
生成的新 class 中不存在该方法
所以 ASM 的 Visitor 同时也是一种非常自然的字节码过滤器链。
16. Writer 系列类
ASM 中存在:
ClassWriter
MethodWriter
FieldWriter
AnnotationWriter
ModuleWriter
RecordComponentWriter
可以统一理解为:
Visitor API 对应的字节码写入实现。
关系大致是:
ClassVisitor
↑
ClassWriter
MethodVisitor
↑
MethodWriter
FieldVisitor
↑
FieldWriter
AnnotationVisitor
↑
AnnotationWriter
ModuleVisitor
↑
ModuleWriter
RecordComponentVisitor
↑
RecordComponentWriter
例如开发者面对的是:
MethodVisitor.visitInsn(...)
而 ASM 内部最终需要将这个调用转换成真正的:
opcode byte
写进 Class File。
这个工作主要由:
MethodWriter
完成。
17. ByteVector
最终 .class 是:
byte[]
所以 ASM 内部需要不断写入:
u1
u2
u4
UTF-8
constant pool
method
attribute
bytecode
ASM 使用:
ByteVector
维护可动态扩容的字节缓冲区。
可以简单类比:
StringBuilder
↓
动态构造 String
ByteVector
↓
动态构造 byte[]
它属于 ASM 内部基础设施,一般不需要直接使用。
18. Symbol 与 SymbolTable
Class File 有一个非常重要的区域:
constant_pool
即常量池。
例如:
#1 Utf8
java/lang/Object
#2 Class
#1
#3 Utf8
<init>
#4 Utf8
()V
#5 NameAndType
#3:#4
ASM 在生成 class 时必须维护常量池。
如果同一个:
java/lang/String
被使用 100 次,不能简单生成 100 个完全相同的常量。
因此:
SymbolTable
负责:
常量查找
常量去重
constant pool index 分配
Bootstrap Method 管理
而:
Symbol
用于描述其中的符号。
关系可以理解为:
ClassWriter
↓
SymbolTable
↓
Symbol
↓
constant_pool
如果后面深入 ClassWriter 源码,这一组类非常重要。
19. Attribute
Class File 中大量信息都是通过 Attribute 表达的:
Code
Signature
SourceFile
LineNumberTable
LocalVariableTable
StackMapTable
RuntimeVisibleAnnotations
BootstrapMethods
ASM 对标准 Attribute 有专门支持。
Attribute 主要提供一个扩展机制,使 ASM 可以处理自定义或者额外的 Class File Attribute。
基础学习阶段知道它对应:
Class File Attribute
即可。
20. Frame、CurrentFrame 与 Edge
这一组是 ASM 中明显更复杂的部分。
它们主要服务于:
StackMapFrame
JVM 字节码验证
控制流分析
最大栈计算
JVM 方法执行依赖:
Local Variables
+
Operand Stack
例如某个字节码位置:
locals:
0 → User
1 → int
2 → String
stack:
0 → Long
这就是一个执行状态。
现代 Class File 中的:
StackMapTable
记录了关键位置上的类型状态,帮助 JVM verifier 验证字节码是否合法。
ASM 可以自动计算:
new ClassWriter(
ClassWriter.COMPUTE_FRAMES
);
这意味着 ASM 必须分析:
方法字节码
↓
划分 Basic Block
↓
建立控制流
↓
模拟 Operand Stack
↓
传播局部变量类型
↓
不同分支进行类型合并
↓
计算 Frame
↓
生成 StackMapTable
其中:
Frame
表示一个栈帧状态。
CurrentFrame
用于字节码模拟执行过程中的当前 Frame。
Edge
表示控制流图中基本块之间的边:
┌──── block B ────┐
│ ↓
block A block D
│ ↑
└──── block C ────┘
这里:
A → B
A → C
B → D
C → D
每一个连接关系都可以看成一个 Edge。
这一部分已经涉及编译原理和 JVM verifier,不建议作为 ASM 入门阅读重点。
21. Handler
Handler 对应 JVM 异常表。
Java:
try {
service.execute();
} catch (Exception e) {
handle(e);
}
在 JVM 中不存在真正的:
try 指令
catch 指令
而是通过 Exception Table 描述:
start end handler type
L0 L1 L2 java/lang/Exception
意思是:
L0 ~ L1 范围执行期间
如果出现 java/lang/Exception
跳转到 L2
ASM 内部使用 Handler 维护这种异常处理关系。
22. TypeReference 与 TypePath
普通注解:
@Deprecated
class User {
}
只需要知道:
注解属于 User
但是 Java 还支持 Type Annotation:
List<@NotNull String>
甚至:
Map<
String,
List<@Nullable User>
>
ASM 必须描述:
@Nullable 到底标在什么位置?
TypeReference 用于描述注解针对哪种类型位置。
TypePath 则继续描述复杂类型结构中的路径。
可以理解为:
Map<String, List<@Nullable User>>
│
└─ 第二个泛型参数
│
└─ List
│
└─ 第一个泛型参数
│
└─ User
TypePath 就负责表达这种“走到哪个类型节点”的路径。
23. ModuleVisitor 与 RecordComponentVisitor
ModuleVisitor 对应 Java 9 Module:
module com.example.app {
requires java.sql;
exports com.example.api;
}
ASM 可以访问:
requires
exports
opens
uses
provides
ModuleWriter 则负责生成对应信息。
RecordComponentVisitor 对应 Java Record:
record User(
String name,
int age
) {
}
可以访问 Record Component 的:
name
descriptor
signature
annotation
type annotation
RecordComponentWriter 负责写入。
24. ConstantDynamic
传统 Class File 常量池中存在:
Integer
Float
Long
Double
String
Class
MethodRef
...
现代 JVM 又引入:
CONSTANT_Dynamic
ASM 对应:
ConstantDynamic
它允许一个常量的实际值通过 Bootstrap Method 动态计算。
可以和 invokedynamic 对比:
invokedynamic
↓
动态确定调用点
CONSTANT_Dynamic
↓
动态确定常量值
属于 JVM 较高级的动态链接能力。
25. SpringAsmInfo
SpringAsmInfo 是 Spring 自己增加的适配类,不属于标准 ASM 核心概念。
Spring Framework 将 ASM 放在:
org.springframework.asm
命名空间中,并维护自己使用的 ASM API 版本。
因此可以把:
SpringAsmInfo
理解成 Spring 与内置 ASM 版本之间的一层版本信息定义。
26. ClassTooLargeException 与 MethodTooLargeException
这两个异常来源于 JVM Class File 格式自身的限制。
MethodTooLargeException 表示单个方法生成的字节码过大。
JVM 的 Code Attribute 对单个方法的代码长度存在限制:
code_length < 65536
因此一个方法的字节码最多约:
65535 bytes
超过之后 ASM 无法生成合法 Class File。
ClassTooLargeException 通常与 Class File 常量池容量限制有关。
因为:
constant_pool_count
使用 u2 表示,因此常量池存在约 65535 项的上限。
27. Spring 中 ASM 的实际位置
理解 ASM 后,再看 Spring 的使用链路会清晰很多。
Spring 做 classpath 扫描时,大致存在这样的关系:
classpath
↓
发现 .class Resource
↓
MetadataReader
↓
读取 Class Metadata
↓
ASM ClassReader
↓
解析 class
↓
读取:
类名
父类
接口
访问标志
注解
方法
方法注解
最终 Spring 上层得到的是:
ClassMetadata
AnnotationMetadata
MethodMetadata
而不是直接面对:
ClassReader
ClassVisitor
MethodVisitor
因此可以把整个分层理解成:
Spring 高层
ClassPathScanningCandidateComponentProvider
ConfigurationClassParser
↓
Spring 元数据抽象
MetadataReader
ClassMetadata
AnnotationMetadata
MethodMetadata
↓
ASM
ClassReader
ClassVisitor
MethodVisitor
AnnotationVisitor
↓
JVM
Class File
ASM 是 Spring 元数据体系和 JVM Class File 之间的底层桥梁。
28. ASM 与 Reflection 的区别
两者都可以获得类信息,但处于完全不同的阶段。
Java 类信息
┌─────────┴─────────┐
↓ ↓
ASM Reflection
↓ ↓
读取 .class 操作 Class<?>
↓ ↓
不要求类已经加载 类已经进入 JVM
↓ ↓
字节码层 Java 运行时层
例如 Spring 只想知道:
UserService 有没有 @Component?
ASM 非常适合。
而 Bean 已经创建后,需要:
method.invoke(bean, args);
则进入 Reflection 的工作范围。
因此:
ASM
解决的是:
这个
.class文件里面有什么?
而:
Reflection
解决的是:
JVM 中这个已经加载的 Java 类型是什么,以及如何操作它?
这是阅读 Spring 源码时非常重要的一条边界。
29. ASM 类的职责
按照阅读优先级,可以将这个包分为四层。
第一层是必须理解的核心 API:
ClassReader
ClassVisitor
ClassWriter
MethodVisitor
FieldVisitor
AnnotationVisitor
Opcodes
Type
Label
理解这些类之后,就已经可以看懂大部分 ASM 基础调用。
第二层是现代 JVM 结构描述:
Handle
ConstantDynamic
TypeReference
TypePath
ModuleVisitor
RecordComponentVisitor
遇到相关功能时再深入即可。
第三层是 Writer 内部实现:
MethodWriter
FieldWriter
AnnotationWriter
ModuleWriter
RecordComponentWriter
ByteVector
Symbol
SymbolTable
Attribute
Context
主要用于理解 ASM 如何真正生成 Class File。
第四层是字节码分析:
Frame
CurrentFrame
Edge
Handler
这部分需要 JVM:
Operand Stack
Local Variables
Basic Block
Control Flow Graph
StackMapTable
Verifier
等知识作为基础,不适合作为 ASM 的阅读入口。
30. 阅读顺序
如果目标是阅读 Spring Framework 源码,不建议从 org.springframework.asm 包开始逐类阅读。
更合适的路线是:
第一阶段:建立 JVM Class File 基础
Class File
descriptor
internal name
constant pool
opcode
operand stack
local variable
然后阅读:
Type
Opcodes
Label
理解 ASM 使用的基本语言。
接下来:
ClassReader
↓
ClassVisitor
↓
MethodVisitor
↓
AnnotationVisitor
重点理解:
ClassReader.accept(visitor)
如何驱动 Visitor。
再反向理解:
ClassWriter
↓
MethodWriter
↓
ByteVector
↓
SymbolTable
理解 ASM 如何重新构造 Class File。
最后有需要再进入:
Frame
CurrentFrame
Edge
Handler
研究:
CFG
Frame 计算
StackMapTable
字节码验证
如果主要目标是 Spring 源码,则掌握到:
ClassReader
Visitor
Type
Opcodes
Label
之后就应该回到 Spring 上层,沿:
MetadataReader
↓
AnnotationMetadata
↓
组件扫描
↓
配置类解析
继续阅读。
ASM 对 Spring 来说是底层实现手段,而不是 Spring 容器的核心业务模型。
31. 最终心智模型
看到:
org.springframework.asm
首先想到:
直接操作 JVM Class File
看到:
ClassReader
想到:
byte[] → 解析 class
看到:
ClassVisitor
MethodVisitor
AnnotationVisitor
想到:
ClassReader 解析过程中产生的访问事件
看到:
ClassWriter
想到:
访问事件 → byte[]
看到:
Type
想到:
JVM descriptor
看到:
Opcodes
想到:
JVM 指令和访问标志
看到:
Label
想到:
字节码位置 / 跳转目标
看到:
SymbolTable
想到:
Class File constant pool
看到:
Frame
Edge
想到:
操作数栈 + 局部变量 + 控制流分析
而从 Spring 整体架构来看:
Spring
│
│ 需要知道某个类有什么注解和结构
↓
MetadataReader
↓
ASM
↓
ClassReader
↓
直接解析 .class
↓
不必先得到 Class<?>
这就是 org.springframework.asm 在 Spring Framework 中最核心的价值。