org.springframework.asm is a bytecode processing package used internally by Spring Framework. It originates from ASM, with Spring repackaging ASM into its own namespace for directly reading, analyzing, and generating JVM .class files.
In Spring's source code, one extremely important use of ASM is:
Directly reading class, method, annotation, and other metadata from
.classfiles without loading the Java class.
Understanding this package doesn't require researching all implementation classes from the start. Just establish this main line:
Reading:
.class
↓
ClassReader
↓
ClassVisitor
├─ FieldVisitor
├─ MethodVisitor
└─ AnnotationVisitor
Writing:
ClassVisitor / MethodVisitor
↓
ClassWriter
↓
byte[]
↓
.class
1. Why Spring Needs ASM
Assume a project contains:
@Component
public class UserService {
@Autowired
private UserRepository repository;
public User findById(Long id) {
return repository.findById(id);
}
}
During Spring's startup process when scanning the classpath, it needs to know:
What is this class called?
Is it an interface?
Is it an abstract class?
What annotations does it have?
Does it have @Component?
What methods does it have?
What annotations do the methods have?
One approach is to use Java reflection:
Class<?> clazz = Class.forName(
"com.example.UserService"
);
Component component =
clazz.getAnnotation(Component.class);
But this requires first having the JVM load the class.
When Spring scans the classpath, it may face tens of thousands of .class files. Many of these classes will never become Beans, so there's no need to load all candidate classes into the JVM just to read a small amount of metadata.
ASM provides another path:
UserService.class
↓
Read binary Class File
↓
Parse class metadata
↓
Discover @Component
The entire process doesn't require first obtaining:
Class<UserService>
Therefore, Spring can separate:
Metadata reading
from:
Java class loading
This is the most important point for understanding Spring ASM.
2. ASM Operates on Java Source Code? No - ASM Operates on JVM Class Files
ASM operates on JVM Class Files.
Java source code:
public class UserService {
public int add(int a, int b) {
return a + b;
}
}
After javac compilation:
UserService.java
↓
javac
↓
UserService.class
The .class file doesn't simply store Java source code; it's a binary structure defined by the JVM, roughly containing:
ClassFile
├─ magic
├─ version
├─ constant_pool
├─ access_flags
├─ this_class
├─ super_class
├─ interfaces
├─ fields
├─ methods
└─ attributes
ASM works around these structures.
Therefore, you'll encounter many concepts in ASM that look different from Java programming habits:
descriptor
internal name
constant pool
opcode
label
frame
attribute
These concepts actually come from JVM Class Files, not invented by ASM itself.
3. ASM's Core Design: Reader + Visitor + Writer
ASM's most core design can be summarized as:
Reader
↓
Visitor
↓
Writer
Where:
ClassReader
is responsible for reading .class.
ClassVisitor
MethodVisitor
FieldVisitor
AnnotationVisitor
are responsible for visiting information at different levels within a class.
ClassWriter
MethodWriter
FieldWriter
AnnotationWriter
are responsible for regenerating the class.
This API is very similar to SAX for parsing XML.
It typically doesn't require first converting the entire class into a huge object tree before processing. Instead, it continuously generates visit events during parsing:
Start visiting class
↓
visit(...)
Discover annotation
↓
visitAnnotation(...)
Discover field
↓
visitField(...)
Discover method
↓
visitMethod(...)
Discover bytecode instruction
↓
visitInsn(...)
Visit ends
↓
visitEnd()
Therefore, ASM's core API is perfect for:
Quick scanning
Bytecode analysis
Bytecode transformation
Java Agent
Framework metadata reading
4. ClassReader: Reading .class
ClassReader is the main entry point for reading with ASM.
For example:
ClassReader reader =
new ClassReader("com.example.UserService");
Then:
reader.accept(visitor, 0);
The entire process can be understood as:
ClassReader
│
│ Read class binary structure
↓
ClassVisitor.visit(...)
↓
ClassVisitor.visitAnnotation(...)
↓
ClassVisitor.visitField(...)
↓
ClassVisitor.visitMethod(...)
↓
ClassVisitor.visitEnd()
Note:
reader.accept(...)
is not simply "handing data to the Visitor."
More accurately:
ClassReaderparses the.classwhile actively calling the corresponding Visitor methods.
This is the classic Visitor pattern.
5. ClassVisitor: Visiting the Entire Class
ClassVisitor is responsible for class-level information.
One of the most important methods:
public void visit(
int version,
int access,
String name,
String signature,
String superName,
String[] interfaces)
For example:
public class UserService
extends BaseService
implements UserApi {
}
ASM can obtain:
name
com/example/UserService
superName
com/example/BaseService
interfaces
com/example/UserApi
Example:
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
);
Here it's easy to encounter the first important concept of ASM:
Internal Name
In Java:
java.lang.String
In ASM / JVM, it's often represented as:
java/lang/String
That is:
Java Class Name
java.lang.String
↓
Internal Name
java/lang/String
6. MethodVisitor: Visiting Methods
When ClassReader parses a method, it calls:
visitMethod(...)
For example:
@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);
}
Assuming a Java method:
public User findById(long id)
The descriptor ASM sees is:
(J)Lcom/example/User;
This involves JVM type descriptors.
7. Type and Descriptor
JVM doesn't directly use:
int
String
User
long[]
to describe types. Instead, it has its own descriptor format.
Common types:
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;
For a method:
User find(String name, long id)
The descriptor:
(Ljava/lang/String;J)Lcom/example/User;
The structure is:
(parameter types...)return type
Therefore:
(Ljava/lang/String;J)Lcom/example/User;
│ │ └───────────────── User
│ └──────────────────── long
└────────────────────────────────────── String
ASM provides Type to help handle descriptors:
Type[] args =
Type.getArgumentTypes(descriptor);
Type returnType =
Type.getReturnType(descriptor);
Therefore, when reading ASM code, you must distinguish these three concepts:
Java Name
java.lang.String
Internal Name
java/lang/String
Descriptor
Ljava/lang/String;
8. AnnotationVisitor: Reading Annotations
Assuming:
@Component("userService")
public class UserService {
}
When ClassReader discovers an annotation, it calls:
visitAnnotation(...)
For example:
@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
);
}
};
}
You might get:
Lorg/springframework/stereotype/Component;
value = userService
Therefore:
@Component("userService")
In ASM's eyes, roughly becomes:
Annotation
descriptor:
Lorg/springframework/stereotype/Component;
attributes:
value
↓
"userService"
This is exactly the important foundation for Spring's annotation metadata reading.
9. FieldVisitor
Fields correspond to:
visitField(...)
For example:
private String username;
ASM can obtain:
access
private
name
username
descriptor
Ljava/lang/String;
The returned:
FieldVisitor
can also continue reading:
field annotations
field type annotations
field attributes
Therefore, Visitors are hierarchical:
ClassVisitor
│
├─ AnnotationVisitor
│
├─ FieldVisitor
│ └─ AnnotationVisitor
│
└─ MethodVisitor
├─ AnnotationVisitor
└─ bytecode instructions
10. MethodVisitor Can Not Only See Methods, But Also Instructions
MethodVisitor's most important capability is visiting JVM instructions inside methods.
For example:
public int add(int a, int b) {
return a + b;
}
After compilation, the core bytecode is similar to:
ILOAD 1
ILOAD 2
IADD
IRETURN
Meaning:
ILOAD 1
↓
Push local variable 1 onto operand stack
ILOAD 2
↓
Push local variable 2 onto operand stack
IADD
↓
Pop two ints, add them, push result onto stack
IRETURN
↓
Return int
ASM can observe these instructions through:
visitVarInsn(...)
visitInsn(...)
visitMethodInsn(...)
visitFieldInsn(...)
visitJumpInsn(...)
For example:
@Override
public void visitInsn(int opcode) {
System.out.println(opcode);
}
Therefore:
MethodVisitor
has entered the actual JVM bytecode layer.
11. Opcodes: JVM Instructions and Flag Constants
ASM extensively uses:
Opcodes
It mainly defines three types of things.
The first type is JVM instructions:
Opcodes.ILOAD
Opcodes.ALOAD
Opcodes.IADD
Opcodes.RETURN
Opcodes.IRETURN
Opcodes.GETFIELD
Opcodes.PUTFIELD
Opcodes.INVOKEVIRTUAL
Opcodes.INVOKESTATIC
Opcodes.NEW
The second type is access flags:
Opcodes.ACC_PUBLIC
Opcodes.ACC_PRIVATE
Opcodes.ACC_PROTECTED
Opcodes.ACC_STATIC
Opcodes.ACC_FINAL
Opcodes.ACC_ABSTRACT
Opcodes.ACC_INTERFACE
For example:
public static final
essentially corresponds to multiple bit flags:
ACC_PUBLIC
|
ACC_STATIC
|
ACC_FINAL
The third type is versions:
Opcodes.ASM9
Opcodes.V17
Opcodes.V21
Note:
Opcodes.ASM9
represents the ASM API version;
Opcodes.V21
represents the Java Class File version.
These are not the same concept.
12. Label: Positions in Bytecode
Java:
if (age > 18) {
work();
}
After compilation, there's no actual JVM if code block.
At the底层, it's more like:
ILOAD age
IF_ICMPLE L1
INVOKEVIRTUAL work
L1:
RETURN
Where:
L1
is a bytecode position.
ASM uses:
Label label = new Label();
to represent such positions.
Then:
visitJumpInsn(..., label);
Finally:
visitLabel(label);
Therefore:
Label
≈
Symbolic address in bytecode
It's heavily used for:
if
goto
switch
loops
try-catch
line numbers
local variable scope
StackMapFrame
13. Handle and invokedynamic
Handle is used to describe JVM Method Handles.
It mainly appears in:
invokedynamic
Lambda
Dynamic language support
Bootstrap Method
For example:
Runnable r = () -> doSomething();
The Java compiler usually doesn't simply generate an anonymous inner class, but uses:
invokedynamic
and associates:
LambdaMetafactory
ASM uses:
Handle
to describe the corresponding method handle information:
owner
name
descriptor
invoke kind
This part already involves JVM dynamic invocation mechanisms and can be skipped when reading basic Spring ASM code.
14. ClassWriter: Generating .class
ClassReader:
byte[] → class information
While ClassWriter:
class information → byte[]
For example:
ClassWriter writer =
new ClassWriter(0);
writer.visit(
Opcodes.V21,
Opcodes.ACC_PUBLIC,
"com/example/Hello",
null,
"java/lang/Object",
null
);
Then define a method:
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();
Finally:
byte[] bytes =
writer.toByteArray();
These bytes can form a real JVM class.
15. Modifying Existing Classes
A more common way to enhance bytecode with ASM is:
Original class
↓
ClassReader
↓
Custom Visitor
↓
ClassWriter
↓
New class
Code structure:
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
);
// Can wrap MethodVisitor
return mv;
}
};
reader.accept(visitor, 0);
byte[] result =
writer.toByteArray();
This reflects a very important design of the ASM Visitor API:
ClassReader
↓
Visitor A
↓
Visitor B
↓
Visitor C
↓
ClassWriter
Each Visitor layer can:
Observe
Modify
Delete
Add
Forward
events.
For example, deleting a method:
@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
);
}
The logic is:
ClassReader
↓
Discover deleteMe()
↓
Custom Visitor
↓
Return null
↓
Event not passed to ClassWriter
↓
Method doesn't exist in generated new class
So ASM's Visitor is also a very natural bytecode filter chain.
16. The Writer Series Classes
In ASM, there are:
ClassWriter
MethodWriter
FieldWriter
AnnotationWriter
ModuleWriter
RecordComponentWriter
These can be uniformly understood as:
The bytecode writing implementations corresponding to the Visitor API.
The relationships are roughly:
ClassVisitor
↑
ClassWriter
MethodVisitor
↑
MethodWriter
FieldVisitor
↑
FieldWriter
AnnotationVisitor
↑
AnnotationWriter
ModuleVisitor
↑
ModuleWriter
RecordComponentVisitor
↑
RecordComponentWriter
For example, developers face:
MethodVisitor.visitInsn(...)
But internally, ASM eventually needs to convert this call into actual:
opcode byte
written into the Class File.
This work is mainly done by:
MethodWriter
17. ByteVector
The final .class is:
byte[]
So internally, ASM needs to continuously write:
u1
u2
u4
u8
UTF-8
constant pool
method
attribute
bytecode
ASM uses:
ByteVector
to maintain a dynamically expandable byte buffer.
It can be simply compared to:
StringBuilder
↓
Dynamically construct String
ByteVector
↓
Dynamically construct byte[]
It's part of ASM's internal infrastructure and generally doesn't need to be used directly.
18. Symbol and SymbolTable
Class Files have a very important region:
constant_pool
That is, the constant pool.
For example:
#1 Utf8
java/lang/Object
#2 Class
#1
#3 Utf8
<init>
#4 Utf8
()V
#5 NameAndType
#3:#4
ASM must maintain the constant pool when generating classes.
If the same:
java/lang/String
is used 100 times, you can't simply generate 100 identical constants.
Therefore:
SymbolTable
is responsible for:
Constant lookup
Constant deduplication
constant pool index allocation
Bootstrap Method management
And:
Symbol
is used to describe symbols within it.
The relationship can be understood as:
ClassWriter
↓
SymbolTable
↓
Symbol
↓
constant_pool
If you dive into ClassWriter source code later, this group of classes is very important.
19. Attribute
A lot of information in Class Files is expressed through Attributes:
Code
Signature
SourceFile
LineNumberTable
LocalVariableTable
StackMapTable
RuntimeVisibleAnnotations
BootstrapMethods
ASM has special support for standard Attributes.
Attribute mainly provides an extension mechanism allowing ASM to handle custom or additional Class File Attributes.
At the basic learning stage, it's enough to know it corresponds to:
Class File Attribute
20. Frame, CurrentFrame, and Edge
This group is clearly a more complex part of ASM.
They mainly serve:
StackMapFrame
JVM bytecode verification
Control flow analysis
Maximum stack calculation
JVM method execution depends on:
Local Variables
+
Operand Stack
For example, at a certain bytecode position:
locals:
0 → User
1 → int
2 → String
stack:
0 → Long
This is an execution state.
In modern Class Files:
StackMapTable
records type states at key positions, helping the JVM verifier verify whether bytecode is legal.
ASM can automatically calculate:
new ClassWriter(
ClassWriter.COMPUTE_FRAMES
);
This means ASM must analyze:
Method bytecode
↓
Divide into Basic Blocks
↓
Build control flow
↓
Simulate Operand Stack
↓
Propagate local variable types
↓
Type merging across branches
↓
Calculate Frame
↓
Generate StackMapTable
Where:
Frame
represents a stack frame state.
CurrentFrame
is used for the current Frame during bytecode simulation execution.
Edge
represents edges between basic blocks in the control flow graph:
┌──── block B ────┐
│ ↓
block A block D
│ ↑
└──── block C ────┘
Here:
A → B
A → C
B → D
C → D
Each connection can be viewed as an Edge.
This part already involves compilation principles and JVM verifier, and is not recommended as a focus for ASM beginner reading.
21. Handler
Handler corresponds to the JVM exception table.
Java:
try {
service.execute();
} catch (Exception e) {
handle(e);
}
In the JVM, there are no actual:
try instruction
catch instruction
Instead, it's described through the Exception Table:
start end handler type
L0 L1 L2 java/lang/Exception
Meaning:
During execution in range L0 ~ L1
If java/lang/Exception occurs
Jump to L2
Internally, ASM uses Handler to maintain such exception handling relationships.
22. TypeReference and TypePath
Regular annotations:
@Deprecated
class User {
}
Only need to know:
Annotation belongs to User
But Java also supports Type Annotations:
List<@NotNull String>
Even:
Map<
String,
List<@Nullable User>
>
ASM must describe:
Where exactly is @Nullable marked?
TypeReference is used to describe what type position an annotation targets.
TypePath continues to describe the path within complex type structures.
It can be understood as:
Map<String, List<@Nullable User>>
│
└─ Second generic parameter
│
└─ List
│
└─ First generic parameter
│
└─ User
TypePath is responsible for expressing this "path to which type node".
23. ModuleVisitor and RecordComponentVisitor
ModuleVisitor corresponds to Java 9 Modules:
module com.example.app {
requires java.sql;
exports com.example.api;
}
ASM can visit:
requires
exports
opens
uses
provides
ModuleWriter is responsible for generating the corresponding information.
RecordComponentVisitor corresponds to Java Records:
record User(
String name,
int age
) {
}
Can visit Record Component's:
name
descriptor
signature
annotation
type annotation
RecordComponentWriter is responsible for writing.
24. ConstantDynamic
Traditional Class File constant pools contain:
Integer
Float
Long
Double
String
Class
MethodRef
...
Modern JVMs introduce:
CONSTANT_Dynamic
ASM corresponds to:
ConstantDynamic
It allows a constant's actual value to be dynamically calculated through Bootstrap Methods.
It can be compared to invokedynamic:
invokedynamic
↓
Dynamically determine call site
CONSTANT_Dynamic
↓
Dynamically determine constant value
This belongs to JVM's more advanced dynamic linking capabilities.
25. SpringAsmInfo
SpringAsmInfo is an adaptation class added by Spring itself and doesn't belong to standard ASM core concepts.
Spring Framework places ASM in the:
org.springframework.asm
namespace and maintains its own ASM API version.
Therefore, you can understand:
SpringAsmInfo
as a layer of version information definition between Spring and the built-in ASM version.
26. ClassTooLargeException and MethodTooLargeException
These two exceptions come from limitations inherent in the JVM Class File format itself.
MethodTooLargeException indicates that the bytecode generated for a single method is too large.
JVM's Code Attribute has a limit on code length for a single method:
code_length < 65536
Therefore, a method's bytecode can be at most approximately:
65535 bytes
If exceeded, ASM cannot generate a valid Class File.
ClassTooLargeException is usually related to the Class File constant pool capacity limit.
Because:
constant_pool_count
is represented using u2, there's an upper limit of approximately 65535 items in the constant pool.
27. ASM's Actual Position in Spring
After understanding ASM, the Spring usage chain becomes much clearer.
When Spring does classpath scanning, there's roughly this relationship:
classpath
↓
Discover .class Resource
↓
MetadataReader
↓
Read Class Metadata
↓
ASM ClassReader
↓
Parse class
↓
Read:
class name
parent class
interfaces
access flags
annotations
methods
method annotations
Ultimately, Spring's upper layer obtains:
ClassMetadata
AnnotationMetadata
MethodMetadata
Instead of directly facing:
ClassReader
ClassVisitor
MethodVisitor
Therefore, the entire layering can be understood as:
Spring High Level
ClassPathScanningCandidateComponentProvider
ConfigurationClassParser
↓
Spring Metadata Abstraction
MetadataReader
ClassMetadata
AnnotationMetadata
MethodMetadata
↓
ASM
ClassReader
ClassVisitor
MethodVisitor
AnnotationVisitor
↓
JVM
Class File
ASM is the底层 bridge between Spring's metadata system and JVM Class Files.
28. The Difference Between ASM and Reflection
Both can obtain class information, but they're at completely different stages.
Java Class Information
┌─────────┴─────────┐
↓ ↓
ASM Reflection
↓ ↓
Read .class Operate on Class<?>
↓ ↓
No requirement for Class already in JVM
class to be loaded ↓
↓ ↓
Bytecode layer Java runtime layer
For example, if Spring just wants to know:
Does UserService have @Component?
ASM is perfect.
But after a Bean has been created and you need:
method.invoke(bean, args);
that's in Reflection's domain.
Therefore:
ASM
solves:
What's in this
.classfile?
While:
Reflection
solves:
What is this already-loaded Java type in the JVM, and how to operate on it?
This is a very important boundary when reading Spring source code.
29. Responsibilities of ASM Classes
By reading priority, this package can be divided into four layers.
The first layer is the core API you must understand:
ClassReader
ClassVisitor
ClassWriter
MethodVisitor
FieldVisitor
AnnotationVisitor
Opcodes
Type
Label
After understanding these classes, you can already understand most basic ASM calls.
The second layer is modern JVM structure descriptions:
Handle
ConstantDynamic
TypeReference
TypePath
ModuleVisitor
RecordComponentVisitor
Dive deeper when you encounter related features.
The third layer is Writer internal implementations:
MethodWriter
FieldWriter
AnnotationWriter
ModuleWriter
RecordComponentWriter
ByteVector
Symbol
SymbolTable
Attribute
Context
Mainly used to understand how ASM truly generates Class Files.
The fourth layer is bytecode analysis:
Frame
CurrentFrame
Edge
Handler
This part requires knowledge of:
JVM:
Operand Stack
Local Variables
Basic Block
Control Flow Graph
StackMapTable
Verifier
as a foundation, and is not suitable as an entry point for reading ASM.
30. Reading Order
If the goal is to read Spring Framework source code, it's not recommended to start by reading classes in the org.springframework.asm package one by one.
A more suitable route is:
Phase 1: Establish JVM Class File basics
Class File
descriptor
internal name
constant pool
opcode
operand stack
local variable
Then read:
Type
Opcodes
Label
Understand the basic language ASM uses.
Next:
ClassReader
↓
ClassVisitor
↓
MethodVisitor
↓
AnnotationVisitor
Focus on understanding:
ClassReader.accept(visitor)
how it drives the Visitor.
Then reverse-understand:
ClassWriter
↓
MethodWriter
↓
ByteVector
↓
SymbolTable
Understand how ASM reconstructs Class Files.
Finally, if needed, go into:
Frame
CurrentFrame
Edge
Handler
Study:
CFG
Frame calculation
StackMapTable
Bytecode verification
If the main goal is Spring source code, after mastering:
ClassReader
Visitor
Type
Opcodes
Label
you should return to Spring's upper layer and continue reading along:
MetadataReader
↓
AnnotationMetadata
↓
Component scanning
↓
Configuration class parsing
ASM is a底层 implementation means for Spring, not the core business model of the Spring container.
31. Final Mental Model
When you see:
org.springframework.asm
First think:
Directly operate on JVM Class Files
When you see:
ClassReader
Think:
byte[] → parse class
When you see:
ClassVisitor
MethodVisitor
AnnotationVisitor
Think:
Visit events generated during ClassReader parsing
When you see:
ClassWriter
Think:
Visit events → byte[]
When you see:
Type
Think:
JVM descriptor
When you see:
Opcodes
Think:
JVM instructions and access flags
When you see:
Label
Think:
Bytecode position / jump target
When you see:
SymbolTable
Think:
Class File constant pool
When you see:
Frame
Edge
Think:
Operand stack + local variables + control flow analysis
And from the overall Spring architecture perspective:
Spring
│
│ Needs to know what annotations and structure a class has
↓
MetadataReader
↓
ASM
↓
ClassReader
↓
Parse .class directly
↓
Don't need to obtain Class<?> first
This is the core value of org.springframework.asm in Spring Framework.