BaiFan
文章目录
  1. 1. 2.3 工具类
    1. 1.1. 2.3.1 类型
    2. 1.2. 2.3.2. TraceClassVisitor
    3. 1.3. 2.3.3. CheckClassAdapter
    4. 1.4. 2.3.4 ASMifier

ASM-类-接口和组件

2.3 工具类

除了ClassVisitor类,以及相关的ClassReaderClassWriter等组件,
ASM在org.objectweb.asm.util还提供了一些工具类,对开发一个Class生成器和适配器非常有用,但在程序运行中不是必需的。
ASM同样也提供了一个在程序运行时,处理内部名、类型描述符和方法描述符的工具类。
所有工具都会在下面进行介绍。
图表 2.8 一个复杂的调用链

2.3.1 类型

正如前面章节介绍的,ASM的API展示Java类型,像编译后的class中一样,即使用内部名、类型描述符。
使用源码中的展示方式,可以使代码更加易读。
但需要在ClassReaderClassWriter中系统的转换,这样会降低性能。
这就是为什么ASM不透明的将内部名和类型描述符转换成源码中的格式。
但是ASM提供了一个Type类,方便在需要的时候进行手动转换。

一个Type对象代表一种Java类型,可以由类型描述符或者Class对象创建。
Type类中也包含了一些表示基本数据类型的静态变量。
例如Type.INT_TYPE代表了int类型的Type对象。

getInternalName方法会返回一个Type对象的內部名。
例如,Type.getType(String.class).getInternalName()会返回String类的内部名,即“java/lang/String”
这个方法只能是类(class)或者接口(interface)类型调用。

getDescriptor方法会返回一个Type对象的类型描述符。
例如,相比于在代码中使用“Ljava/lang/String;”,可以使用“Type.getType(String.class).getDescriptor()”代替。
再者,相比于使用“I”,可以使用“Type.INT_TYPE.getDescriptor()”

一个Type对象也可以表示一个方法类型。这些对象可以由方法描述符或者一个方法对象创建。
getDescriptor方法会返回该类型相对应的方法描述符。
除此以外,getArgumentTypes方法和getReturnType方法可以返回Type对象对应的参数类型和方法返回值类型。
例如,Type.getArgumentTypes(“(I)V”) 会返回包含一个Type.INT_TYPE元素的数组。
同样的,调用Type.getReturnType(“(I)V”)会返回一个Type.VOID_TYPE对象。

2.3.2. TraceClassVisitor

为了检查生成或者转换后的class是否是你所期望的,通过byte数组不会有所帮助,因为byte数组可读性太差。
使用文本表述会更容易阅读。这就是TraceClassVisitor所提供的功能。
顾名思义,这个类继承了ClassVisitor,为访问的class创建一个易读的文本描述。
这样,为了得到实际生成类可读性的描述,可以使用TraceClassVisitor代替ClassWriter
或者,更胜一筹的是同时使用这两个类。
TraceClassVisitor除了默认的行为外,还可以委托所有调用它的方法到其他visitor上面,例如到一个ClassWriter

1
2
3
4
5
6
ClassWriter cw = new ClassWriter(0);
TraceClassVisitor cv = new TraceClassVisitor(cw, printWriter);
cv.visit(...);
...
cv.visitEnd();
byte b[] = cw.toByteArray();

上面的代码创建了一个TraceClassVisitor代理所有对它的调用到cw上面,并且将所有的调用都以文本的方式输出到printWriter中。
例如,在2.2.3章中使用TraceClassVisitor,将会得到如下的输出:

1
2
3
4
5
6
7
8
9
10
11
12
// class version 49.0 (49)
// access flags 1537
public abstract interface pkg/Comparable implements pkg/Mesurable {
// access flags 25
public final static I LESS=-1
// access flags 25
public final static I EQUAL=0
// access flags 25
public final static I GREATER=1
// access flags 1025
public abstract compareTo(Ljava/lang/Object;)I
}

需要注意的是,为了跟踪链路中的情况,你可以在生成或者转换链的任何一个节点使用TraceClassVisitor,但需要在ClassWriter之前。
还需要注意的是,本章生成的class文本描述可以使用String.equals()进行比较。

2.3.3. CheckClassAdapter

ClassWriter不会检查方法调用的顺序是否合适,以及参数是否合法。
因此生成被Java虚拟机验证器所拒绝的非法class是可行的。
为了尽快的检测到这些错误,可以使用CheckClassAdapter类。
TraceClassVisitor一样,该类也继承了ClassVisitor类,并且代理所有调用转发到其他ClassVisitor,比如转发给TraceClassVisitor对象或者ClassWriter对象。
然而,不同于打印被访问class的文本描述信息,在将调用转发到下一个visitor之前,该类会先检查方法调用顺序是否合适,以及参数是否合法,
如果有错误,会抛出一个IllegalStateException或者IllegalArgumentException异常对象。
为了检测class、打印文本描述,并且最终输出byte数组,你可以使用下面这种方式:

1
2
3
4
5
6
7
ClassWriter cw = new ClassWriter(0);
TraceClassVisitor tcv = new TraceClassVisitor(cw, printWriter);
CheckClassAdapter cv = new CheckClassAdapter(tcv);
cv.visit(...);
...
cv.visitEnd();
byte b[] = cw.toByteArray();

需要注意的是,如果这些class的visitor处在不同的调用链中,这些操作所执行的顺序也会不一样。
例如下面代码所示,检测的vistor会在跟踪的visitor之后执行:

1
2
3
ClassWriter cw = new ClassWriter(0);
CheckClassAdapter cca = new CheckClassAdapter(cw);
TraceClassVisitor cv = new TraceClassVisitor(cca, printWriter);

TraceClassVisitor一样,为了检测节点中class的正确性,可以在生成或者转换调用链中的任意一点使用CheckClassAdapter,但顺序必须在ClassWriter之前:

2.3.4 ASMifier

ASMifier提供了一个代替TraceClassVisitor的后端调用(默认情况下使用一个Textifier,产生如上面所示文本描述)。
这个后端会根据TraceClassVisitor类所调用的每一个方法,打印出生成该方法的Java代码。
例如调用visitEnd()方法会打印‘cv.visitEnd();’
产生的结果是,在后端调用ASMifier访问一个class的时候,就会打印出使用ASM构造该class的代码。
使用这个visitor访问编译后的class是非常有用的。
例如,如果不知道如何使用ASM生成一个编译后的class,你可以直接编写该类的源码,使用javac编译,最后使用ASMifier访问编译后的class。
就可以得到该编译类使用ASM生成的代码了。
ASMifier可以直接在命令行中使用,例如:

java -classpath asm.jar:asm-util.jar \
org.objectweb.asm.util.ASMifier \
java.lang.Runnable

生成的代码,使用缩进后,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package asm.java.lang;
import org.objectweb.asm.*;
public class RunnableDump implements Opcodes {
public static byte[] dump() throws Exception {
ClassWriter cw = new ClassWriter(0);
FieldVisitor fv;
MethodVisitor mv;
AnnotationVisitor av0;
cw.visit(V1_5, ACC_PUBLIC + ACC_ABSTRACT + ACC_INTERFACE,
"java/lang/Runnable", null, "java/lang/Object", null);
mv = cw.visitMethod(ACC_PUBLIC + ACC_ABSTRACT, "run", "()V", null, null);
mv.visitEnd();
cw.visitEnd();
return cw.toByteArray();
}
}

文章目录
  1. 1. 2.3 工具类
    1. 1.1. 2.3.1 类型
    2. 1.2. 2.3.2. TraceClassVisitor
    3. 1.3. 2.3.3. CheckClassAdapter
    4. 1.4. 2.3.4 ASMifier