访问者模式的定义是:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
ASM
框架使用访问者模式封装了class
文件结构的各项元素的操作,我们将通过实现一个简单版的ASM
框架学习访问者模式在ASM
框架中的应用。
首先定义类访问者接口ClassVisitor
,代码如下。
public interface ClassVisitor {
// 设置class文件结构的版本号、类的访问标志、类名
void visit(int version, String access, String className);
// 为类添加一个字段
FieldVisitor visitField(String access, String name, String descriptor);
// 为类添加一个方法
MethodVisitor visitMethod(String access, String name, String descriptor);
}
我们为类访问者定义了三个方法,visit
方法可设置class
文件结构的版本号、类的访问标志以及类名,visitField
方法可给类添加一个字段,visitMethod
方法可给类添加一个方法。
由于字段元素也是一个数据结构,也可使用访问者模式封装字段结构中各项元素的操作。如通过调用字段访问者的visitAnnotation
方法可为字段添加一个注解。字段访问者接口FieldVisitor
的定义如下。
public interface FieldVisitor {
// 为字段添加一个注解
void visitAnnotation(String annotation, boolean runtime);
}
字段访问者接口的实现类FieldWriter
,代码如下。
@Getter
public class FieldWriter implements FieldVisitor {
private String access;
private String name;
private String descriptor;
private List<String> annotations;
public FieldWriter(String access, String name, String descriptor) {
this.access = access;
this.name = name;
this.descriptor = descriptor;
this.annotations = new ArrayList<>();
}
@Override
public void visitAnnotation(String annotation, boolean runtime) {
this.annotations.add("注解:" + annotation + "," + runtime);
}
}
与字段结构一样,方法结构也可使用访问者模式封装各项元素的操作。如通过调用方法访问者的visitMaxs
可设置方法的操作数栈和局部变量表的大小。方法访问者接口MethodVisitor
的定义如下。
public interface MethodVisitor {
// 设置局部变量表和操作数栈的大小
void visitMaxs(int maxStackSize, int maxLocalSize);
}
方法访问者接口的实现类MethodWriter
,代码如下。
@Getter
public class MethodWriter implements MethodVisitor {
private String access;
private String name;
private String descriptor;
private int maxStackSize;
private int maxLocalSize;
public MethodWriter(String access, String name, String descriptor) {
this.access = access;
this.name = name;
this.descriptor = descriptor;
}
@Override
public void visitMaxs(int maxStackSize, int maxLocalSize) {
this.maxLocalSize = maxLocalSize;
this.maxStackSize = maxStackSize;
}
}
在class
文件结构中,字段表可以有零个或多个字段,方法表可以有一个或多个方法,因此我们需要使用数组存储字段表和方法表。由于方法表和字段表中的每个方法或每个字段都是一个数据结构,因此字段表和方法表的元素类型存储的是字段访问者和方法访问者。现在我们编写类访问者接口的实现类ClassWriter
,代码如下。
@Getter
public class ClassWriter implements ClassVisitor {
private int version;
private String className;
private String access;
private List<FieldWriter> fieldWriters = new ArrayList<>();
private List<MethodWriter> methodWriters = new ArrayList<>();
@Override
public void visit(int version, String access, String className) {
this.version = version;
this.className = className;
this.access = access;
}
@Override
public FieldVisitor visitField(String access, String name, String descriptor) {
FieldWriter fieldWriter = new FieldWriter(access, name, descriptor);
fieldWriters.add(fieldWriter);
return fieldWriter;
}
@Override
public MethodVisitor visitMethod(String access, String name, String descriptor) {
MethodWriter methodWriter = new MethodWriter(access, name, descriptor);
methodWriters.add(methodWriter);
return methodWriter;
}
}
类访问者的visitField
方法先为类添加一个字段元素,创建字段的访问者FieldVisitor
并将字段访问者添加到字段表,最后返回该字段访问者。类访问者的visitMethod
方法先为类添加一个方法元素,创建方法的访问者MethodVisitor
并将访问者添加到方法表,最后返回该方法访问者。
在ASM
框架中,可调用ClassWriter
的toByteArray
方法获取生成的类的class
字节数组,我们可以模拟实现toByteArray
方法,在ClassWriter
添加showClass
方法,如下代码所示。
public void showClass() {
System.out.println("版本号:" + getVersion());
System.out.println("访问标志:" + getAccess());
System.out.println("类名:" + getClassName());
for (FieldWriter fieldWriter : fieldWriters) {
System.out.print(fieldWriter.getAccess()
+ " " + fieldWriter.getDescriptor()
+ " " + fieldWriter.getName()
+ " ");
for (String annotation : fieldWriter.getAnnotations()) {
System.out.println(annotation + " ");
}
}
for (MethodWriter methodWriter : methodWriters) {
System.out.println(methodWriter.getAccess()
+ " " + methodWriter.getName()
+ " " + methodWriter.getDescriptor()
+ " 操作数栈大小:" + methodWriter.getMaxStackSize()
+ " 局部变量表大小:" + methodWriter.getMaxLocalSize());
}
}
现在我们使用自己编写的简单版ASM
框架生成一个类,为该类添加一个字段并为该字段添加一个注解,为类添加一个方法并设置该方法的局部变量表和操作数栈的大小,代码下。
public static void main(String[] args) {
ClassWriter classWriter = new ClassWriter();
classWriter.visit(52, "public", "com.wujiuye.User");
FieldVisitor fieldVisitor = classWriter
.visitField("private", "name", "Ljava/lang/String;");
fieldVisitor.visitAnnotation("@Getter", true);
MethodVisitor methodVisitor = classWriter
.visitMethod("public", "getName", "(Ljava/lang/String)V");
methodVisitor.visitMaxs(1, 1);
classWriter.showClass();
}
结果如下
版本号:52
访问标志:public
类名:com.wujiuye.User
private Ljava/lang/String; name 注解:@Getter,true
public getName (Ljava/lang/String)V 操作数栈大小:1 局部变量表大小:1