Asm 访问者模式 和 ClassWriter 数据结构

ASM Core API 是基于访问者模式设计的,在性能上非常有保证。
Apptalking

一般基于访问者模式,都是从文件开始进行处理,按顺序处理到文件末端,中间触发相应的事件。以上述的 AMS 为例,ClassReader 负责读取,触发中间的Adapter,最终输入到 ClassWriter中。

ClassReader 只能从Class文件开始读取到文件结束终止,按顺序处理,不能做调整。但是 ClassWriter 并没有把结果存在一个大的数据结构中,所以 Adapter 在使用起来还是存在一定的灵活性的。

ClassWriter 中 Method 存储结构

ClassWriter中的存储结构的方式是一致的,以下以 Method 为例进行说明。

1
MethodWriter firstMethod;

ClassWriter 中 firstMethod 存储了这个类中所有的 MethodWriter 内容,其是链式存储方式,MethodWriter 中会存储下一个方法的内容。

1
2
3
4
/**
* The class writer to which this method must be added.
*/
final ClassWriter cw;

MethodWriter 中的 cw 存储 下一个方法的引用。

在ClassWriter toByteArray() 生成字节的方法的中,会遍历所有的 MethodWriter 生成相应的 字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public byte[] toByteArray() {
.......
// 计算方法的数量和总的体积
MethodWriter mb = firstMethod;
while (mb != null) {
++nbMethods;
size += mb.getSize();
mb = (MethodWriter) mb.mv;
}
........
// 输出方法字节
mb = firstMethod;
while (mb != null) {
mb.put(out);
mb = (MethodWriter) mb.mv;
}
......

灵活使用方式

因为ClassWriter 中 存储了 每一个方法的 MethodWiter , 所以在调用toByteArray() 之前,如果 Adapter 持有 MethodVisitor的引用,就可以一致在此方法中追加内容。 ClassReader 读取此方法之后也可以。

使用示例

StaticInitMerger 负责合并静态方法块,其原理就是在第一次读取到 静态方法块,创建
private MethodVisitor clinit; , 以后再读到静态方法块的时候在次方法中追加内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public class StaticInitMerger extends ClassVisitor {

private String name;

private MethodVisitor clinit;

private final String prefix;

private int counter;

public StaticInitMerger(final String prefix, final ClassVisitor cv) {
this(Opcodes.ASM5, prefix, cv);
}

protected StaticInitMerger(final int api, final String prefix,
final ClassVisitor cv) {
super(api, cv);
this.prefix = prefix;
}

@Override
public void visit(final int version, final int access, final String name,
final String signature, final String superName,
final String[] interfaces) {
cv.visit(version, access, name, signature, superName, interfaces);
this.name = name;
}

@Override
public MethodVisitor visitMethod(final int access, final String name,
final String desc, final String signature, final String[] exceptions) {
MethodVisitor mv;
if ("<clinit>".equals(name)) {
int a = Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC;
String n = prefix + counter++;
mv = cv.visitMethod(a, n, desc, signature, exceptions);
// 创建方法,并且持有引用
if (clinit == null) {
clinit = cv.visitMethod(a, name, desc, null, null);
}
// 追加方法的内容
clinit.visitMethodInsn(Opcodes.INVOKESTATIC, this.name, n, desc, false);
} else {
mv = cv.visitMethod(access, name, desc, signature, exceptions);
}
return mv;
}

@Override
public void visitEnd() {
// 结束方法
if (clinit != null) {
clinit.visitInsn(Opcodes.RETURN);
clinit.visitMaxs(0, 0);
}
cv.visitEnd();
}
}