访问者模式在ASM框架中的使用

原创 吴就业 83 0 2020-04-18

本文为博主原创文章,未经博主允许不得转载。

本文链接:https://wujiuye.com/article/0e7c7471ded449a79e0797ef5ed030e4

作者:吴就业
链接:https://wujiuye.com/article/0e7c7471ded449a79e0797ef5ed030e4
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。

访问者模式的定义是:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。

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框架中,可调用ClassWritertoByteArray方法获取生成的类的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
#后端

声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。

文章推荐

这又是导致事务注解@Transactional不生效的一个原因

事务方法`A`调用事务方法`B`,当方法`B`抛出的异常被方法`A` `catch`后会发生什么?

在同一个线程下数据源多次切换的回溯问题

在某些场景下,我们可能需要多次切换数据源才能处理完同一个请求,也就是在一个线程上多次切换数据源。

深入理解类加载阶段之准备阶段

准备阶段是为类中定义的静态变量分配内存并设置初始化值的阶段,这里的初始值通常情况下指的是对应类型的零值,比如int类型的零值为0。

为什么要使用Redis的多数据库呢?

`Redis`多数据库是我在`Redis`设计中最糟糕的决定,我希望在某种程度上,我们可以放弃多个数据库的支持,但我认为可能已经太晚了,因为有很多人在工作中使用这个特性。

遇到VerifyError束手无策?从虚拟机源码分析原因

在学习使用asm动态生成字节码的过程中,我们或多或少都会遇到这样个错误,那么越到这个问题我们该如何解决呢?

教你如何将开源项目发布到maven中央仓库

如何将开源项目发布到maven中央仓库,让别人通过依赖使用你的开源项目,想必很多朋友都有过这个想法。