Asm 之 visitOuterClass 使用

Asm ClassVisitor 中 visitOuterClass 方法处理匿名类的 Enclosing Method 信息,简单从方法名称上看,可能认为是处理外部类的内容,但是一般的外部类的场景是不会调用到此方法的。

visitOuterClass API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Visits the enclosing class of the class. This method must be called only
* if the class has an enclosing class.
*
* @param owner
* internal name of the enclosing class of the class.
* @param name
* the name of the method that contains the class, or
* <tt>null</tt> if the class is not enclosed in a method of its
* enclosing class.
* @param desc
* the descriptor of the method that contains the class, or
* <tt>null</tt> if the class is not enclosed in a method of its
* enclosing class.
*/
public void visitOuterClass(String owner, String name, String desc)

owner 为创建匿名类的类,当然其也是一个enclosing class类型的类。
name 创建匿名类的方法。
desc 创建匿名类的方法描述信息。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.example.outerclass;

public class OuterClass {

public void test(){
new Runnable() {
@Override
public void run() {
System.out.println("test");
}
};
}
}

以上的代码的字节码内容

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
// Compiled from OuterClass.java (version 1.8 : 52.0, super bit)
class com.example.outerclass.OuterClass$1 implements java.lang.Runnable {

// Field descriptor #8 Lcom/example/outerclass/OuterClass;
final synthetic com.example.outerclass.OuterClass this$0;

// Method descriptor #10 (Lcom/example/outerclass/OuterClass;)V
// Stack: 2, Locals: 2
OuterClass$1(com.example.outerclass.OuterClass arg0);
0 aload_0 [this]
1 aload_1 [arg0]
2 putfield com.example.outerclass.OuterClass$1.this$0 : com.example.outerclass.OuterClass [12]
5 aload_0 [this]
6 invokespecial java.lang.Object() [14]
9 return
Line numbers:
[pc: 0, line: 1]
[pc: 5, line: 6]
Local variable table:
[pc: 0, pc: 10] local: this index: 0 type: new com.example.outerclass.OuterClass(){}

// Method descriptor #16 ()V
// Stack: 2, Locals: 1
public void run();
0 getstatic java.lang.System.out : java.io.PrintStream [22]
3 ldc <String "test"> [28]
5 invokevirtual java.io.PrintStream.println(java.lang.String) : void [30]
8 return
Line numbers:
[pc: 0, line: 9]
[pc: 8, line: 10]
Local variable table:
[pc: 0, pc: 9] local: this index: 0 type: new com.example.outerclass.OuterClass(){}

Inner classes:
[inner class info: #1 com/example/outerclass/OuterClass$1, outer class info: #0
inner name: #0, accessflags: 0 default]
Enclosing Method: #39 #41 com/example/outerclass/OuterClass.test()V
}

注意到 Enclosing Method: #39 #41 com/example/outerclass/OuterClass.test()V ,这些内容是visitOuterClass 要读取的。

测试代码

匿名类 Java 编译器会生成一个单独的Class,其命名规则为 XXX$S1 , XXX为 外部类的Class名称,S1 中的数字随着创建匿名类的个数递增。

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
package com.example.outerclass;

import java.io.IOException;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

public class OutterTest {

public static class MyClassVisitor extends ClassVisitor {
public MyClassVisitor() {
super(Opcodes.ASM5);
}

@Override
public void visitOuterClass(String owner, String name, String desc) {
super.visitOuterClass(owner, name, desc);
System.out.println("owner=" + owner +",name=" + name + ",desc=" + desc );
}


}



public static void main(String[] args) throws IOException {
ClassReader classReader = new ClassReader("com.example.outerclass.OuterClass$1");
//ClassReader classReader = new ClassReader("com.example.outerclass.OuterClass$InnerClass");
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
classReader.accept(new MyClassVisitor(),ClassReader.EXPAND_FRAMES);
}
}

读取的结果信息为:

1
owner=com/example/outerclass/OuterClass,name=test,desc=()V

源码分析

分析 ClassReader 中的读取class内容的方法,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 } else if ("EnclosingMethod".equals(attrName)) {
enclosingOwner = readClass(u + 8, c);
int item = readUnsignedShort(u + 10);
if (item != 0) {
enclosingName = readUTF8(items[item], c);
enclosingDesc = readUTF8(items[item] + 2, c);
}
}

......
......
......

if (enclosingOwner != null) {
classVisitor.visitOuterClass(enclosingOwner, enclosingName, enclosingDesc);
}